feat(breadth): complete the Market Breadth family (14 indicators) (#157)

Completes expansion-roadmap block **A2 — Market Breadth**: the 14 indicators that remained after the `AdvanceDecline` bootstrap, all built on the existing `CrossSection` input.

## Indicators (all scalar `Indicator<Input = CrossSection, Output = f64>`)

| Indicator | Reading |
|-----------|---------|
| `AdvanceDeclineRatio` | advancers / decliners |
| `AdVolumeLine` | cumulative net advancing volume |
| `McClellanOscillator` | 19/39 EMAs of ratio-adjusted net advances |
| `McClellanSummationIndex` | running total of the oscillator |
| `Trin` (Arms Index) | A/D ratio over up/down volume ratio |
| `BreadthThrust` (Zweig) | SMA of the advancing-issues share |
| `NewHighsNewLows` | new highs − new lows |
| `HighLowIndex` | SMA of the record-high percent |
| `PercentAboveMa` | % of the universe above its MA |
| `UpDownVolumeRatio` | advancing / declining volume |
| `BullishPercentIndex` | % on a point-and-figure buy signal |
| `CumulativeVolumeIndex` | volume-normalised cumulative net advancing volume |
| `AbsoluteBreadthIndex` | \|advancers − decliners\| |
| `TickIndex` | instantaneous net advancers − decliners |

## Input model

`AdVolumeLine` and `CumulativeVolumeIndex` are kept distinct (the latter normalises each tick's net advancing volume by total volume, so it stays comparable across volume regimes). `PercentAboveMa` and `BullishPercentIndex` need a per-symbol state signal that `Member` did not carry, so `Member` gains two additive flags (`above_ma`, `on_buy_signal`) via a new `Member::with_signals` constructor; the 4-arg `Member::new` leaves both cleared, so every existing caller and binding is unchanged. `CrossSection` gains volume / new-extreme / state aggregation helpers.

## Wiring

Fully wired across the Rust core, the python/node/wasm bindings, the cross-section fuzz target, the README + docs indicator counters (325 → 339), and dedicated python/node streaming-vs-batch tests. `fmt` / `test --workspace --all-features` / `clippy --workspace -D warnings` / node build+test / pytest all green locally.
This commit is contained in:
kingchenc
2026-06-03 17:24:33 +02:00
committed by GitHub
parent c44f625e69
commit c096943bdf
30 changed files with 5648 additions and 63 deletions
+106
View File
@@ -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);
+126
View File
@@ -3253,6 +3253,132 @@ export declare class AdvanceDecline {
isReady(): boolean
warmupPeriod(): number
}
export type AdvanceDeclineRatioNode = AdvanceDeclineRatio
export declare class AdvanceDeclineRatio {
constructor()
update(change: Array<number>, volume: Array<number>, newHigh: Array<boolean>, newLow: Array<boolean>): number | null
batch(change: Array<Array<number>>, volume: Array<Array<number>>, newHigh: Array<Array<boolean>>, newLow: Array<Array<boolean>>): Array<number>
reset(): void
isReady(): boolean
warmupPeriod(): number
}
export type AdVolumeLineNode = AdVolumeLine
export declare class AdVolumeLine {
constructor()
update(change: Array<number>, volume: Array<number>, newHigh: Array<boolean>, newLow: Array<boolean>): number | null
batch(change: Array<Array<number>>, volume: Array<Array<number>>, newHigh: Array<Array<boolean>>, newLow: Array<Array<boolean>>): Array<number>
reset(): void
isReady(): boolean
warmupPeriod(): number
}
export type McClellanOscillatorNode = McClellanOscillator
export declare class McClellanOscillator {
constructor()
update(change: Array<number>, volume: Array<number>, newHigh: Array<boolean>, newLow: Array<boolean>): number | null
batch(change: Array<Array<number>>, volume: Array<Array<number>>, newHigh: Array<Array<boolean>>, newLow: Array<Array<boolean>>): Array<number>
reset(): void
isReady(): boolean
warmupPeriod(): number
}
export type McClellanSummationIndexNode = McClellanSummationIndex
export declare class McClellanSummationIndex {
constructor()
update(change: Array<number>, volume: Array<number>, newHigh: Array<boolean>, newLow: Array<boolean>): number | null
batch(change: Array<Array<number>>, volume: Array<Array<number>>, newHigh: Array<Array<boolean>>, newLow: Array<Array<boolean>>): Array<number>
reset(): void
isReady(): boolean
warmupPeriod(): number
}
export type TrinNode = Trin
export declare class Trin {
constructor()
update(change: Array<number>, volume: Array<number>, newHigh: Array<boolean>, newLow: Array<boolean>): number | null
batch(change: Array<Array<number>>, volume: Array<Array<number>>, newHigh: Array<Array<boolean>>, newLow: Array<Array<boolean>>): Array<number>
reset(): void
isReady(): boolean
warmupPeriod(): number
}
export type BreadthThrustNode = BreadthThrust
export declare class BreadthThrust {
constructor(period: number)
update(change: Array<number>, volume: Array<number>, newHigh: Array<boolean>, newLow: Array<boolean>): number | null
batch(change: Array<Array<number>>, volume: Array<Array<number>>, newHigh: Array<Array<boolean>>, newLow: Array<Array<boolean>>): Array<number>
reset(): void
isReady(): boolean
warmupPeriod(): number
}
export type NewHighsNewLowsNode = NewHighsNewLows
export declare class NewHighsNewLows {
constructor()
update(change: Array<number>, volume: Array<number>, newHigh: Array<boolean>, newLow: Array<boolean>): number | null
batch(change: Array<Array<number>>, volume: Array<Array<number>>, newHigh: Array<Array<boolean>>, newLow: Array<Array<boolean>>): Array<number>
reset(): void
isReady(): boolean
warmupPeriod(): number
}
export type HighLowIndexNode = HighLowIndex
export declare class HighLowIndex {
constructor(period: number)
update(change: Array<number>, volume: Array<number>, newHigh: Array<boolean>, newLow: Array<boolean>): number | null
batch(change: Array<Array<number>>, volume: Array<Array<number>>, newHigh: Array<Array<boolean>>, newLow: Array<Array<boolean>>): Array<number>
reset(): void
isReady(): boolean
warmupPeriod(): number
}
export type PercentAboveMaNode = PercentAboveMa
export declare class PercentAboveMa {
constructor()
update(change: Array<number>, volume: Array<number>, newHigh: Array<boolean>, newLow: Array<boolean>, aboveMa: Array<boolean>): number | null
batch(change: Array<Array<number>>, volume: Array<Array<number>>, newHigh: Array<Array<boolean>>, newLow: Array<Array<boolean>>, aboveMa: Array<Array<boolean>>): Array<number>
reset(): void
isReady(): boolean
warmupPeriod(): number
}
export type UpDownVolumeRatioNode = UpDownVolumeRatio
export declare class UpDownVolumeRatio {
constructor()
update(change: Array<number>, volume: Array<number>, newHigh: Array<boolean>, newLow: Array<boolean>): number | null
batch(change: Array<Array<number>>, volume: Array<Array<number>>, newHigh: Array<Array<boolean>>, newLow: Array<Array<boolean>>): Array<number>
reset(): void
isReady(): boolean
warmupPeriod(): number
}
export type BullishPercentIndexNode = BullishPercentIndex
export declare class BullishPercentIndex {
constructor()
update(change: Array<number>, volume: Array<number>, newHigh: Array<boolean>, newLow: Array<boolean>, onBuySignal: Array<boolean>): number | null
batch(change: Array<Array<number>>, volume: Array<Array<number>>, newHigh: Array<Array<boolean>>, newLow: Array<Array<boolean>>, onBuySignal: Array<Array<boolean>>): Array<number>
reset(): void
isReady(): boolean
warmupPeriod(): number
}
export type CumulativeVolumeIndexNode = CumulativeVolumeIndex
export declare class CumulativeVolumeIndex {
constructor()
update(change: Array<number>, volume: Array<number>, newHigh: Array<boolean>, newLow: Array<boolean>): number | null
batch(change: Array<Array<number>>, volume: Array<Array<number>>, newHigh: Array<Array<boolean>>, newLow: Array<Array<boolean>>): Array<number>
reset(): void
isReady(): boolean
warmupPeriod(): number
}
export type AbsoluteBreadthIndexNode = AbsoluteBreadthIndex
export declare class AbsoluteBreadthIndex {
constructor()
update(change: Array<number>, volume: Array<number>, newHigh: Array<boolean>, newLow: Array<boolean>): number | null
batch(change: Array<Array<number>>, volume: Array<Array<number>>, newHigh: Array<Array<boolean>>, newLow: Array<Array<boolean>>): Array<number>
reset(): void
isReady(): boolean
warmupPeriod(): number
}
export type TickIndexNode = TickIndex
export declare class TickIndex {
constructor()
update(change: Array<number>, volume: Array<number>, newHigh: Array<boolean>, newLow: Array<boolean>): number | null
batch(change: Array<Array<number>>, volume: Array<Array<number>>, newHigh: Array<Array<boolean>>, newLow: Array<Array<boolean>>): Array<number>
reset(): void
isReady(): boolean
warmupPeriod(): number
}
export type SharpeRatioNode = SharpeRatio
export declare class SharpeRatio {
constructor(period: number, riskFree: number)
+15 -1
View File
@@ -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
File diff suppressed because it is too large Load Diff
+28
View File
@@ -352,6 +352,20 @@ from ._wickra import (
TermStructureBasis,
CalendarSpread,
# Market Breadth
TickIndex,
AbsoluteBreadthIndex,
CumulativeVolumeIndex,
BullishPercentIndex,
UpDownVolumeRatio,
PercentAboveMa,
HighLowIndex,
NewHighsNewLows,
BreadthThrust,
Trin,
McClellanSummationIndex,
McClellanOscillator,
AdVolumeLine,
AdvanceDeclineRatio,
AdvanceDecline,
# Risk / Performance
SharpeRatio,
@@ -702,6 +716,20 @@ __all__ = [
"TermStructureBasis",
"CalendarSpread",
# Market Breadth
"TickIndex",
"AbsoluteBreadthIndex",
"CumulativeVolumeIndex",
"BullishPercentIndex",
"UpDownVolumeRatio",
"PercentAboveMa",
"HighLowIndex",
"NewHighsNewLows",
"BreadthThrust",
"Trin",
"McClellanSummationIndex",
"McClellanOscillator",
"AdVolumeLine",
"AdvanceDeclineRatio",
"AdvanceDecline",
# Risk / Performance
"SharpeRatio",
File diff suppressed because it is too large Load Diff
@@ -2783,6 +2783,190 @@ def test_advance_decline_rejects_ragged_universe():
ad.update([1.0, -1.0], [10.0], [False, False], [False, False])
def _breadth_streaming_equals_batch(indicator, change, volume, new_high, new_low):
"""Assert a 4-array breadth indicator's batch matches its streaming output."""
batch = indicator().batch(change, volume, new_high, new_low)
streamer = indicator()
streamed = np.array(
[
streamer.update(change[i], volume[i], new_high[i], new_low[i])
for i in range(len(change))
],
dtype=np.float64,
)
assert batch.shape == (len(change),)
assert _eq_nan(batch, streamed)
return batch
def test_advance_decline_ratio_breadth():
change = [[1.0, 1.0, 1.0, -1.0], [1.0, 0.0, 0.0, 0.0], [-1.0, -1.0, -1.0, -1.0]]
volume = [[10.0] * 4 for _ in range(3)]
flags = [[False] * 4 for _ in range(3)]
batch = _breadth_streaming_equals_batch(ta.AdvanceDeclineRatio, change, volume, flags, flags)
# 3/1 = 3 ; 1/max(0,1) = 1 ; 0/3 = 0.
assert list(batch) == [3.0, 1.0, 0.0]
def test_ad_volume_line_breadth():
change = [[1.0, -1.0], [1.0, -1.0], [1.0, 0.0]]
volume = [[150.0, 50.0], [60.0, 60.0], [30.0, 0.0]]
flags = [[False] * 2 for _ in range(3)]
batch = _breadth_streaming_equals_batch(ta.AdVolumeLine, change, volume, flags, flags)
# net +100 -> 100 ; net 0 -> 100 ; net +30 -> 130.
assert list(batch) == [100.0, 100.0, 130.0]
def test_mcclellan_oscillator_breadth():
change = [[1.0, 1.0, 1.0, -1.0], [-1.0, -1.0, -1.0, 1.0], [1.0, 1.0, -1.0, -1.0]]
volume = [[10.0] * 4 for _ in range(3)]
flags = [[False] * 4 for _ in range(3)]
batch = _breadth_streaming_equals_batch(ta.McClellanOscillator, change, volume, flags, flags)
# seed 0 ; -50 ; -67.5.
assert abs(batch[0]) < 1e-9
assert abs(batch[1] - (-50.0)) < 1e-9
assert abs(batch[2] - (-67.5)) < 1e-9
def test_mcclellan_summation_index_breadth():
change = [[1.0, 1.0, 1.0, -1.0], [-1.0, -1.0, -1.0, 1.0], [1.0, 1.0, -1.0, -1.0]]
volume = [[10.0] * 4 for _ in range(3)]
flags = [[False] * 4 for _ in range(3)]
batch = _breadth_streaming_equals_batch(ta.McClellanSummationIndex, change, volume, flags, flags)
# 0 ; -50 ; -117.5.
assert abs(batch[0]) < 1e-9
assert abs(batch[1] - (-50.0)) < 1e-9
assert abs(batch[2] - (-117.5)) < 1e-9
def test_trin_breadth():
change = [[1.0, 1.0, 1.0, -1.0], [1.0, 1.0, -1.0, -1.0]]
volume = [[50.0, 50.0, 50.0, 50.0], [10.0, 10.0, 40.0, 40.0]]
flags = [[False] * 4 for _ in range(2)]
batch = _breadth_streaming_equals_batch(ta.Trin, change, volume, flags, flags)
# (3/1)/(150/50) = 1 ; (2/2)/(20/80) = 4.
assert abs(batch[0] - 1.0) < 1e-9
assert abs(batch[1] - 4.0) < 1e-9
def test_breadth_thrust_breadth():
change = [[1.0] * 8 + [-1.0] * 2, [1.0] * 6 + [-1.0] * 4]
volume = [[10.0] * 10 for _ in range(2)]
flags = [[False] * 10 for _ in range(2)]
batch = ta.BreadthThrust(2).batch(change, volume, flags, flags)
streamer = ta.BreadthThrust(2)
streamed = np.array(
[streamer.update(change[i], volume[i], flags[i], flags[i]) for i in range(2)],
dtype=np.float64,
)
assert _eq_nan(batch, streamed)
# 0.8 (warmup -> NaN) ; SMA(2) of [0.8, 0.6] = 0.7.
assert math.isnan(batch[0])
assert abs(batch[1] - 0.7) < 1e-9
def test_new_highs_new_lows_breadth():
change = [[1.0, 1.0, -1.0], [1.0, -1.0, -1.0]]
volume = [[10.0] * 3 for _ in range(2)]
new_high = [[True, True, False], [True, False, False]]
new_low = [[False, False, True], [False, True, True]]
batch = _breadth_streaming_equals_batch(ta.NewHighsNewLows, change, volume, new_high, new_low)
# 2 - 1 = 1 ; 1 - 2 = -1.
assert list(batch) == [1.0, -1.0]
def test_high_low_index_breadth():
change = [[1.0] * 10, [1.0] * 10]
volume = [[10.0] * 10 for _ in range(2)]
new_high = [[True] * 8 + [False] * 2, [True] * 6 + [False] * 4]
new_low = [[False] * 8 + [True] * 2, [False] * 6 + [True] * 4]
batch = ta.HighLowIndex(2).batch(change, volume, new_high, new_low)
streamer = ta.HighLowIndex(2)
streamed = np.array(
[streamer.update(change[i], volume[i], new_high[i], new_low[i]) for i in range(2)],
dtype=np.float64,
)
assert _eq_nan(batch, streamed)
# 80% (warmup) ; SMA(2) of [80, 60] = 70.
assert math.isnan(batch[0])
assert abs(batch[1] - 70.0) < 1e-9
def test_percent_above_ma_breadth():
change = [[1.0, 1.0, 1.0, -1.0], [1.0, 1.0, -1.0, -1.0]]
volume = [[10.0] * 4 for _ in range(2)]
flags = [[False] * 4 for _ in range(2)]
above_ma = [[True, True, True, False], [True, False, False, False]]
batch = ta.PercentAboveMa().batch(change, volume, flags, flags, above_ma)
streamer = ta.PercentAboveMa()
streamed = np.array(
[streamer.update(change[i], volume[i], flags[i], flags[i], above_ma[i]) for i in range(2)],
dtype=np.float64,
)
assert _eq_nan(batch, streamed)
# 3/4 -> 75 ; 1/4 -> 25.
assert list(batch) == [75.0, 25.0]
def test_up_down_volume_ratio_breadth():
change = [[1.0, -1.0], [1.0, 0.0]]
volume = [[150.0, 50.0], [100.0, 0.0]]
flags = [[False] * 2 for _ in range(2)]
batch = _breadth_streaming_equals_batch(ta.UpDownVolumeRatio, change, volume, flags, flags)
# 150/50 = 3 ; 100/max(0,1) = 100.
assert list(batch) == [3.0, 100.0]
def test_bullish_percent_index_breadth():
change = [[1.0, 1.0, -1.0, -1.0], [1.0, 1.0, 1.0, 1.0]]
volume = [[10.0] * 4 for _ in range(2)]
flags = [[False] * 4 for _ in range(2)]
on_buy = [[True, True, False, False], [True, True, True, True]]
batch = ta.BullishPercentIndex().batch(change, volume, flags, flags, on_buy)
streamer = ta.BullishPercentIndex()
streamed = np.array(
[streamer.update(change[i], volume[i], flags[i], flags[i], on_buy[i]) for i in range(2)],
dtype=np.float64,
)
assert _eq_nan(batch, streamed)
# 2/4 -> 50 ; 4/4 -> 100.
assert list(batch) == [50.0, 100.0]
def test_cumulative_volume_index_breadth():
change = [[1.0, -1.0], [1.0, -1.0], [0.0]]
volume = [[150.0, 50.0], [60.0, 60.0], [0.0]]
new_high = [[False, False], [False, False], [False]]
new_low = [[False, False], [False, False], [False]]
batch = ta.CumulativeVolumeIndex().batch(change, volume, new_high, new_low)
streamer = ta.CumulativeVolumeIndex()
streamed = np.array(
[streamer.update(change[i], volume[i], new_high[i], new_low[i]) for i in range(3)],
dtype=np.float64,
)
assert _eq_nan(batch, streamed)
# (100/200) -> 0.5 ; net 0 -> 0.5 ; zero-volume tick -> 0.5.
assert list(batch) == [0.5, 0.5, 0.5]
def test_absolute_breadth_index_breadth():
change = [[1.0, 1.0, -1.0, -1.0, -1.0], [1.0, 1.0, 1.0, -1.0, -1.0]]
volume = [[10.0] * 5 for _ in range(2)]
flags = [[False] * 5 for _ in range(2)]
batch = _breadth_streaming_equals_batch(ta.AbsoluteBreadthIndex, change, volume, flags, flags)
# |2 - 3| = 1 ; |3 - 2| = 1.
assert list(batch) == [1.0, 1.0]
def test_tick_index_breadth():
change = [[1.0, 1.0, -1.0, -1.0, -1.0], [1.0, 1.0, 1.0, -1.0, -1.0]]
volume = [[10.0] * 5 for _ in range(2)]
flags = [[False] * 5 for _ in range(2)]
batch = _breadth_streaming_equals_batch(ta.TickIndex, change, volume, flags, flags)
# 2 - 3 = -1 ; 3 - 2 = 1.
assert list(batch) == [-1.0, 1.0]
def test_funding_basis_streaming_equals_batch():
n = 40
index = np.array([100.0 + 0.5 * math.sin(i * 0.2) for i in range(n)], dtype=np.float64)
+658
View File
@@ -8437,6 +8437,664 @@ impl WasmAdvanceDecline {
}
}
fn build_cross_section_above_ma(
change: &[f64],
volume: &[f64],
new_high: &[f64],
new_low: &[f64],
above_ma: &[f64],
) -> Result<wc::CrossSection, JsError> {
if change.len() != volume.len()
|| change.len() != new_high.len()
|| change.len() != new_low.len()
|| change.len() != above_ma.len()
{
return Err(JsError::new(
"change, volume, newHigh, newLow and aboveMa must be equal length",
));
}
let members = (0..change.len())
.map(|i| {
wc::Member::with_signals(
change[i],
volume[i],
new_high[i] != 0.0,
new_low[i] != 0.0,
above_ma[i] != 0.0,
false,
)
})
.collect();
wc::CrossSection::new(members, 0).map_err(map_err)
}
fn build_cross_section_buy(
change: &[f64],
volume: &[f64],
new_high: &[f64],
new_low: &[f64],
on_buy_signal: &[f64],
) -> Result<wc::CrossSection, JsError> {
if change.len() != volume.len()
|| change.len() != new_high.len()
|| change.len() != new_low.len()
|| change.len() != on_buy_signal.len()
{
return Err(JsError::new(
"change, volume, newHigh, newLow and onBuySignal must be equal length",
));
}
let members = (0..change.len())
.map(|i| {
wc::Member::with_signals(
change[i],
volume[i],
new_high[i] != 0.0,
new_low[i] != 0.0,
false,
on_buy_signal[i] != 0.0,
)
})
.collect();
wc::CrossSection::new(members, 0).map_err(map_err)
}
#[wasm_bindgen(js_name = AdvanceDeclineRatio)]
pub struct WasmAdvanceDeclineRatio {
inner: wc::AdvanceDeclineRatio,
}
impl Default for WasmAdvanceDeclineRatio {
fn default() -> Self {
Self::new()
}
}
#[wasm_bindgen(js_class = AdvanceDeclineRatio)]
impl WasmAdvanceDeclineRatio {
#[wasm_bindgen(constructor)]
pub fn new() -> WasmAdvanceDeclineRatio {
Self {
inner: wc::AdvanceDeclineRatio::new(),
}
}
pub fn update(
&mut self,
change: Vec<f64>,
volume: Vec<f64>,
new_high: Vec<f64>,
new_low: Vec<f64>,
) -> Result<Option<f64>, JsError> {
Ok(self
.inner
.update(build_cross_section(&change, &volume, &new_high, &new_low)?))
}
pub fn reset(&mut self) {
self.inner.reset();
}
#[wasm_bindgen(js_name = isReady)]
pub fn is_ready(&self) -> bool {
self.inner.is_ready()
}
#[wasm_bindgen(js_name = warmupPeriod)]
pub fn warmup_period(&self) -> usize {
self.inner.warmup_period()
}
}
#[wasm_bindgen(js_name = AdVolumeLine)]
pub struct WasmAdVolumeLine {
inner: wc::AdVolumeLine,
}
impl Default for WasmAdVolumeLine {
fn default() -> Self {
Self::new()
}
}
#[wasm_bindgen(js_class = AdVolumeLine)]
impl WasmAdVolumeLine {
#[wasm_bindgen(constructor)]
pub fn new() -> WasmAdVolumeLine {
Self {
inner: wc::AdVolumeLine::new(),
}
}
pub fn update(
&mut self,
change: Vec<f64>,
volume: Vec<f64>,
new_high: Vec<f64>,
new_low: Vec<f64>,
) -> Result<Option<f64>, JsError> {
Ok(self
.inner
.update(build_cross_section(&change, &volume, &new_high, &new_low)?))
}
pub fn reset(&mut self) {
self.inner.reset();
}
#[wasm_bindgen(js_name = isReady)]
pub fn is_ready(&self) -> bool {
self.inner.is_ready()
}
#[wasm_bindgen(js_name = warmupPeriod)]
pub fn warmup_period(&self) -> usize {
self.inner.warmup_period()
}
}
#[wasm_bindgen(js_name = McClellanOscillator)]
pub struct WasmMcClellanOscillator {
inner: wc::McClellanOscillator,
}
impl Default for WasmMcClellanOscillator {
fn default() -> Self {
Self::new()
}
}
#[wasm_bindgen(js_class = McClellanOscillator)]
impl WasmMcClellanOscillator {
#[wasm_bindgen(constructor)]
pub fn new() -> WasmMcClellanOscillator {
Self {
inner: wc::McClellanOscillator::new(),
}
}
pub fn update(
&mut self,
change: Vec<f64>,
volume: Vec<f64>,
new_high: Vec<f64>,
new_low: Vec<f64>,
) -> Result<Option<f64>, JsError> {
Ok(self
.inner
.update(build_cross_section(&change, &volume, &new_high, &new_low)?))
}
pub fn reset(&mut self) {
self.inner.reset();
}
#[wasm_bindgen(js_name = isReady)]
pub fn is_ready(&self) -> bool {
self.inner.is_ready()
}
#[wasm_bindgen(js_name = warmupPeriod)]
pub fn warmup_period(&self) -> usize {
self.inner.warmup_period()
}
}
#[wasm_bindgen(js_name = McClellanSummationIndex)]
pub struct WasmMcClellanSummationIndex {
inner: wc::McClellanSummationIndex,
}
impl Default for WasmMcClellanSummationIndex {
fn default() -> Self {
Self::new()
}
}
#[wasm_bindgen(js_class = McClellanSummationIndex)]
impl WasmMcClellanSummationIndex {
#[wasm_bindgen(constructor)]
pub fn new() -> WasmMcClellanSummationIndex {
Self {
inner: wc::McClellanSummationIndex::new(),
}
}
pub fn update(
&mut self,
change: Vec<f64>,
volume: Vec<f64>,
new_high: Vec<f64>,
new_low: Vec<f64>,
) -> Result<Option<f64>, JsError> {
Ok(self
.inner
.update(build_cross_section(&change, &volume, &new_high, &new_low)?))
}
pub fn reset(&mut self) {
self.inner.reset();
}
#[wasm_bindgen(js_name = isReady)]
pub fn is_ready(&self) -> bool {
self.inner.is_ready()
}
#[wasm_bindgen(js_name = warmupPeriod)]
pub fn warmup_period(&self) -> usize {
self.inner.warmup_period()
}
}
#[wasm_bindgen(js_name = Trin)]
pub struct WasmTrin {
inner: wc::Trin,
}
impl Default for WasmTrin {
fn default() -> Self {
Self::new()
}
}
#[wasm_bindgen(js_class = Trin)]
impl WasmTrin {
#[wasm_bindgen(constructor)]
pub fn new() -> WasmTrin {
Self {
inner: wc::Trin::new(),
}
}
pub fn update(
&mut self,
change: Vec<f64>,
volume: Vec<f64>,
new_high: Vec<f64>,
new_low: Vec<f64>,
) -> Result<Option<f64>, JsError> {
Ok(self
.inner
.update(build_cross_section(&change, &volume, &new_high, &new_low)?))
}
pub fn reset(&mut self) {
self.inner.reset();
}
#[wasm_bindgen(js_name = isReady)]
pub fn is_ready(&self) -> bool {
self.inner.is_ready()
}
#[wasm_bindgen(js_name = warmupPeriod)]
pub fn warmup_period(&self) -> usize {
self.inner.warmup_period()
}
}
#[wasm_bindgen(js_name = BreadthThrust)]
pub struct WasmBreadthThrust {
inner: wc::BreadthThrust,
}
#[wasm_bindgen(js_class = BreadthThrust)]
impl WasmBreadthThrust {
#[wasm_bindgen(constructor)]
pub fn new(period: usize) -> Result<WasmBreadthThrust, JsError> {
Ok(WasmBreadthThrust {
inner: wc::BreadthThrust::new(period).map_err(map_err)?,
})
}
pub fn update(
&mut self,
change: Vec<f64>,
volume: Vec<f64>,
new_high: Vec<f64>,
new_low: Vec<f64>,
) -> Result<Option<f64>, JsError> {
Ok(self
.inner
.update(build_cross_section(&change, &volume, &new_high, &new_low)?))
}
pub fn reset(&mut self) {
self.inner.reset();
}
#[wasm_bindgen(js_name = isReady)]
pub fn is_ready(&self) -> bool {
self.inner.is_ready()
}
#[wasm_bindgen(js_name = warmupPeriod)]
pub fn warmup_period(&self) -> usize {
self.inner.warmup_period()
}
}
#[wasm_bindgen(js_name = NewHighsNewLows)]
pub struct WasmNewHighsNewLows {
inner: wc::NewHighsNewLows,
}
impl Default for WasmNewHighsNewLows {
fn default() -> Self {
Self::new()
}
}
#[wasm_bindgen(js_class = NewHighsNewLows)]
impl WasmNewHighsNewLows {
#[wasm_bindgen(constructor)]
pub fn new() -> WasmNewHighsNewLows {
Self {
inner: wc::NewHighsNewLows::new(),
}
}
pub fn update(
&mut self,
change: Vec<f64>,
volume: Vec<f64>,
new_high: Vec<f64>,
new_low: Vec<f64>,
) -> Result<Option<f64>, JsError> {
Ok(self
.inner
.update(build_cross_section(&change, &volume, &new_high, &new_low)?))
}
pub fn reset(&mut self) {
self.inner.reset();
}
#[wasm_bindgen(js_name = isReady)]
pub fn is_ready(&self) -> bool {
self.inner.is_ready()
}
#[wasm_bindgen(js_name = warmupPeriod)]
pub fn warmup_period(&self) -> usize {
self.inner.warmup_period()
}
}
#[wasm_bindgen(js_name = HighLowIndex)]
pub struct WasmHighLowIndex {
inner: wc::HighLowIndex,
}
#[wasm_bindgen(js_class = HighLowIndex)]
impl WasmHighLowIndex {
#[wasm_bindgen(constructor)]
pub fn new(period: usize) -> Result<WasmHighLowIndex, JsError> {
Ok(WasmHighLowIndex {
inner: wc::HighLowIndex::new(period).map_err(map_err)?,
})
}
pub fn update(
&mut self,
change: Vec<f64>,
volume: Vec<f64>,
new_high: Vec<f64>,
new_low: Vec<f64>,
) -> Result<Option<f64>, JsError> {
Ok(self
.inner
.update(build_cross_section(&change, &volume, &new_high, &new_low)?))
}
pub fn reset(&mut self) {
self.inner.reset();
}
#[wasm_bindgen(js_name = isReady)]
pub fn is_ready(&self) -> bool {
self.inner.is_ready()
}
#[wasm_bindgen(js_name = warmupPeriod)]
pub fn warmup_period(&self) -> usize {
self.inner.warmup_period()
}
}
#[wasm_bindgen(js_name = PercentAboveMa)]
pub struct WasmPercentAboveMa {
inner: wc::PercentAboveMa,
}
impl Default for WasmPercentAboveMa {
fn default() -> Self {
Self::new()
}
}
#[wasm_bindgen(js_class = PercentAboveMa)]
impl WasmPercentAboveMa {
#[wasm_bindgen(constructor)]
pub fn new() -> WasmPercentAboveMa {
Self {
inner: wc::PercentAboveMa::new(),
}
}
pub fn update(
&mut self,
change: Vec<f64>,
volume: Vec<f64>,
new_high: Vec<f64>,
new_low: Vec<f64>,
above_ma: Vec<f64>,
) -> Result<Option<f64>, JsError> {
Ok(self.inner.update(build_cross_section_above_ma(
&change, &volume, &new_high, &new_low, &above_ma,
)?))
}
pub fn reset(&mut self) {
self.inner.reset();
}
#[wasm_bindgen(js_name = isReady)]
pub fn is_ready(&self) -> bool {
self.inner.is_ready()
}
#[wasm_bindgen(js_name = warmupPeriod)]
pub fn warmup_period(&self) -> usize {
self.inner.warmup_period()
}
}
#[wasm_bindgen(js_name = UpDownVolumeRatio)]
pub struct WasmUpDownVolumeRatio {
inner: wc::UpDownVolumeRatio,
}
impl Default for WasmUpDownVolumeRatio {
fn default() -> Self {
Self::new()
}
}
#[wasm_bindgen(js_class = UpDownVolumeRatio)]
impl WasmUpDownVolumeRatio {
#[wasm_bindgen(constructor)]
pub fn new() -> WasmUpDownVolumeRatio {
Self {
inner: wc::UpDownVolumeRatio::new(),
}
}
pub fn update(
&mut self,
change: Vec<f64>,
volume: Vec<f64>,
new_high: Vec<f64>,
new_low: Vec<f64>,
) -> Result<Option<f64>, JsError> {
Ok(self
.inner
.update(build_cross_section(&change, &volume, &new_high, &new_low)?))
}
pub fn reset(&mut self) {
self.inner.reset();
}
#[wasm_bindgen(js_name = isReady)]
pub fn is_ready(&self) -> bool {
self.inner.is_ready()
}
#[wasm_bindgen(js_name = warmupPeriod)]
pub fn warmup_period(&self) -> usize {
self.inner.warmup_period()
}
}
#[wasm_bindgen(js_name = BullishPercentIndex)]
pub struct WasmBullishPercentIndex {
inner: wc::BullishPercentIndex,
}
impl Default for WasmBullishPercentIndex {
fn default() -> Self {
Self::new()
}
}
#[wasm_bindgen(js_class = BullishPercentIndex)]
impl WasmBullishPercentIndex {
#[wasm_bindgen(constructor)]
pub fn new() -> WasmBullishPercentIndex {
Self {
inner: wc::BullishPercentIndex::new(),
}
}
pub fn update(
&mut self,
change: Vec<f64>,
volume: Vec<f64>,
new_high: Vec<f64>,
new_low: Vec<f64>,
on_buy_signal: Vec<f64>,
) -> Result<Option<f64>, JsError> {
Ok(self.inner.update(build_cross_section_buy(
&change,
&volume,
&new_high,
&new_low,
&on_buy_signal,
)?))
}
pub fn reset(&mut self) {
self.inner.reset();
}
#[wasm_bindgen(js_name = isReady)]
pub fn is_ready(&self) -> bool {
self.inner.is_ready()
}
#[wasm_bindgen(js_name = warmupPeriod)]
pub fn warmup_period(&self) -> usize {
self.inner.warmup_period()
}
}
#[wasm_bindgen(js_name = CumulativeVolumeIndex)]
pub struct WasmCumulativeVolumeIndex {
inner: wc::CumulativeVolumeIndex,
}
impl Default for WasmCumulativeVolumeIndex {
fn default() -> Self {
Self::new()
}
}
#[wasm_bindgen(js_class = CumulativeVolumeIndex)]
impl WasmCumulativeVolumeIndex {
#[wasm_bindgen(constructor)]
pub fn new() -> WasmCumulativeVolumeIndex {
Self {
inner: wc::CumulativeVolumeIndex::new(),
}
}
pub fn update(
&mut self,
change: Vec<f64>,
volume: Vec<f64>,
new_high: Vec<f64>,
new_low: Vec<f64>,
) -> Result<Option<f64>, JsError> {
Ok(self
.inner
.update(build_cross_section(&change, &volume, &new_high, &new_low)?))
}
pub fn reset(&mut self) {
self.inner.reset();
}
#[wasm_bindgen(js_name = isReady)]
pub fn is_ready(&self) -> bool {
self.inner.is_ready()
}
#[wasm_bindgen(js_name = warmupPeriod)]
pub fn warmup_period(&self) -> usize {
self.inner.warmup_period()
}
}
#[wasm_bindgen(js_name = AbsoluteBreadthIndex)]
pub struct WasmAbsoluteBreadthIndex {
inner: wc::AbsoluteBreadthIndex,
}
impl Default for WasmAbsoluteBreadthIndex {
fn default() -> Self {
Self::new()
}
}
#[wasm_bindgen(js_class = AbsoluteBreadthIndex)]
impl WasmAbsoluteBreadthIndex {
#[wasm_bindgen(constructor)]
pub fn new() -> WasmAbsoluteBreadthIndex {
Self {
inner: wc::AbsoluteBreadthIndex::new(),
}
}
pub fn update(
&mut self,
change: Vec<f64>,
volume: Vec<f64>,
new_high: Vec<f64>,
new_low: Vec<f64>,
) -> Result<Option<f64>, JsError> {
Ok(self
.inner
.update(build_cross_section(&change, &volume, &new_high, &new_low)?))
}
pub fn reset(&mut self) {
self.inner.reset();
}
#[wasm_bindgen(js_name = isReady)]
pub fn is_ready(&self) -> bool {
self.inner.is_ready()
}
#[wasm_bindgen(js_name = warmupPeriod)]
pub fn warmup_period(&self) -> usize {
self.inner.warmup_period()
}
}
#[wasm_bindgen(js_name = TickIndex)]
pub struct WasmTickIndex {
inner: wc::TickIndex,
}
impl Default for WasmTickIndex {
fn default() -> Self {
Self::new()
}
}
#[wasm_bindgen(js_class = TickIndex)]
impl WasmTickIndex {
#[wasm_bindgen(constructor)]
pub fn new() -> WasmTickIndex {
Self {
inner: wc::TickIndex::new(),
}
}
pub fn update(
&mut self,
change: Vec<f64>,
volume: Vec<f64>,
new_high: Vec<f64>,
new_low: Vec<f64>,
) -> Result<Option<f64>, JsError> {
Ok(self
.inner
.update(build_cross_section(&change, &volume, &new_high, &new_low)?))
}
pub fn reset(&mut self) {
self.inner.reset();
}
#[wasm_bindgen(js_name = isReady)]
pub fn is_ready(&self) -> bool {
self.inner.is_ready()
}
#[wasm_bindgen(js_name = warmupPeriod)]
pub fn warmup_period(&self) -> usize {
self.inner.warmup_period()
}
}
#[cfg(test)]
mod tests {
use super::*;