feat: TA-Lib parity — 19 standalone indicators (DM components, price transforms, ROC/LinReg/MACD/SAR variants, Hilbert outputs) (#148)
Closes the remaining TA-Lib function-name gap by shipping each missing or bundled-only function as a real, standalone, fully-covered indicator. 19 new indicators across 5 families; mod-count 295 -> 314. ### Trend & Directional — Directional Movement components - `PlusDm` (`PLUS_DM`), `MinusDm` (`MINUS_DM`) — Wilder-smoothed ±DM. - `PlusDi` (`PLUS_DI`), `MinusDi` (`MINUS_DI`) — `100·smoothed(±DM)/ATR`. - `Dx` (`DX`) — `100·|+DI−−DI|/(+DI+−DI)`. ### Price Statistics - `AvgPrice` (`AVGPRICE`) — `(O+H+L+C)/4`. - `MidPoint` (`MIDPOINT`) — `(max+min)/2` of a scalar series over N. - `MidPrice` (`MIDPRICE`) — `(highestHigh+lowestLow)/2` over N. - `LinRegIntercept` (`LINEARREG_INTERCEPT`) — OLS intercept. - `Tsf` (`TSF`) — time series forecast `a + b·period`. ### Momentum Oscillators - `Rocp` (`ROCP`), `Rocr` (`ROCR`), `Rocr100` (`ROCR100`) — ROC ratio forms. ### Trailing Stops - `SarExt` (`SAREXT`) — Parabolic SAR with start value, reversal offset, separate long/short acceleration, signed output. ### Trend & Directional — MACD variants - `MacdFix` (`MACDFIX`) — MACD fixed 12/26. - `MacdExt` (`MACDEXT`) — MACD with a selectable moving-average type per line (new public `MaType` enum: SMA/EMA/WMA/DEMA/TEMA/TRIMA). ### Ehlers / Cycle (DSP) — Hilbert transform outputs - `HtPhasor` (`HT_PHASOR`) — in-phase / quadrature components. - `HtDcPhase` (`HT_DCPHASE`) — dominant-cycle phase (degrees). - `HtTrendMode` (`HT_TRENDMODE`) — trend (1) vs cycle (0) classification. Each indicator ships the full chain: core + every-branch unit tests, Python / Node / WASM bindings, fuzz coverage, README counter + family rows, CHANGELOG. `cargo test`, doctests, `clippy -D warnings`, `npm test` and pytest all green locally; mod-count == lib-block == README counter (314), FAMILIES total 309.
This commit is contained in:
@@ -28,6 +28,12 @@ function num(v) {
|
||||
// --- Scalar indicators: update(value) vs batch(prices) ---
|
||||
|
||||
const scalarFactories = {
|
||||
TSF: () => new wickra.TSF(14),
|
||||
LINEARREG_INTERCEPT: () => new wickra.LINEARREG_INTERCEPT(14),
|
||||
ROCR100: () => new wickra.ROCR100(10),
|
||||
ROCR: () => new wickra.ROCR(10),
|
||||
ROCP: () => new wickra.ROCP(10),
|
||||
MIDPOINT: () => new wickra.MIDPOINT(14),
|
||||
SMA: () => new wickra.SMA(14),
|
||||
EMA: () => new wickra.EMA(14),
|
||||
WMA: () => new wickra.WMA(14),
|
||||
@@ -90,6 +96,8 @@ const scalarFactories = {
|
||||
EhlersStochastic: () => new wickra.EhlersStochastic(20),
|
||||
EmpiricalModeDecomposition: () => new wickra.EmpiricalModeDecomposition(20, 0.5),
|
||||
HilbertDominantCycle: () => new wickra.HilbertDominantCycle(),
|
||||
HT_DCPHASE: () => new wickra.HT_DCPHASE(),
|
||||
HT_TRENDMODE: () => new wickra.HT_TRENDMODE(),
|
||||
AdaptiveCycle: () => new wickra.AdaptiveCycle(),
|
||||
SineWave: () => new wickra.SineWave(),
|
||||
FAMA: () => new wickra.FAMA(0.5, 0.05),
|
||||
@@ -159,10 +167,17 @@ for (const [name, make] of Object.entries(scalarFactories)) {
|
||||
// --- Scalar-output candle indicators: update(...) vs batch(...) ---
|
||||
|
||||
const candleScalar = {
|
||||
MIDPRICE: { make: () => new wickra.MIDPRICE(14), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
|
||||
DX: { make: () => new wickra.DX(14), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
|
||||
MINUS_DI: { make: () => new wickra.MINUS_DI(14), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
|
||||
PLUS_DI: { make: () => new wickra.PLUS_DI(14), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
|
||||
ATR: { make: () => new wickra.ATR(14), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
|
||||
PLUS_DM: { make: () => new wickra.PLUS_DM(14), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
|
||||
MINUS_DM: { make: () => new wickra.MINUS_DM(14), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
|
||||
CCI: { make: () => new wickra.CCI(20), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
|
||||
WilliamsR: { make: () => new wickra.WilliamsR(14), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
|
||||
PSAR: { make: () => new wickra.PSAR(0.02, 0.02, 0.2), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
|
||||
SAREXT: { make: () => new wickra.SAREXT(0, 0, 0.02, 0.02, 0.2, 0.02, 0.02, 0.2), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
|
||||
MFI: { make: () => new wickra.MFI(14), step: (ind, i) => ind.update(high[i], low[i], close[i], volume[i]), batch: (ind) => ind.batch(high, low, close, volume) },
|
||||
VWAP: { make: () => new wickra.VWAP(), step: (ind, i) => ind.update(high[i], low[i], close[i], volume[i]), batch: (ind) => ind.batch(high, low, close, volume) },
|
||||
RollingVWAP: { make: () => new wickra.RollingVWAP(20), step: (ind, i) => ind.update(high[i], low[i], close[i], volume[i]), batch: (ind) => ind.batch(high, low, close, volume) },
|
||||
@@ -170,6 +185,7 @@ const candleScalar = {
|
||||
OBV: { make: () => new wickra.OBV(), step: (ind, i) => ind.update(close[i], volume[i]), batch: (ind) => ind.batch(close, volume) },
|
||||
VWMA: { make: () => new wickra.VWMA(20), step: (ind, i) => ind.update(close[i], volume[i]), batch: (ind) => ind.batch(close, volume) },
|
||||
RVI: { make: () => new wickra.RVI(10), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
|
||||
AVGPRICE: { make: () => new wickra.AVGPRICE(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
|
||||
Inertia: { make: () => new wickra.Inertia(14, 20), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
|
||||
PGO: { make: () => new wickra.PGO(14), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
|
||||
SMI: { make: () => new wickra.SMI(5, 3, 3), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
|
||||
@@ -302,6 +318,9 @@ const multi = {
|
||||
Alligator: { make: () => new wickra.Alligator(13, 8, 5), fields: ['jaw', 'teeth', 'lips'], step: (ind, i) => ind.update(high[i], low[i]), batch: (ind) => ind.batch(high, low) },
|
||||
ZeroLagMACD: { make: () => new wickra.ZeroLagMACD(12, 26, 9), fields: ['macd', 'signal', 'histogram'], step: (ind, i) => ind.update(close[i]), batch: (ind) => ind.batch(close) },
|
||||
MACD: { make: () => new wickra.MACD(12, 26, 9), fields: ['macd', 'signal', 'histogram'], step: (ind, i) => ind.update(close[i]), batch: (ind) => ind.batch(close) },
|
||||
HT_PHASOR: { make: () => new wickra.HT_PHASOR(), fields: ['inphase', 'quadrature'], step: (ind, i) => ind.update(close[i]), batch: (ind) => ind.batch(close) },
|
||||
MACDFIX: { make: () => new wickra.MACDFIX(9), fields: ['macd', 'signal', 'histogram'], step: (ind, i) => ind.update(close[i]), batch: (ind) => ind.batch(close) },
|
||||
MACDEXT: { make: () => new wickra.MACDEXT(12, 0, 26, 0, 9, 0), fields: ['macd', 'signal', 'histogram'], step: (ind, i) => ind.update(close[i]), batch: (ind) => ind.batch(close) },
|
||||
KST: { make: () => wickra.KST.classic(), fields: ['kst', 'signal'], step: (ind, i) => ind.update(close[i]), batch: (ind) => ind.batch(close) },
|
||||
BollingerBands: { make: () => new wickra.BollingerBands(20, 2), fields: ['upper', 'middle', 'lower', 'stddev'], step: (ind, i) => ind.update(close[i]), batch: (ind) => ind.batch(close) },
|
||||
Stochastic: { make: () => new wickra.Stochastic(14, 3), fields: ['k', 'd'], step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
|
||||
|
||||
Vendored
+191
@@ -45,6 +45,10 @@ export interface BollingerValue {
|
||||
lower: number
|
||||
stddev: number
|
||||
}
|
||||
export interface HtPhasorValue {
|
||||
inphase: number
|
||||
quadrature: number
|
||||
}
|
||||
export interface StochValue {
|
||||
k: number
|
||||
d: number
|
||||
@@ -658,6 +662,60 @@ export declare class MedianAbsoluteDeviation {
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type MidPointNode = MIDPOINT
|
||||
export declare class MIDPOINT {
|
||||
constructor(period: number)
|
||||
update(value: number): number | null
|
||||
batch(prices: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type RocpNode = ROCP
|
||||
export declare class ROCP {
|
||||
constructor(period: number)
|
||||
update(value: number): number | null
|
||||
batch(prices: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type RocrNode = ROCR
|
||||
export declare class ROCR {
|
||||
constructor(period: number)
|
||||
update(value: number): number | null
|
||||
batch(prices: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type Rocr100Node = ROCR100
|
||||
export declare class ROCR100 {
|
||||
constructor(period: number)
|
||||
update(value: number): number | null
|
||||
batch(prices: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type LinRegInterceptNode = LINEARREG_INTERCEPT
|
||||
export declare class LINEARREG_INTERCEPT {
|
||||
constructor(period: number)
|
||||
update(value: number): number | null
|
||||
batch(prices: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type TsfNode = TSF
|
||||
export declare class TSF {
|
||||
constructor(period: number)
|
||||
update(value: number): number | null
|
||||
batch(prices: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type AutocorrelationNode = Autocorrelation
|
||||
export declare class Autocorrelation {
|
||||
constructor(period: number, lag: number)
|
||||
@@ -801,6 +859,36 @@ export declare class MACD {
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type MacdFixNode = MACDFIX
|
||||
export declare class MACDFIX {
|
||||
constructor(signal: number)
|
||||
update(value: number): MacdValue | null
|
||||
/**
|
||||
* Batch over a price array. Returns a flat array of length `3 * n`,
|
||||
* interleaved per row as `[macd0, signal0, histogram0, macd1, ...]`.
|
||||
*/
|
||||
batch(prices: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type MacdExtNode = MACDEXT
|
||||
export declare class MACDEXT {
|
||||
/**
|
||||
* Moving-average types are TA-Lib `MA_Type` codes `0..=5`
|
||||
* (SMA, EMA, WMA, DEMA, TEMA, TRIMA).
|
||||
*/
|
||||
constructor(fast: number, fastMatype: number, slow: number, slowMatype: number, signal: number, signalMatype: number)
|
||||
update(value: number): MacdValue | null
|
||||
/**
|
||||
* Batch over a price array. Returns a flat array of length `3 * n`,
|
||||
* interleaved per row as `[macd0, signal0, histogram0, macd1, ...]`.
|
||||
*/
|
||||
batch(prices: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type BollingerNode = BollingerBands
|
||||
export declare class BollingerBands {
|
||||
constructor(period: number, multiplier: number)
|
||||
@@ -824,6 +912,91 @@ export declare class ATR {
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type PlusDmNode = PLUS_DM
|
||||
export declare class PLUS_DM {
|
||||
constructor(period: number)
|
||||
update(high: number, low: number, close: number): number | null
|
||||
batch(high: Array<number>, low: Array<number>, close: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type MinusDmNode = MINUS_DM
|
||||
export declare class MINUS_DM {
|
||||
constructor(period: number)
|
||||
update(high: number, low: number, close: number): number | null
|
||||
batch(high: Array<number>, low: Array<number>, close: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type PlusDiNode = PLUS_DI
|
||||
export declare class PLUS_DI {
|
||||
constructor(period: number)
|
||||
update(high: number, low: number, close: number): number | null
|
||||
batch(high: Array<number>, low: Array<number>, close: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type MinusDiNode = MINUS_DI
|
||||
export declare class MINUS_DI {
|
||||
constructor(period: number)
|
||||
update(high: number, low: number, close: number): number | null
|
||||
batch(high: Array<number>, low: Array<number>, close: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type DxNode = DX
|
||||
export declare class DX {
|
||||
constructor(period: number)
|
||||
update(high: number, low: number, close: number): number | null
|
||||
batch(high: Array<number>, low: Array<number>, close: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type MidPriceNode = MIDPRICE
|
||||
export declare class MIDPRICE {
|
||||
constructor(period: number)
|
||||
update(high: number, low: number, close: number): number | null
|
||||
batch(high: Array<number>, low: Array<number>, close: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type AvgPriceNode = AVGPRICE
|
||||
export declare class AVGPRICE {
|
||||
constructor()
|
||||
update(open: number, high: number, low: number, close: number): number | null
|
||||
batch(open: Array<number>, high: Array<number>, low: Array<number>, close: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type SarExtNode = SAREXT
|
||||
export declare class SAREXT {
|
||||
constructor(startValue: number, offsetOnReverse: number, accelInitLong: number, accelLong: number, accelMaxLong: number, accelInitShort: number, accelShort: number, accelMaxShort: number)
|
||||
update(high: number, low: number, close: number): number | null
|
||||
batch(high: Array<number>, low: Array<number>, close: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type HtPhasorNode = HT_PHASOR
|
||||
export declare class HT_PHASOR {
|
||||
constructor()
|
||||
update(value: number): HtPhasorValue | null
|
||||
/**
|
||||
* Batch over a price array. Returns a flat array of length `2 * n`,
|
||||
* interleaved per row as `[inphase0, quadrature0, inphase1, ...]`.
|
||||
*/
|
||||
batch(prices: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type StochNode = Stochastic
|
||||
export declare class Stochastic {
|
||||
constructor(kPeriod: number, dPeriod: number)
|
||||
@@ -2010,6 +2183,24 @@ export declare class EmpiricalModeDecomposition {
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type HtDcPhaseNode = HT_DCPHASE
|
||||
export declare class HT_DCPHASE {
|
||||
constructor()
|
||||
update(value: number): number | null
|
||||
batch(prices: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type HtTrendModeNode = HT_TRENDMODE
|
||||
export declare class HT_TRENDMODE {
|
||||
constructor()
|
||||
update(value: number): number | null
|
||||
batch(prices: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type HilbertDominantCycleNode = HilbertDominantCycle
|
||||
export declare class HilbertDominantCycle {
|
||||
constructor()
|
||||
|
||||
+20
-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, Autocorrelation, HurstExponent, PearsonCorrelation, Beta, PairwiseBeta, SpearmanCorrelation, PairSpreadZScore, LeadLagCrossCorrelation, Cointegration, RelativeStrengthAB, MACD, BollingerBands, ATR, 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, 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, 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, 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, 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
|
||||
@@ -350,6 +350,12 @@ module.exports.StandardError = StandardError
|
||||
module.exports.DetrendedStdDev = DetrendedStdDev
|
||||
module.exports.RSquared = RSquared
|
||||
module.exports.MedianAbsoluteDeviation = MedianAbsoluteDeviation
|
||||
module.exports.MIDPOINT = MIDPOINT
|
||||
module.exports.ROCP = ROCP
|
||||
module.exports.ROCR = ROCR
|
||||
module.exports.ROCR100 = ROCR100
|
||||
module.exports.LINEARREG_INTERCEPT = LINEARREG_INTERCEPT
|
||||
module.exports.TSF = TSF
|
||||
module.exports.Autocorrelation = Autocorrelation
|
||||
module.exports.HurstExponent = HurstExponent
|
||||
module.exports.PearsonCorrelation = PearsonCorrelation
|
||||
@@ -361,8 +367,19 @@ module.exports.LeadLagCrossCorrelation = LeadLagCrossCorrelation
|
||||
module.exports.Cointegration = Cointegration
|
||||
module.exports.RelativeStrengthAB = RelativeStrengthAB
|
||||
module.exports.MACD = MACD
|
||||
module.exports.MACDFIX = MACDFIX
|
||||
module.exports.MACDEXT = MACDEXT
|
||||
module.exports.BollingerBands = BollingerBands
|
||||
module.exports.ATR = ATR
|
||||
module.exports.PLUS_DM = PLUS_DM
|
||||
module.exports.MINUS_DM = MINUS_DM
|
||||
module.exports.PLUS_DI = PLUS_DI
|
||||
module.exports.MINUS_DI = MINUS_DI
|
||||
module.exports.DX = DX
|
||||
module.exports.MIDPRICE = MIDPRICE
|
||||
module.exports.AVGPRICE = AVGPRICE
|
||||
module.exports.SAREXT = SAREXT
|
||||
module.exports.HT_PHASOR = HT_PHASOR
|
||||
module.exports.Stochastic = Stochastic
|
||||
module.exports.OBV = OBV
|
||||
module.exports.ADX = ADX
|
||||
@@ -491,6 +508,8 @@ module.exports.InverseFisherTransform = InverseFisherTransform
|
||||
module.exports.DecyclerOscillator = DecyclerOscillator
|
||||
module.exports.RoofingFilter = RoofingFilter
|
||||
module.exports.EmpiricalModeDecomposition = EmpiricalModeDecomposition
|
||||
module.exports.HT_DCPHASE = HT_DCPHASE
|
||||
module.exports.HT_TRENDMODE = HT_TRENDMODE
|
||||
module.exports.HilbertDominantCycle = HilbertDominantCycle
|
||||
module.exports.AdaptiveCycle = AdaptiveCycle
|
||||
module.exports.SineWave = SineWave
|
||||
|
||||
@@ -172,6 +172,16 @@ node_scalar_indicator!(
|
||||
"MedianAbsoluteDeviation",
|
||||
wc::MedianAbsoluteDeviation
|
||||
);
|
||||
node_scalar_indicator!(MidPointNode, "MIDPOINT", wc::MidPoint);
|
||||
node_scalar_indicator!(RocpNode, "ROCP", wc::Rocp);
|
||||
node_scalar_indicator!(RocrNode, "ROCR", wc::Rocr);
|
||||
node_scalar_indicator!(Rocr100Node, "ROCR100", wc::Rocr100);
|
||||
node_scalar_indicator!(
|
||||
LinRegInterceptNode,
|
||||
"LINEARREG_INTERCEPT",
|
||||
wc::LinRegIntercept
|
||||
);
|
||||
node_scalar_indicator!(TsfNode, "TSF", wc::Tsf);
|
||||
|
||||
// ============================== Autocorrelation (period + lag) ==============================
|
||||
|
||||
@@ -632,6 +642,121 @@ impl MacdNode {
|
||||
}
|
||||
}
|
||||
|
||||
#[napi(js_name = "MACDFIX")]
|
||||
pub struct MacdFixNode {
|
||||
inner: wc::MacdFix,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl MacdFixNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(signal: u32) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::MacdFix::new(signal as usize).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, value: f64) -> Option<MacdValue> {
|
||||
self.inner.update(value).map(|o| MacdValue {
|
||||
macd: o.macd,
|
||||
signal: o.signal,
|
||||
histogram: o.histogram,
|
||||
})
|
||||
}
|
||||
/// Batch over a price array. Returns a flat array of length `3 * n`,
|
||||
/// interleaved per row as `[macd0, signal0, histogram0, macd1, ...]`.
|
||||
#[napi]
|
||||
pub fn batch(&mut self, prices: Vec<f64>) -> Vec<f64> {
|
||||
let mut out = vec![f64::NAN; prices.len() * 3];
|
||||
for (i, p) in prices.iter().enumerate() {
|
||||
if let Some(o) = self.inner.update(*p) {
|
||||
out[i * 3] = o.macd;
|
||||
out[i * 3 + 1] = o.signal;
|
||||
out[i * 3 + 2] = o.histogram;
|
||||
}
|
||||
}
|
||||
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 = "MACDEXT")]
|
||||
pub struct MacdExtNode {
|
||||
inner: wc::MacdExt,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl MacdExtNode {
|
||||
/// Moving-average types are TA-Lib `MA_Type` codes `0..=5`
|
||||
/// (SMA, EMA, WMA, DEMA, TEMA, TRIMA).
|
||||
#[napi(constructor)]
|
||||
pub fn new(
|
||||
fast: u32,
|
||||
fast_matype: u32,
|
||||
slow: u32,
|
||||
slow_matype: u32,
|
||||
signal: u32,
|
||||
signal_matype: u32,
|
||||
) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::MacdExt::new(
|
||||
fast as usize,
|
||||
wc::MaType::from_code(fast_matype).map_err(map_err)?,
|
||||
slow as usize,
|
||||
wc::MaType::from_code(slow_matype).map_err(map_err)?,
|
||||
signal as usize,
|
||||
wc::MaType::from_code(signal_matype).map_err(map_err)?,
|
||||
)
|
||||
.map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, value: f64) -> Option<MacdValue> {
|
||||
self.inner.update(value).map(|o| MacdValue {
|
||||
macd: o.macd,
|
||||
signal: o.signal,
|
||||
histogram: o.histogram,
|
||||
})
|
||||
}
|
||||
/// Batch over a price array. Returns a flat array of length `3 * n`,
|
||||
/// interleaved per row as `[macd0, signal0, histogram0, macd1, ...]`.
|
||||
#[napi]
|
||||
pub fn batch(&mut self, prices: Vec<f64>) -> Vec<f64> {
|
||||
let mut out = vec![f64::NAN; prices.len() * 3];
|
||||
for (i, p) in prices.iter().enumerate() {
|
||||
if let Some(o) = self.inner.update(*p) {
|
||||
out[i * 3] = o.macd;
|
||||
out[i * 3 + 1] = o.signal;
|
||||
out[i * 3 + 2] = o.histogram;
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== Bollinger ==============================
|
||||
|
||||
#[napi(object)]
|
||||
@@ -753,6 +878,522 @@ impl AtrNode {
|
||||
}
|
||||
}
|
||||
|
||||
#[napi(js_name = "PLUS_DM")]
|
||||
pub struct PlusDmNode {
|
||||
inner: wc::PlusDm,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl PlusDmNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(period: u32) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::PlusDm::new(period as usize).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, high: f64, low: f64, close: f64) -> napi::Result<Option<f64>> {
|
||||
Ok(self.inner.update(cnd(high, low, close, 0.0)?))
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
high: Vec<f64>,
|
||||
low: Vec<f64>,
|
||||
close: Vec<f64>,
|
||||
) -> napi::Result<Vec<f64>> {
|
||||
if high.len() != low.len() || low.len() != close.len() {
|
||||
return Err(NapiError::from_reason(
|
||||
"high, low, close must be equal length".to_string(),
|
||||
));
|
||||
}
|
||||
let mut out = Vec::with_capacity(high.len());
|
||||
for i in 0..high.len() {
|
||||
out.push(
|
||||
self.inner
|
||||
.update(cnd(high[i], low[i], close[i], 0.0)?)
|
||||
.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 = "MINUS_DM")]
|
||||
pub struct MinusDmNode {
|
||||
inner: wc::MinusDm,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl MinusDmNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(period: u32) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::MinusDm::new(period as usize).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, high: f64, low: f64, close: f64) -> napi::Result<Option<f64>> {
|
||||
Ok(self.inner.update(cnd(high, low, close, 0.0)?))
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
high: Vec<f64>,
|
||||
low: Vec<f64>,
|
||||
close: Vec<f64>,
|
||||
) -> napi::Result<Vec<f64>> {
|
||||
if high.len() != low.len() || low.len() != close.len() {
|
||||
return Err(NapiError::from_reason(
|
||||
"high, low, close must be equal length".to_string(),
|
||||
));
|
||||
}
|
||||
let mut out = Vec::with_capacity(high.len());
|
||||
for i in 0..high.len() {
|
||||
out.push(
|
||||
self.inner
|
||||
.update(cnd(high[i], low[i], close[i], 0.0)?)
|
||||
.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 = "PLUS_DI")]
|
||||
pub struct PlusDiNode {
|
||||
inner: wc::PlusDi,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl PlusDiNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(period: u32) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::PlusDi::new(period as usize).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, high: f64, low: f64, close: f64) -> napi::Result<Option<f64>> {
|
||||
Ok(self.inner.update(cnd(high, low, close, 0.0)?))
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
high: Vec<f64>,
|
||||
low: Vec<f64>,
|
||||
close: Vec<f64>,
|
||||
) -> napi::Result<Vec<f64>> {
|
||||
if high.len() != low.len() || low.len() != close.len() {
|
||||
return Err(NapiError::from_reason(
|
||||
"high, low, close must be equal length".to_string(),
|
||||
));
|
||||
}
|
||||
let mut out = Vec::with_capacity(high.len());
|
||||
for i in 0..high.len() {
|
||||
out.push(
|
||||
self.inner
|
||||
.update(cnd(high[i], low[i], close[i], 0.0)?)
|
||||
.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 = "MINUS_DI")]
|
||||
pub struct MinusDiNode {
|
||||
inner: wc::MinusDi,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl MinusDiNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(period: u32) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::MinusDi::new(period as usize).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, high: f64, low: f64, close: f64) -> napi::Result<Option<f64>> {
|
||||
Ok(self.inner.update(cnd(high, low, close, 0.0)?))
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
high: Vec<f64>,
|
||||
low: Vec<f64>,
|
||||
close: Vec<f64>,
|
||||
) -> napi::Result<Vec<f64>> {
|
||||
if high.len() != low.len() || low.len() != close.len() {
|
||||
return Err(NapiError::from_reason(
|
||||
"high, low, close must be equal length".to_string(),
|
||||
));
|
||||
}
|
||||
let mut out = Vec::with_capacity(high.len());
|
||||
for i in 0..high.len() {
|
||||
out.push(
|
||||
self.inner
|
||||
.update(cnd(high[i], low[i], close[i], 0.0)?)
|
||||
.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 = "DX")]
|
||||
pub struct DxNode {
|
||||
inner: wc::Dx,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl DxNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(period: u32) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::Dx::new(period as usize).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, high: f64, low: f64, close: f64) -> napi::Result<Option<f64>> {
|
||||
Ok(self.inner.update(cnd(high, low, close, 0.0)?))
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
high: Vec<f64>,
|
||||
low: Vec<f64>,
|
||||
close: Vec<f64>,
|
||||
) -> napi::Result<Vec<f64>> {
|
||||
if high.len() != low.len() || low.len() != close.len() {
|
||||
return Err(NapiError::from_reason(
|
||||
"high, low, close must be equal length".to_string(),
|
||||
));
|
||||
}
|
||||
let mut out = Vec::with_capacity(high.len());
|
||||
for i in 0..high.len() {
|
||||
out.push(
|
||||
self.inner
|
||||
.update(cnd(high[i], low[i], close[i], 0.0)?)
|
||||
.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 = "MIDPRICE")]
|
||||
pub struct MidPriceNode {
|
||||
inner: wc::MidPrice,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl MidPriceNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(period: u32) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::MidPrice::new(period as usize).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, high: f64, low: f64, close: f64) -> napi::Result<Option<f64>> {
|
||||
Ok(self.inner.update(cnd(high, low, close, 0.0)?))
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
high: Vec<f64>,
|
||||
low: Vec<f64>,
|
||||
close: Vec<f64>,
|
||||
) -> napi::Result<Vec<f64>> {
|
||||
if high.len() != low.len() || low.len() != close.len() {
|
||||
return Err(NapiError::from_reason(
|
||||
"high, low, close must be equal length".to_string(),
|
||||
));
|
||||
}
|
||||
let mut out = Vec::with_capacity(high.len());
|
||||
for i in 0..high.len() {
|
||||
out.push(
|
||||
self.inner
|
||||
.update(cnd(high[i], low[i], close[i], 0.0)?)
|
||||
.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 = "AVGPRICE")]
|
||||
pub struct AvgPriceNode {
|
||||
inner: wc::AvgPrice,
|
||||
}
|
||||
|
||||
impl Default for AvgPriceNode {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl AvgPriceNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: wc::AvgPrice::new(),
|
||||
}
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(
|
||||
&mut self,
|
||||
open: f64,
|
||||
high: f64,
|
||||
low: f64,
|
||||
close: f64,
|
||||
) -> napi::Result<Option<f64>> {
|
||||
Ok(self.inner.update(cnd4(open, high, low, close)?))
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
open: Vec<f64>,
|
||||
high: Vec<f64>,
|
||||
low: Vec<f64>,
|
||||
close: Vec<f64>,
|
||||
) -> napi::Result<Vec<f64>> {
|
||||
if !(open.len() == high.len() && high.len() == low.len() && low.len() == close.len()) {
|
||||
return Err(NapiError::from_reason(
|
||||
"open, high, low and close must be equal length".to_string(),
|
||||
));
|
||||
}
|
||||
let mut out = Vec::with_capacity(close.len());
|
||||
for i in 0..close.len() {
|
||||
out.push(
|
||||
self.inner
|
||||
.update(cnd4(open[i], high[i], low[i], close[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
|
||||
}
|
||||
}
|
||||
|
||||
#[napi(js_name = "SAREXT")]
|
||||
pub struct SarExtNode {
|
||||
inner: wc::SarExt,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl SarExtNode {
|
||||
#[napi(constructor)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
start_value: f64,
|
||||
offset_on_reverse: f64,
|
||||
accel_init_long: f64,
|
||||
accel_long: f64,
|
||||
accel_max_long: f64,
|
||||
accel_init_short: f64,
|
||||
accel_short: f64,
|
||||
accel_max_short: f64,
|
||||
) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::SarExt::new(
|
||||
start_value,
|
||||
offset_on_reverse,
|
||||
accel_init_long,
|
||||
accel_long,
|
||||
accel_max_long,
|
||||
accel_init_short,
|
||||
accel_short,
|
||||
accel_max_short,
|
||||
)
|
||||
.map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, high: f64, low: f64, close: f64) -> napi::Result<Option<f64>> {
|
||||
Ok(self.inner.update(cnd(high, low, close, 0.0)?))
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
high: Vec<f64>,
|
||||
low: Vec<f64>,
|
||||
close: Vec<f64>,
|
||||
) -> napi::Result<Vec<f64>> {
|
||||
if high.len() != low.len() || low.len() != close.len() {
|
||||
return Err(NapiError::from_reason(
|
||||
"high, low, close must be equal length".to_string(),
|
||||
));
|
||||
}
|
||||
let mut out = Vec::with_capacity(high.len());
|
||||
for i in 0..high.len() {
|
||||
out.push(
|
||||
self.inner
|
||||
.update(cnd(high[i], low[i], close[i], 0.0)?)
|
||||
.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(object)]
|
||||
pub struct HtPhasorValue {
|
||||
pub inphase: f64,
|
||||
pub quadrature: f64,
|
||||
}
|
||||
|
||||
#[napi(js_name = "HT_PHASOR")]
|
||||
pub struct HtPhasorNode {
|
||||
inner: wc::HtPhasor,
|
||||
}
|
||||
|
||||
impl Default for HtPhasorNode {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl HtPhasorNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: wc::HtPhasor::new(),
|
||||
}
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, value: f64) -> Option<HtPhasorValue> {
|
||||
self.inner.update(value).map(|o| HtPhasorValue {
|
||||
inphase: o.inphase,
|
||||
quadrature: o.quadrature,
|
||||
})
|
||||
}
|
||||
/// Batch over a price array. Returns a flat array of length `2 * n`,
|
||||
/// interleaved per row as `[inphase0, quadrature0, inphase1, ...]`.
|
||||
#[napi]
|
||||
pub fn batch(&mut self, prices: Vec<f64>) -> Vec<f64> {
|
||||
let mut out = vec![f64::NAN; prices.len() * 2];
|
||||
for (i, p) in prices.iter().enumerate() {
|
||||
if let Some(o) = self.inner.update(*p) {
|
||||
out[i * 2] = o.inphase;
|
||||
out[i * 2 + 1] = o.quadrature;
|
||||
}
|
||||
}
|
||||
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(object)]
|
||||
pub struct StochValue {
|
||||
pub k: f64,
|
||||
@@ -7991,6 +8632,88 @@ impl EmpiricalModeDecompositionNode {
|
||||
}
|
||||
}
|
||||
|
||||
#[napi(js_name = "HT_DCPHASE")]
|
||||
pub struct HtDcPhaseNode {
|
||||
inner: wc::HtDcPhase,
|
||||
}
|
||||
|
||||
impl Default for HtDcPhaseNode {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl HtDcPhaseNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: wc::HtDcPhase::new(),
|
||||
}
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, value: f64) -> Option<f64> {
|
||||
self.inner.update(value)
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(&mut self, prices: Vec<f64>) -> Vec<f64> {
|
||||
flatten(self.inner.batch(&prices))
|
||||
}
|
||||
#[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 = "HT_TRENDMODE")]
|
||||
pub struct HtTrendModeNode {
|
||||
inner: wc::HtTrendMode,
|
||||
}
|
||||
|
||||
impl Default for HtTrendModeNode {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl HtTrendModeNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: wc::HtTrendMode::new(),
|
||||
}
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, value: f64) -> Option<f64> {
|
||||
self.inner.update(value)
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(&mut self, prices: Vec<f64>) -> Vec<f64> {
|
||||
flatten(self.inner.batch(&prices))
|
||||
}
|
||||
#[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 = "HilbertDominantCycle")]
|
||||
pub struct HilbertDominantCycleNode {
|
||||
inner: wc::HilbertDominantCycle,
|
||||
|
||||
@@ -25,6 +25,17 @@ from __future__ import annotations
|
||||
|
||||
from ._wickra import (
|
||||
__version__,
|
||||
TSF,
|
||||
LINEARREG_INTERCEPT,
|
||||
ROCR100,
|
||||
ROCR,
|
||||
ROCP,
|
||||
AVGPRICE,
|
||||
MIDPOINT,
|
||||
MIDPRICE,
|
||||
DX,
|
||||
MINUS_DI,
|
||||
PLUS_DI,
|
||||
# Trend
|
||||
SMA,
|
||||
EMA,
|
||||
@@ -49,12 +60,16 @@ from ._wickra import (
|
||||
RSI,
|
||||
AnchoredRSI,
|
||||
MACD,
|
||||
MACDFIX,
|
||||
MACDEXT,
|
||||
Stochastic,
|
||||
CCI,
|
||||
ROC,
|
||||
WilliamsR,
|
||||
ADX,
|
||||
ADXR,
|
||||
PLUS_DM,
|
||||
MINUS_DM,
|
||||
MFI,
|
||||
TRIX,
|
||||
AwesomeOscillator,
|
||||
@@ -98,6 +113,7 @@ from ._wickra import (
|
||||
Keltner,
|
||||
Donchian,
|
||||
PSAR,
|
||||
SAREXT,
|
||||
NATR,
|
||||
StdDev,
|
||||
UlcerIndex,
|
||||
@@ -181,6 +197,9 @@ from ._wickra import (
|
||||
EhlersStochastic,
|
||||
EmpiricalModeDecomposition,
|
||||
HilbertDominantCycle,
|
||||
HT_DCPHASE,
|
||||
HT_PHASOR,
|
||||
HT_TRENDMODE,
|
||||
AdaptiveCycle,
|
||||
SineWave,
|
||||
MAMA,
|
||||
@@ -343,6 +362,17 @@ from ._wickra import (
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"TSF",
|
||||
"LINEARREG_INTERCEPT",
|
||||
"ROCR100",
|
||||
"ROCR",
|
||||
"ROCP",
|
||||
"AVGPRICE",
|
||||
"MIDPOINT",
|
||||
"MIDPRICE",
|
||||
"DX",
|
||||
"MINUS_DI",
|
||||
"PLUS_DI",
|
||||
"__version__",
|
||||
# Trend
|
||||
"SMA",
|
||||
@@ -368,12 +398,16 @@ __all__ = [
|
||||
"RSI",
|
||||
"AnchoredRSI",
|
||||
"MACD",
|
||||
"MACDFIX",
|
||||
"MACDEXT",
|
||||
"Stochastic",
|
||||
"CCI",
|
||||
"ROC",
|
||||
"WilliamsR",
|
||||
"ADX",
|
||||
"ADXR",
|
||||
"PLUS_DM",
|
||||
"MINUS_DM",
|
||||
"MFI",
|
||||
"TRIX",
|
||||
"AwesomeOscillator",
|
||||
@@ -417,6 +451,7 @@ __all__ = [
|
||||
"Keltner",
|
||||
"Donchian",
|
||||
"PSAR",
|
||||
"SAREXT",
|
||||
"NATR",
|
||||
"StdDev",
|
||||
"UlcerIndex",
|
||||
@@ -500,6 +535,9 @@ __all__ = [
|
||||
"EhlersStochastic",
|
||||
"EmpiricalModeDecomposition",
|
||||
"HilbertDominantCycle",
|
||||
"HT_DCPHASE",
|
||||
"HT_PHASOR",
|
||||
"HT_TRENDMODE",
|
||||
"AdaptiveCycle",
|
||||
"SineWave",
|
||||
"MAMA",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -45,6 +45,12 @@ def ohlcv() -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
||||
# --- Scalar (f64 -> f64) indicators ---------------------------------------
|
||||
|
||||
SCALAR = [
|
||||
(ta.TSF, (14,)),
|
||||
(ta.LINEARREG_INTERCEPT, (14,)),
|
||||
(ta.ROCR100, (10,)),
|
||||
(ta.ROCR, (10,)),
|
||||
(ta.ROCP, (10,)),
|
||||
(ta.MIDPOINT, (14,)),
|
||||
(ta.SMMA, (14,)),
|
||||
(ta.TRIMA, (20,)),
|
||||
(ta.ZLEMA, (14,)),
|
||||
@@ -96,6 +102,8 @@ SCALAR = [
|
||||
(ta.EhlersStochastic, (20,)),
|
||||
(ta.EmpiricalModeDecomposition, (20, 0.5)),
|
||||
(ta.HilbertDominantCycle, ()),
|
||||
(ta.HT_DCPHASE, ()),
|
||||
(ta.HT_TRENDMODE, ()),
|
||||
(ta.AdaptiveCycle, ()),
|
||||
(ta.SineWave, ()),
|
||||
(ta.FAMA, (0.5, 0.05)),
|
||||
@@ -136,6 +144,9 @@ SCALAR_MULTI = {
|
||||
"LinRegChannel": (lambda: ta.LinRegChannel(20, 2.0), 3),
|
||||
"StandardErrorBands": (lambda: ta.StandardErrorBands(21, 2.0), 3),
|
||||
"DoubleBollinger": (lambda: ta.DoubleBollinger(20, 1.0, 2.0), 5),
|
||||
"MacdFix": (lambda: ta.MACDFIX(9), 3),
|
||||
"MacdExt": (lambda: ta.MACDEXT(12, 0, 26, 0, 9, 0), 3),
|
||||
"HtPhasor": (lambda: ta.HT_PHASOR(), 2),
|
||||
}
|
||||
|
||||
|
||||
@@ -275,7 +286,15 @@ def test_relative_strength_streaming_matches_batch():
|
||||
# 6-tuple candle; the batch helper takes only the columns it needs.
|
||||
|
||||
CANDLE_SCALAR = {
|
||||
"MIDPRICE": (lambda: ta.MIDPRICE(14), lambda ind, h, l, c, v: ind.batch(h, l, c)),
|
||||
"AVGPRICE": (lambda: ta.AVGPRICE(), lambda ind, h, l, c, v: ind.batch(c, h, l, c)),
|
||||
"DX": (lambda: ta.DX(14), lambda ind, h, l, c, v: ind.batch(h, l, c)),
|
||||
"MINUS_DI": (lambda: ta.MINUS_DI(14), lambda ind, h, l, c, v: ind.batch(h, l, c)),
|
||||
"PLUS_DI": (lambda: ta.PLUS_DI(14), lambda ind, h, l, c, v: ind.batch(h, l, c)),
|
||||
"VWMA": (lambda: ta.VWMA(20), lambda ind, h, l, c, v: ind.batch(c, v)),
|
||||
"SAREXT": (lambda: ta.SAREXT(), lambda ind, h, l, c, v: ind.batch(h, l, c)),
|
||||
"PLUS_DM": (lambda: ta.PLUS_DM(14), lambda ind, h, l, c, v: ind.batch(h, l, c)),
|
||||
"MINUS_DM": (lambda: ta.MINUS_DM(14), lambda ind, h, l, c, v: ind.batch(h, l, c)),
|
||||
"RVI": (
|
||||
# extract_candle pulls the open price from index 0 of the tuple; the
|
||||
# streaming test below already builds candles with open == close, so
|
||||
@@ -1171,6 +1190,99 @@ def test_weighted_close_reference():
|
||||
)
|
||||
|
||||
|
||||
def test_plus_dm_reference():
|
||||
# Highs rise by 1 (up = +1) while lows rise by 0.5, so every raw +DM equals
|
||||
# the up-move (1.0). Period 3: seed = 3 * 1 = 3.0, then the Wilder step holds it.
|
||||
high = np.array([11.0, 12.0, 13.0, 14.0, 15.0])
|
||||
low = np.array([9.0, 9.5, 10.0, 10.5, 11.0])
|
||||
close = np.array([10.0, 11.0, 12.0, 13.0, 14.0])
|
||||
out = ta.PLUS_DM(3).batch(high, low, close)
|
||||
assert math.isnan(out[0]) and math.isnan(out[2])
|
||||
assert out[3] == pytest.approx(3.0)
|
||||
assert out[4] == pytest.approx(3.0)
|
||||
|
||||
|
||||
def test_minus_dm_reference():
|
||||
# Lows fall by 1 (down = +1) while highs fall by 0.5, so every raw -DM equals
|
||||
# the down-move (1.0). Period 3: seed = 3 * 1 = 3.0, then the Wilder step holds it.
|
||||
high = np.array([20.0, 19.5, 19.0, 18.5, 18.0])
|
||||
low = np.array([18.0, 17.0, 16.0, 15.0, 14.0])
|
||||
close = np.array([19.0, 18.0, 17.0, 16.0, 15.0])
|
||||
out = ta.MINUS_DM(3).batch(high, low, close)
|
||||
assert math.isnan(out[0]) and math.isnan(out[2])
|
||||
assert out[3] == pytest.approx(3.0)
|
||||
assert out[4] == pytest.approx(3.0)
|
||||
|
||||
|
||||
def test_plus_di_reference():
|
||||
# Strict uptrend -> +DI dominates and stays within (0, 100].
|
||||
high = np.array([101.0, 103.0, 105.0, 107.0, 109.0, 111.0])
|
||||
low = np.array([99.5, 101.5, 103.5, 105.5, 107.5, 109.5])
|
||||
close = np.array([100.5, 102.5, 104.5, 106.5, 108.5, 110.5])
|
||||
out = ta.PLUS_DI(3).batch(high, low, close)
|
||||
assert 0.0 < out[-1] <= 100.0
|
||||
|
||||
|
||||
def test_minus_di_reference():
|
||||
# Strict downtrend -> -DI dominates and stays within (0, 100].
|
||||
high = np.array([111.0, 109.0, 107.0, 105.0, 103.0, 101.0])
|
||||
low = np.array([109.5, 107.5, 105.5, 103.5, 101.5, 99.5])
|
||||
close = np.array([110.5, 108.5, 106.5, 104.5, 102.5, 100.5])
|
||||
out = ta.MINUS_DI(3).batch(high, low, close)
|
||||
assert 0.0 < out[-1] <= 100.0
|
||||
|
||||
|
||||
def test_dx_reference():
|
||||
# Strict trend -> one-sided directional movement -> DX is large, in (0, 100].
|
||||
high = np.array([101.0, 103.0, 105.0, 107.0, 109.0, 111.0])
|
||||
low = np.array([99.5, 101.5, 103.5, 105.5, 107.5, 109.5])
|
||||
close = np.array([100.5, 102.5, 104.5, 106.5, 108.5, 110.5])
|
||||
out = ta.DX(3).batch(high, low, close)
|
||||
assert 50.0 < out[-1] <= 100.0
|
||||
|
||||
|
||||
def test_mid_price_reference():
|
||||
# Window highs {12, 14, 16}, lows {8, 9, 10}: (16 + 8) / 2 = 12.
|
||||
high = np.array([12.0, 14.0, 16.0])
|
||||
low = np.array([8.0, 9.0, 10.0])
|
||||
close = np.array([10.0, 11.0, 12.0])
|
||||
out = ta.MIDPRICE(3).batch(high, low, close)
|
||||
assert out[-1] == pytest.approx(12.0)
|
||||
|
||||
|
||||
def test_mid_point_reference():
|
||||
# Window {8, 12, 10}: (12 + 8) / 2 = 10.
|
||||
out = ta.MIDPOINT(3).batch(np.array([8.0, 12.0, 10.0]))
|
||||
assert out[-1] == pytest.approx(10.0)
|
||||
|
||||
|
||||
def test_avg_price_reference():
|
||||
# (open + high + low + close) / 4 = (10 + 14 + 6 + 12) / 4 = 10.5.
|
||||
assert ta.AVGPRICE().update((10.0, 14.0, 6.0, 12.0, 1.0, 0)) == pytest.approx(10.5)
|
||||
|
||||
|
||||
def test_roc_ratio_variants_reference():
|
||||
# period 1 over [10, 11]: ROCP = 0.1, ROCR = 1.1, ROCR100 = 110.
|
||||
assert ta.ROCP(1).batch(np.array([10.0, 11.0]))[-1] == pytest.approx(0.1)
|
||||
assert ta.ROCR(1).batch(np.array([10.0, 11.0]))[-1] == pytest.approx(1.1)
|
||||
assert ta.ROCR100(1).batch(np.array([10.0, 11.0]))[-1] == pytest.approx(110.0)
|
||||
|
||||
|
||||
def test_linreg_intercept_and_tsf_reference():
|
||||
# period 3 over [1, 2, 9]: fit y = 0 + 4x. intercept = 0; forecast at x=3 = 12.
|
||||
data = np.array([1.0, 2.0, 9.0])
|
||||
assert ta.LINEARREG_INTERCEPT(3).batch(data)[-1] == pytest.approx(0.0, abs=1e-9)
|
||||
assert ta.TSF(3).batch(data)[-1] == pytest.approx(12.0)
|
||||
|
||||
|
||||
def test_macdfix_matches_macd():
|
||||
# MACDFIX(signal) is exactly MACD(12, 26, signal).
|
||||
prices = 100.0 + np.sin(np.arange(80) * 0.3) * 5.0
|
||||
fix = ta.MACDFIX(9).batch(prices)
|
||||
classic = ta.MACD(12, 26, 9).batch(prices)
|
||||
np.testing.assert_allclose(fix, classic, equal_nan=True)
|
||||
|
||||
|
||||
def test_nvi_reference():
|
||||
# closes [10, 11], volumes [200, 100]: volume contracts -> NVI absorbs +10%.
|
||||
# 1000 * (1 + 0.1) = 1100.
|
||||
|
||||
@@ -1131,6 +1131,494 @@ impl WasmAtr {
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = PLUS_DM)]
|
||||
pub struct WasmPlusDm {
|
||||
inner: wc::PlusDm,
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = PLUS_DM)]
|
||||
impl WasmPlusDm {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(period: usize) -> Result<WasmPlusDm, JsError> {
|
||||
Ok(Self {
|
||||
inner: wc::PlusDm::new(period).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
pub fn update(&mut self, high: f64, low: f64, close: f64) -> Result<Option<f64>, JsError> {
|
||||
let c = make_candle(high, low, close, 0.0)?;
|
||||
Ok(self.inner.update(c))
|
||||
}
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
high: &[f64],
|
||||
low: &[f64],
|
||||
close: &[f64],
|
||||
) -> Result<Float64Array, JsError> {
|
||||
if high.len() != low.len() || low.len() != close.len() {
|
||||
return Err(JsError::new("high, low, close must be equal length"));
|
||||
}
|
||||
let mut out = Vec::with_capacity(high.len());
|
||||
for i in 0..high.len() {
|
||||
let c = make_candle(high[i], low[i], close[i], 0.0)?;
|
||||
out.push(self.inner.update(c).unwrap_or(f64::NAN));
|
||||
}
|
||||
Ok(Float64Array::from(out.as_slice()))
|
||||
}
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = MINUS_DM)]
|
||||
pub struct WasmMinusDm {
|
||||
inner: wc::MinusDm,
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = MINUS_DM)]
|
||||
impl WasmMinusDm {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(period: usize) -> Result<WasmMinusDm, JsError> {
|
||||
Ok(Self {
|
||||
inner: wc::MinusDm::new(period).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
pub fn update(&mut self, high: f64, low: f64, close: f64) -> Result<Option<f64>, JsError> {
|
||||
let c = make_candle(high, low, close, 0.0)?;
|
||||
Ok(self.inner.update(c))
|
||||
}
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
high: &[f64],
|
||||
low: &[f64],
|
||||
close: &[f64],
|
||||
) -> Result<Float64Array, JsError> {
|
||||
if high.len() != low.len() || low.len() != close.len() {
|
||||
return Err(JsError::new("high, low, close must be equal length"));
|
||||
}
|
||||
let mut out = Vec::with_capacity(high.len());
|
||||
for i in 0..high.len() {
|
||||
let c = make_candle(high[i], low[i], close[i], 0.0)?;
|
||||
out.push(self.inner.update(c).unwrap_or(f64::NAN));
|
||||
}
|
||||
Ok(Float64Array::from(out.as_slice()))
|
||||
}
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = PLUS_DI)]
|
||||
pub struct WasmPlusDi {
|
||||
inner: wc::PlusDi,
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = PLUS_DI)]
|
||||
impl WasmPlusDi {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(period: usize) -> Result<WasmPlusDi, JsError> {
|
||||
Ok(Self {
|
||||
inner: wc::PlusDi::new(period).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
pub fn update(&mut self, high: f64, low: f64, close: f64) -> Result<Option<f64>, JsError> {
|
||||
let c = make_candle(high, low, close, 0.0)?;
|
||||
Ok(self.inner.update(c))
|
||||
}
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
high: &[f64],
|
||||
low: &[f64],
|
||||
close: &[f64],
|
||||
) -> Result<Float64Array, JsError> {
|
||||
if high.len() != low.len() || low.len() != close.len() {
|
||||
return Err(JsError::new("high, low, close must be equal length"));
|
||||
}
|
||||
let mut out = Vec::with_capacity(high.len());
|
||||
for i in 0..high.len() {
|
||||
let c = make_candle(high[i], low[i], close[i], 0.0)?;
|
||||
out.push(self.inner.update(c).unwrap_or(f64::NAN));
|
||||
}
|
||||
Ok(Float64Array::from(out.as_slice()))
|
||||
}
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = MINUS_DI)]
|
||||
pub struct WasmMinusDi {
|
||||
inner: wc::MinusDi,
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = MINUS_DI)]
|
||||
impl WasmMinusDi {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(period: usize) -> Result<WasmMinusDi, JsError> {
|
||||
Ok(Self {
|
||||
inner: wc::MinusDi::new(period).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
pub fn update(&mut self, high: f64, low: f64, close: f64) -> Result<Option<f64>, JsError> {
|
||||
let c = make_candle(high, low, close, 0.0)?;
|
||||
Ok(self.inner.update(c))
|
||||
}
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
high: &[f64],
|
||||
low: &[f64],
|
||||
close: &[f64],
|
||||
) -> Result<Float64Array, JsError> {
|
||||
if high.len() != low.len() || low.len() != close.len() {
|
||||
return Err(JsError::new("high, low, close must be equal length"));
|
||||
}
|
||||
let mut out = Vec::with_capacity(high.len());
|
||||
for i in 0..high.len() {
|
||||
let c = make_candle(high[i], low[i], close[i], 0.0)?;
|
||||
out.push(self.inner.update(c).unwrap_or(f64::NAN));
|
||||
}
|
||||
Ok(Float64Array::from(out.as_slice()))
|
||||
}
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = DX)]
|
||||
pub struct WasmDx {
|
||||
inner: wc::Dx,
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = DX)]
|
||||
impl WasmDx {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(period: usize) -> Result<WasmDx, JsError> {
|
||||
Ok(Self {
|
||||
inner: wc::Dx::new(period).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
pub fn update(&mut self, high: f64, low: f64, close: f64) -> Result<Option<f64>, JsError> {
|
||||
let c = make_candle(high, low, close, 0.0)?;
|
||||
Ok(self.inner.update(c))
|
||||
}
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
high: &[f64],
|
||||
low: &[f64],
|
||||
close: &[f64],
|
||||
) -> Result<Float64Array, JsError> {
|
||||
if high.len() != low.len() || low.len() != close.len() {
|
||||
return Err(JsError::new("high, low, close must be equal length"));
|
||||
}
|
||||
let mut out = Vec::with_capacity(high.len());
|
||||
for i in 0..high.len() {
|
||||
let c = make_candle(high[i], low[i], close[i], 0.0)?;
|
||||
out.push(self.inner.update(c).unwrap_or(f64::NAN));
|
||||
}
|
||||
Ok(Float64Array::from(out.as_slice()))
|
||||
}
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = MIDPRICE)]
|
||||
pub struct WasmMidPrice {
|
||||
inner: wc::MidPrice,
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = MIDPRICE)]
|
||||
impl WasmMidPrice {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(period: usize) -> Result<WasmMidPrice, JsError> {
|
||||
Ok(Self {
|
||||
inner: wc::MidPrice::new(period).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
pub fn update(&mut self, high: f64, low: f64, close: f64) -> Result<Option<f64>, JsError> {
|
||||
let c = make_candle(high, low, close, 0.0)?;
|
||||
Ok(self.inner.update(c))
|
||||
}
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
high: &[f64],
|
||||
low: &[f64],
|
||||
close: &[f64],
|
||||
) -> Result<Float64Array, JsError> {
|
||||
if high.len() != low.len() || low.len() != close.len() {
|
||||
return Err(JsError::new("high, low, close must be equal length"));
|
||||
}
|
||||
let mut out = Vec::with_capacity(high.len());
|
||||
for i in 0..high.len() {
|
||||
let c = make_candle(high[i], low[i], close[i], 0.0)?;
|
||||
out.push(self.inner.update(c).unwrap_or(f64::NAN));
|
||||
}
|
||||
Ok(Float64Array::from(out.as_slice()))
|
||||
}
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = AVGPRICE)]
|
||||
pub struct WasmAvgPrice {
|
||||
inner: wc::AvgPrice,
|
||||
}
|
||||
|
||||
impl Default for WasmAvgPrice {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = AVGPRICE)]
|
||||
impl WasmAvgPrice {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> WasmAvgPrice {
|
||||
Self {
|
||||
inner: wc::AvgPrice::new(),
|
||||
}
|
||||
}
|
||||
pub fn update(
|
||||
&mut self,
|
||||
open: f64,
|
||||
high: f64,
|
||||
low: f64,
|
||||
close: f64,
|
||||
) -> Result<Option<f64>, JsError> {
|
||||
let c = make_candle_ohlc(open, high, low, close)?;
|
||||
Ok(self.inner.update(c))
|
||||
}
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
open: &[f64],
|
||||
high: &[f64],
|
||||
low: &[f64],
|
||||
close: &[f64],
|
||||
) -> Result<Float64Array, JsError> {
|
||||
if !(open.len() == high.len() && high.len() == low.len() && low.len() == close.len()) {
|
||||
return Err(JsError::new(
|
||||
"open, high, low and close must be equal length",
|
||||
));
|
||||
}
|
||||
let mut out = Vec::with_capacity(close.len());
|
||||
for i in 0..close.len() {
|
||||
let c = make_candle_ohlc(open[i], high[i], low[i], close[i])?;
|
||||
out.push(self.inner.update(c).unwrap_or(f64::NAN));
|
||||
}
|
||||
Ok(Float64Array::from(out.as_slice()))
|
||||
}
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = MACDEXT)]
|
||||
pub struct WasmMacdExt {
|
||||
inner: wc::MacdExt,
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = MACDEXT)]
|
||||
impl WasmMacdExt {
|
||||
/// Moving-average types are TA-Lib `MA_Type` codes `0..=5`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(
|
||||
fast: usize,
|
||||
fast_matype: u32,
|
||||
slow: usize,
|
||||
slow_matype: u32,
|
||||
signal: usize,
|
||||
signal_matype: u32,
|
||||
) -> Result<WasmMacdExt, JsError> {
|
||||
Ok(Self {
|
||||
inner: wc::MacdExt::new(
|
||||
fast,
|
||||
wc::MaType::from_code(fast_matype).map_err(map_err)?,
|
||||
slow,
|
||||
wc::MaType::from_code(slow_matype).map_err(map_err)?,
|
||||
signal,
|
||||
wc::MaType::from_code(signal_matype).map_err(map_err)?,
|
||||
)
|
||||
.map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
pub fn update(&mut self, value: f64) -> JsValue {
|
||||
match self.inner.update(value) {
|
||||
Some(o) => {
|
||||
let obj = Object::new();
|
||||
Reflect::set(&obj, &"macd".into(), &o.macd.into()).ok();
|
||||
Reflect::set(&obj, &"signal".into(), &o.signal.into()).ok();
|
||||
Reflect::set(&obj, &"histogram".into(), &o.histogram.into()).ok();
|
||||
obj.into()
|
||||
}
|
||||
None => JsValue::NULL,
|
||||
}
|
||||
}
|
||||
/// Returns a flat `Float64Array` of length `3 * n`: `[macd0, sig0, hist0, ...]`.
|
||||
pub fn batch(&mut self, prices: &[f64]) -> Float64Array {
|
||||
let n = prices.len();
|
||||
let mut out = vec![f64::NAN; n * 3];
|
||||
for (i, p) in prices.iter().enumerate() {
|
||||
if let Some(o) = self.inner.update(*p) {
|
||||
out[i * 3] = o.macd;
|
||||
out[i * 3 + 1] = o.signal;
|
||||
out[i * 3 + 2] = o.histogram;
|
||||
}
|
||||
}
|
||||
Float64Array::from(out.as_slice())
|
||||
}
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = MACDFIX)]
|
||||
pub struct WasmMacdFix {
|
||||
inner: wc::MacdFix,
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = MACDFIX)]
|
||||
impl WasmMacdFix {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(signal: usize) -> Result<WasmMacdFix, JsError> {
|
||||
Ok(Self {
|
||||
inner: wc::MacdFix::new(signal).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
pub fn update(&mut self, value: f64) -> JsValue {
|
||||
match self.inner.update(value) {
|
||||
Some(o) => {
|
||||
let obj = Object::new();
|
||||
Reflect::set(&obj, &"macd".into(), &o.macd.into()).ok();
|
||||
Reflect::set(&obj, &"signal".into(), &o.signal.into()).ok();
|
||||
Reflect::set(&obj, &"histogram".into(), &o.histogram.into()).ok();
|
||||
obj.into()
|
||||
}
|
||||
None => JsValue::NULL,
|
||||
}
|
||||
}
|
||||
/// Returns a flat `Float64Array` of length `3 * n`: `[macd0, sig0, hist0, ...]`.
|
||||
pub fn batch(&mut self, prices: &[f64]) -> Float64Array {
|
||||
let n = prices.len();
|
||||
let mut out = vec![f64::NAN; n * 3];
|
||||
for (i, p) in prices.iter().enumerate() {
|
||||
if let Some(o) = self.inner.update(*p) {
|
||||
out[i * 3] = o.macd;
|
||||
out[i * 3 + 1] = o.signal;
|
||||
out[i * 3 + 2] = o.histogram;
|
||||
}
|
||||
}
|
||||
Float64Array::from(out.as_slice())
|
||||
}
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = SAREXT)]
|
||||
pub struct WasmSarExt {
|
||||
inner: wc::SarExt,
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = SAREXT)]
|
||||
impl WasmSarExt {
|
||||
#[wasm_bindgen(constructor)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
start_value: f64,
|
||||
offset_on_reverse: f64,
|
||||
accel_init_long: f64,
|
||||
accel_long: f64,
|
||||
accel_max_long: f64,
|
||||
accel_init_short: f64,
|
||||
accel_short: f64,
|
||||
accel_max_short: f64,
|
||||
) -> Result<WasmSarExt, JsError> {
|
||||
Ok(Self {
|
||||
inner: wc::SarExt::new(
|
||||
start_value,
|
||||
offset_on_reverse,
|
||||
accel_init_long,
|
||||
accel_long,
|
||||
accel_max_long,
|
||||
accel_init_short,
|
||||
accel_short,
|
||||
accel_max_short,
|
||||
)
|
||||
.map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
pub fn update(&mut self, high: f64, low: f64, close: f64) -> Result<Option<f64>, JsError> {
|
||||
let c = make_candle(high, low, close, 0.0)?;
|
||||
Ok(self.inner.update(c))
|
||||
}
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
high: &[f64],
|
||||
low: &[f64],
|
||||
close: &[f64],
|
||||
) -> Result<Float64Array, JsError> {
|
||||
if high.len() != low.len() || low.len() != close.len() {
|
||||
return Err(JsError::new("high, low, close must be equal length"));
|
||||
}
|
||||
let mut out = Vec::with_capacity(high.len());
|
||||
for i in 0..high.len() {
|
||||
let c = make_candle(high[i], low[i], close[i], 0.0)?;
|
||||
out.push(self.inner.update(c).unwrap_or(f64::NAN));
|
||||
}
|
||||
Ok(Float64Array::from(out.as_slice()))
|
||||
}
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = HT_PHASOR)]
|
||||
pub struct WasmHtPhasor {
|
||||
inner: wc::HtPhasor,
|
||||
}
|
||||
|
||||
impl Default for WasmHtPhasor {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = HT_PHASOR)]
|
||||
impl WasmHtPhasor {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> WasmHtPhasor {
|
||||
Self {
|
||||
inner: wc::HtPhasor::new(),
|
||||
}
|
||||
}
|
||||
pub fn update(&mut self, value: f64) -> JsValue {
|
||||
match self.inner.update(value) {
|
||||
Some(o) => {
|
||||
let obj = Object::new();
|
||||
Reflect::set(&obj, &"inphase".into(), &o.inphase.into()).ok();
|
||||
Reflect::set(&obj, &"quadrature".into(), &o.quadrature.into()).ok();
|
||||
obj.into()
|
||||
}
|
||||
None => JsValue::NULL,
|
||||
}
|
||||
}
|
||||
/// Returns a flat `Float64Array` of length `2 * n`: `[inphase0, quad0, ...]`.
|
||||
pub fn batch(&mut self, prices: &[f64]) -> Float64Array {
|
||||
let n = prices.len();
|
||||
let mut out = vec![f64::NAN; n * 2];
|
||||
for (i, p) in prices.iter().enumerate() {
|
||||
if let Some(o) = self.inner.update(*p) {
|
||||
out[i * 2] = o.inphase;
|
||||
out[i * 2 + 1] = o.quadrature;
|
||||
}
|
||||
}
|
||||
Float64Array::from(out.as_slice())
|
||||
}
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = Stochastic)]
|
||||
pub struct WasmStoch {
|
||||
inner: wc::Stochastic,
|
||||
@@ -3826,6 +4314,72 @@ impl WasmAroon {
|
||||
|
||||
// ============================== Family 10: parameterless / multi-output ==============================
|
||||
|
||||
#[wasm_bindgen(js_name = HT_DCPHASE)]
|
||||
pub struct WasmHtDcPhase {
|
||||
inner: wc::HtDcPhase,
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = HT_DCPHASE)]
|
||||
impl WasmHtDcPhase {
|
||||
#[wasm_bindgen(constructor)]
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new() -> WasmHtDcPhase {
|
||||
Self {
|
||||
inner: wc::HtDcPhase::new(),
|
||||
}
|
||||
}
|
||||
pub fn update(&mut self, value: f64) -> Option<f64> {
|
||||
self.inner.update(value)
|
||||
}
|
||||
pub fn batch(&mut self, prices: &[f64]) -> Float64Array {
|
||||
Float64Array::from(flatten(self.inner.batch(prices)).as_slice())
|
||||
}
|
||||
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 = HT_TRENDMODE)]
|
||||
pub struct WasmHtTrendMode {
|
||||
inner: wc::HtTrendMode,
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = HT_TRENDMODE)]
|
||||
impl WasmHtTrendMode {
|
||||
#[wasm_bindgen(constructor)]
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new() -> WasmHtTrendMode {
|
||||
Self {
|
||||
inner: wc::HtTrendMode::new(),
|
||||
}
|
||||
}
|
||||
pub fn update(&mut self, value: f64) -> Option<f64> {
|
||||
self.inner.update(value)
|
||||
}
|
||||
pub fn batch(&mut self, prices: &[f64]) -> Float64Array {
|
||||
Float64Array::from(flatten(self.inner.batch(prices)).as_slice())
|
||||
}
|
||||
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 = HilbertDominantCycle)]
|
||||
pub struct WasmHilbertDominantCycle {
|
||||
inner: wc::HilbertDominantCycle,
|
||||
@@ -8339,6 +8893,12 @@ wasm_scalar_indicator!(WasmSortinoRatio, "SortinoRatio", wc::SortinoRatio, perio
|
||||
wasm_scalar_indicator!(WasmOmegaRatio, "OmegaRatio", wc::OmegaRatio, period: usize, threshold: f64);
|
||||
wasm_scalar_indicator!(WasmValueAtRisk, "ValueAtRisk", wc::ValueAtRisk, period: usize, confidence: f64);
|
||||
wasm_scalar_indicator!(WasmConditionalValueAtRisk, "ConditionalValueAtRisk", wc::ConditionalValueAtRisk, period: usize, confidence: f64);
|
||||
wasm_scalar_indicator!(WasmMidPoint, "MIDPOINT", wc::MidPoint, period: usize);
|
||||
wasm_scalar_indicator!(WasmRocp, "ROCP", wc::Rocp, period: usize);
|
||||
wasm_scalar_indicator!(WasmRocr, "ROCR", wc::Rocr, period: usize);
|
||||
wasm_scalar_indicator!(WasmRocr100, "ROCR100", wc::Rocr100, period: usize);
|
||||
wasm_scalar_indicator!(WasmLinRegIntercept, "LINEARREG_INTERCEPT", wc::LinRegIntercept, period: usize);
|
||||
wasm_scalar_indicator!(WasmTsf, "TSF", wc::Tsf, period: usize);
|
||||
|
||||
// --- DrawdownDuration: u32 output, no constructor args ---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user