feat(seasonality): add the Seasonality & Session family (12 indicators) (#161)
## Summary Adds the **Seasonality & Session** family — the first family that reads the wall-clock fields of `Candle::timestamp`. A new private `calendar` module decomposes an epoch-millisecond instant (shifted by a per-indicator `utc_offset_minutes`) into civil fields via Howard Hinnant's branch-light `civil_from_days` algorithm. Session / day / month rollovers are detected automatically, so callers never have to invoke `reset()` at a boundary. Indicator counter **339 → 351**; family count **20 → 21**. ## Indicators | Shape | Indicators | |-------|-----------| | Scalar (`f64`) | `SessionVwap`, `AverageDailyRange`, `OvernightGap`, `TurnOfMonth`, `SeasonalZScore` | | Struct | `SessionHighLow`, `SessionRange` (Asia/EU/US), `OvernightIntradayReturn` | | Profile (`Vec<f64>`) | `TimeOfDayReturnProfile`, `DayOfWeekProfile`, `IntradayVolatilityProfile`, `VolumeByTimeProfile` | ## Bindings The input is the **full** candle (`open, high, low, close, volume, timestamp`), not the `high/low/close` slice the value-indicator helper assumes, so the Python / Node / WASM bindings are custom full-candle implementations: - **Python** — `update((o,h,l,c,v,ts))`; `batch(open, high, low, close, volume, timestamp)` → `PyArray1` (scalar) / `PyArray2` (struct & profile), warmup rows `NaN`. - **Node** — `update(open, high, low, close, volume, timestamp)`; `batch(...)` → flat `Vec<f64>`; struct outputs as `#[napi(object)]` values. - **WASM** — `update` only (multi-input precedent); profiles as `Float64Array`, structs as camelCase objects, `timestamp` as `BigInt`. ## Verification - `wickra-core`: full per-branch unit tests, **100%** coverage target; 2852 lib tests + 334 doctests green. - `cargo clippy --workspace --all-targets --all-features -- -D warnings`: clean. - Node: 428 tests (dedicated `seasonality.test.js` streaming-vs-batch). - Python: full suite + dedicated `test_seasonality.py` streaming-vs-batch. - Counter check: mod-count == counted lib block == 351.
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
// Streaming-vs-batch equivalence and reference values for the Seasonality &
|
||||
// Session family. These indicators consume the full candle (open, high, low,
|
||||
// close, volume, timestamp), so they have a dedicated suite.
|
||||
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const wickra = require('..');
|
||||
|
||||
const HOUR = 3_600_000;
|
||||
const N = 240;
|
||||
const close = Array.from({ length: N }, (_, i) => 100 + Math.sin(i * 0.3) * 5 + Math.cos(i * 0.1) * 3);
|
||||
const open = close.map((c, i) => c + Math.sin(i * 0.5) * 0.5);
|
||||
const high = close.map((c, i) => Math.max(open[i], c) + 1);
|
||||
const low = close.map((c, i) => Math.min(open[i], c) - 1);
|
||||
const volume = Array.from({ length: N }, (_, i) => 1000 + (i % 24) * 50);
|
||||
const ts = Array.from({ length: N }, (_, i) => i * HOUR);
|
||||
|
||||
function eq(a, b) {
|
||||
if (Number.isNaN(a)) return Number.isNaN(b);
|
||||
return Math.abs(a - b) < 1e-9;
|
||||
}
|
||||
|
||||
function streamScalar(ind, i) {
|
||||
const v = ind.update(open[i], high[i], low[i], close[i], volume[i], ts[i]);
|
||||
return v === null || v === undefined ? NaN : v;
|
||||
}
|
||||
|
||||
function checkScalar(name, make) {
|
||||
test(`${name} streaming equals batch`, () => {
|
||||
const a = make();
|
||||
const b = make();
|
||||
const batch = b.batch(open, high, low, close, volume, ts);
|
||||
for (let i = 0; i < N; i += 1) {
|
||||
assert.ok(eq(streamScalar(a, i), batch[i]), `${name} row ${i}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function checkMatrix(name, make, k, pick) {
|
||||
test(`${name} streaming equals batch`, () => {
|
||||
const a = make();
|
||||
const b = make();
|
||||
const batch = b.batch(open, high, low, close, volume, ts);
|
||||
for (let i = 0; i < N; i += 1) {
|
||||
const out = a.update(open[i], high[i], low[i], close[i], volume[i], ts[i]);
|
||||
for (let j = 0; j < k; j += 1) {
|
||||
const s = out === null || out === undefined ? NaN : pick(out, j);
|
||||
assert.ok(eq(s, batch[i * k + j]), `${name} row ${i} col ${j}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
checkScalar('SessionVwap', () => new wickra.SessionVwap(0));
|
||||
checkScalar('OvernightGap', () => new wickra.OvernightGap(0));
|
||||
checkScalar('SeasonalZScore', () => new wickra.SeasonalZScore(0));
|
||||
checkScalar('AverageDailyRange', () => new wickra.AverageDailyRange(3, 0));
|
||||
checkScalar('TurnOfMonth', () => new wickra.TurnOfMonth(3, 1, 0));
|
||||
|
||||
checkMatrix('SessionHighLow', () => new wickra.SessionHighLow(0), 2, (o, j) => (j === 0 ? o.high : o.low));
|
||||
checkMatrix('SessionRange', () => new wickra.SessionRange(0), 3, (o, j) => [o.asia, o.eu, o.us][j]);
|
||||
checkMatrix(
|
||||
'OvernightIntradayReturn',
|
||||
() => new wickra.OvernightIntradayReturn(0),
|
||||
2,
|
||||
(o, j) => (j === 0 ? o.overnight : o.intraday),
|
||||
);
|
||||
checkMatrix('TimeOfDayReturnProfile', () => new wickra.TimeOfDayReturnProfile(24, 0), 24, (o, j) => o[j]);
|
||||
checkMatrix('IntradayVolatilityProfile', () => new wickra.IntradayVolatilityProfile(12, 0), 12, (o, j) => o[j]);
|
||||
checkMatrix('VolumeByTimeProfile', () => new wickra.VolumeByTimeProfile(24, 0), 24, (o, j) => o[j]);
|
||||
checkMatrix('DayOfWeekProfile', () => new wickra.DayOfWeekProfile(0), 7, (o, j) => o[j]);
|
||||
|
||||
test('SessionVwap reference value', () => {
|
||||
const vwap = new wickra.SessionVwap(0);
|
||||
assert.ok(eq(vwap.update(100, 100, 100, 100, 10, 0), 100));
|
||||
assert.ok(eq(vwap.update(110, 110, 110, 110, 30, HOUR), 107.5));
|
||||
assert.ok(eq(vwap.update(200, 200, 200, 200, 5, 24 * HOUR), 200));
|
||||
});
|
||||
|
||||
test('OvernightGap reference value', () => {
|
||||
const gap = new wickra.OvernightGap(0);
|
||||
assert.equal(gap.update(99, 101, 98, 100, 1, 0), null);
|
||||
assert.ok(eq(gap.update(105, 106, 104, 105.5, 1, 24 * HOUR), 0.05));
|
||||
});
|
||||
|
||||
test('SessionHighLow reference object', () => {
|
||||
const shl = new wickra.SessionHighLow(0);
|
||||
shl.update(100, 105, 99, 101, 1, 0);
|
||||
const out = shl.update(101, 108, 100, 107, 1, HOUR);
|
||||
assert.ok(eq(out.high, 108));
|
||||
assert.ok(eq(out.low, 99));
|
||||
});
|
||||
|
||||
test('AverageDailyRange rejects zero period', () => {
|
||||
assert.throws(() => new wickra.AverageDailyRange(0, 0));
|
||||
});
|
||||
Vendored
+131
@@ -349,6 +349,19 @@ export interface PnfColumnValue {
|
||||
high: number
|
||||
low: number
|
||||
}
|
||||
export interface SessionHighLowValue {
|
||||
high: number
|
||||
low: number
|
||||
}
|
||||
export interface SessionRangeValue {
|
||||
asia: number
|
||||
eu: number
|
||||
us: number
|
||||
}
|
||||
export interface OvernightIntradayReturnValue {
|
||||
overnight: number
|
||||
intraday: number
|
||||
}
|
||||
export type SmaNode = SMA
|
||||
export declare class SMA {
|
||||
constructor(period: number)
|
||||
@@ -3557,3 +3570,121 @@ export declare class Alpha {
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type SessionVwapNode = SessionVwap
|
||||
export declare class SessionVwap {
|
||||
constructor(utcOffsetMinutes: number)
|
||||
update(open: number, high: number, low: number, close: number, volume: number, timestamp: number): number | null
|
||||
batch(open: Array<number>, high: Array<number>, low: Array<number>, close: Array<number>, volume: Array<number>, timestamp: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
utcOffsetMinutes(): number
|
||||
}
|
||||
export type OvernightGapNode = OvernightGap
|
||||
export declare class OvernightGap {
|
||||
constructor(utcOffsetMinutes: number)
|
||||
update(open: number, high: number, low: number, close: number, volume: number, timestamp: number): number | null
|
||||
batch(open: Array<number>, high: Array<number>, low: Array<number>, close: Array<number>, volume: Array<number>, timestamp: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
utcOffsetMinutes(): number
|
||||
}
|
||||
export type SeasonalZScoreNode = SeasonalZScore
|
||||
export declare class SeasonalZScore {
|
||||
constructor(utcOffsetMinutes: number)
|
||||
update(open: number, high: number, low: number, close: number, volume: number, timestamp: number): number | null
|
||||
batch(open: Array<number>, high: Array<number>, low: Array<number>, close: Array<number>, volume: Array<number>, timestamp: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
utcOffsetMinutes(): number
|
||||
}
|
||||
export type TimeOfDayReturnProfileNode = TimeOfDayReturnProfile
|
||||
export declare class TimeOfDayReturnProfile {
|
||||
constructor(buckets: number, utcOffsetMinutes: number)
|
||||
update(open: number, high: number, low: number, close: number, volume: number, timestamp: number): Array<number> | null
|
||||
batch(open: Array<number>, high: Array<number>, low: Array<number>, close: Array<number>, volume: Array<number>, timestamp: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
buckets(): number
|
||||
utcOffsetMinutes(): number
|
||||
}
|
||||
export type IntradayVolatilityProfileNode = IntradayVolatilityProfile
|
||||
export declare class IntradayVolatilityProfile {
|
||||
constructor(buckets: number, utcOffsetMinutes: number)
|
||||
update(open: number, high: number, low: number, close: number, volume: number, timestamp: number): Array<number> | null
|
||||
batch(open: Array<number>, high: Array<number>, low: Array<number>, close: Array<number>, volume: Array<number>, timestamp: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
buckets(): number
|
||||
utcOffsetMinutes(): number
|
||||
}
|
||||
export type VolumeByTimeProfileNode = VolumeByTimeProfile
|
||||
export declare class VolumeByTimeProfile {
|
||||
constructor(buckets: number, utcOffsetMinutes: number)
|
||||
update(open: number, high: number, low: number, close: number, volume: number, timestamp: number): Array<number> | null
|
||||
batch(open: Array<number>, high: Array<number>, low: Array<number>, close: Array<number>, volume: Array<number>, timestamp: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
buckets(): number
|
||||
utcOffsetMinutes(): number
|
||||
}
|
||||
export type DayOfWeekProfileNode = DayOfWeekProfile
|
||||
export declare class DayOfWeekProfile {
|
||||
constructor(utcOffsetMinutes: number)
|
||||
update(open: number, high: number, low: number, close: number, volume: number, timestamp: number): Array<number> | null
|
||||
batch(open: Array<number>, high: Array<number>, low: Array<number>, close: Array<number>, volume: Array<number>, timestamp: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
utcOffsetMinutes(): number
|
||||
}
|
||||
export type AverageDailyRangeNode = AverageDailyRange
|
||||
export declare class AverageDailyRange {
|
||||
constructor(period: number, utcOffsetMinutes: number)
|
||||
update(open: number, high: number, low: number, close: number, volume: number, timestamp: number): number | null
|
||||
batch(open: Array<number>, high: Array<number>, low: Array<number>, close: Array<number>, volume: Array<number>, timestamp: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type TurnOfMonthNode = TurnOfMonth
|
||||
export declare class TurnOfMonth {
|
||||
constructor(nFirst: number, nLast: number, utcOffsetMinutes: number)
|
||||
update(open: number, high: number, low: number, close: number, volume: number, timestamp: number): number | null
|
||||
batch(open: Array<number>, high: Array<number>, low: Array<number>, close: Array<number>, volume: Array<number>, timestamp: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type SessionHighLowNode = SessionHighLow
|
||||
export declare class SessionHighLow {
|
||||
constructor(utcOffsetMinutes: number)
|
||||
update(open: number, high: number, low: number, close: number, volume: number, timestamp: number): SessionHighLowValue | null
|
||||
batch(open: Array<number>, high: Array<number>, low: Array<number>, close: Array<number>, volume: Array<number>, timestamp: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type SessionRangeNode = SessionRange
|
||||
export declare class SessionRange {
|
||||
constructor(utcOffsetMinutes: number)
|
||||
update(open: number, high: number, low: number, close: number, volume: number, timestamp: number): SessionRangeValue | null
|
||||
batch(open: Array<number>, high: Array<number>, low: Array<number>, close: Array<number>, volume: Array<number>, timestamp: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type OvernightIntradayReturnNode = OvernightIntradayReturn
|
||||
export declare class OvernightIntradayReturn {
|
||||
constructor(utcOffsetMinutes: number)
|
||||
update(open: number, high: number, low: number, close: number, volume: number, timestamp: number): OvernightIntradayReturnValue | null
|
||||
batch(open: Array<number>, high: Array<number>, low: Array<number>, close: Array<number>, volume: Array<number>, timestamp: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
|
||||
+13
-1
@@ -310,7 +310,7 @@ if (!nativeBinding) {
|
||||
throw new Error(`Failed to load native binding`)
|
||||
}
|
||||
|
||||
const { version, SMA, EMA, WMA, RSI, DEMA, TEMA, HMA, ROC, TRIX, SMMA, TRIMA, ZLEMA, MOM, CMO, DPO, StdDev, UlcerIndex, VerticalHorizontalFilter, ZScore, McGinleyDynamic, FRAMA, SuperSmoother, FisherTransform, Decycler, CenterOfGravity, CyberneticCycle, InstantaneousTrendline, EhlersStochastic, RVIVolatility, Variance, CoefficientOfVariation, Skewness, Kurtosis, StandardError, DetrendedStdDev, RSquared, MedianAbsoluteDeviation, MIDPOINT, ROCP, ROCR, ROCR100, LINEARREG_INTERCEPT, TSF, Autocorrelation, HurstExponent, PearsonCorrelation, Beta, PairwiseBeta, SpearmanCorrelation, 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
|
||||
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, SessionVwap, OvernightGap, SeasonalZScore, TimeOfDayReturnProfile, IntradayVolatilityProfile, VolumeByTimeProfile, DayOfWeekProfile, AverageDailyRange, TurnOfMonth, SessionHighLow, SessionRange, OvernightIntradayReturn } = nativeBinding
|
||||
|
||||
module.exports.version = version
|
||||
module.exports.SMA = SMA
|
||||
@@ -652,3 +652,15 @@ module.exports.RenkoBars = RenkoBars
|
||||
module.exports.KagiBars = KagiBars
|
||||
module.exports.PointAndFigureBars = PointAndFigureBars
|
||||
module.exports.Alpha = Alpha
|
||||
module.exports.SessionVwap = SessionVwap
|
||||
module.exports.OvernightGap = OvernightGap
|
||||
module.exports.SeasonalZScore = SeasonalZScore
|
||||
module.exports.TimeOfDayReturnProfile = TimeOfDayReturnProfile
|
||||
module.exports.IntradayVolatilityProfile = IntradayVolatilityProfile
|
||||
module.exports.VolumeByTimeProfile = VolumeByTimeProfile
|
||||
module.exports.DayOfWeekProfile = DayOfWeekProfile
|
||||
module.exports.AverageDailyRange = AverageDailyRange
|
||||
module.exports.TurnOfMonth = TurnOfMonth
|
||||
module.exports.SessionHighLow = SessionHighLow
|
||||
module.exports.SessionRange = SessionRange
|
||||
module.exports.OvernightIntradayReturn = OvernightIntradayReturn
|
||||
|
||||
@@ -13398,3 +13398,615 @@ impl AlphaNode {
|
||||
self.inner.warmup_period() as u32
|
||||
}
|
||||
}
|
||||
|
||||
// ====================== Seasonality & Session (full-candle) ======================
|
||||
//
|
||||
// These read the wall-clock fields of `Candle::timestamp`, so the bindings take
|
||||
// the full candle (open, high, low, close, volume, timestamp) rather than the
|
||||
// high/low/close slice used by the candle indicators above.
|
||||
|
||||
fn season_candles(
|
||||
open: &[f64],
|
||||
high: &[f64],
|
||||
low: &[f64],
|
||||
close: &[f64],
|
||||
volume: &[f64],
|
||||
timestamp: &[i64],
|
||||
) -> napi::Result<Vec<wc::Candle>> {
|
||||
let n = open.len();
|
||||
if [
|
||||
high.len(),
|
||||
low.len(),
|
||||
close.len(),
|
||||
volume.len(),
|
||||
timestamp.len(),
|
||||
]
|
||||
.iter()
|
||||
.any(|&x| x != n)
|
||||
{
|
||||
return Err(NapiError::from_reason(
|
||||
"open, high, low, close, volume, timestamp must be equal length".to_string(),
|
||||
));
|
||||
}
|
||||
let mut out = Vec::with_capacity(n);
|
||||
for i in 0..n {
|
||||
out.push(
|
||||
wc::Candle::new(open[i], high[i], low[i], close[i], volume[i], timestamp[i])
|
||||
.map_err(map_err)?,
|
||||
);
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
macro_rules! node_seasonality_offset_scalar {
|
||||
($wrapper:ident, $node_name:literal, $rust_ty:ty) => {
|
||||
#[napi(js_name = $node_name)]
|
||||
pub struct $wrapper {
|
||||
inner: $rust_ty,
|
||||
}
|
||||
#[napi]
|
||||
impl $wrapper {
|
||||
#[napi(constructor)]
|
||||
pub fn new(utc_offset_minutes: i32) -> Self {
|
||||
Self {
|
||||
inner: <$rust_ty>::new(utc_offset_minutes),
|
||||
}
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(
|
||||
&mut self,
|
||||
open: f64,
|
||||
high: f64,
|
||||
low: f64,
|
||||
close: f64,
|
||||
volume: f64,
|
||||
timestamp: i64,
|
||||
) -> napi::Result<Option<f64>> {
|
||||
Ok(self.inner.update(
|
||||
wc::Candle::new(open, high, low, close, volume, timestamp).map_err(map_err)?,
|
||||
))
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
open: Vec<f64>,
|
||||
high: Vec<f64>,
|
||||
low: Vec<f64>,
|
||||
close: Vec<f64>,
|
||||
volume: Vec<f64>,
|
||||
timestamp: Vec<i64>,
|
||||
) -> napi::Result<Vec<f64>> {
|
||||
let candles = season_candles(&open, &high, &low, &close, &volume, ×tamp)?;
|
||||
Ok(candles
|
||||
.into_iter()
|
||||
.map(|c| self.inner.update(c).unwrap_or(f64::NAN))
|
||||
.collect())
|
||||
}
|
||||
#[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 = "utcOffsetMinutes")]
|
||||
pub fn utc_offset_minutes(&self) -> i32 {
|
||||
self.inner.utc_offset_minutes()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! node_seasonality_bucket_profile {
|
||||
($wrapper:ident, $node_name:literal, $rust_ty:ty) => {
|
||||
#[napi(js_name = $node_name)]
|
||||
pub struct $wrapper {
|
||||
inner: $rust_ty,
|
||||
}
|
||||
#[napi]
|
||||
impl $wrapper {
|
||||
#[napi(constructor)]
|
||||
pub fn new(buckets: u32, utc_offset_minutes: i32) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: <$rust_ty>::new(buckets as usize, utc_offset_minutes)
|
||||
.map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(
|
||||
&mut self,
|
||||
open: f64,
|
||||
high: f64,
|
||||
low: f64,
|
||||
close: f64,
|
||||
volume: f64,
|
||||
timestamp: i64,
|
||||
) -> napi::Result<Option<Vec<f64>>> {
|
||||
Ok(self
|
||||
.inner
|
||||
.update(
|
||||
wc::Candle::new(open, high, low, close, volume, timestamp)
|
||||
.map_err(map_err)?,
|
||||
)
|
||||
.map(|o| o.bins))
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
open: Vec<f64>,
|
||||
high: Vec<f64>,
|
||||
low: Vec<f64>,
|
||||
close: Vec<f64>,
|
||||
volume: Vec<f64>,
|
||||
timestamp: Vec<i64>,
|
||||
) -> napi::Result<Vec<f64>> {
|
||||
let candles = season_candles(&open, &high, &low, &close, &volume, ×tamp)?;
|
||||
let k = self.inner.params().0;
|
||||
let n = candles.len();
|
||||
let mut out = vec![f64::NAN; n * k];
|
||||
for (i, c) in candles.into_iter().enumerate() {
|
||||
if let Some(o) = self.inner.update(c) {
|
||||
for (j, b) in o.bins.iter().enumerate() {
|
||||
out[i * k + j] = *b;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
#[napi]
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
#[napi(js_name = "isReady")]
|
||||
pub fn is_ready(&self) -> bool {
|
||||
self.inner.is_ready()
|
||||
}
|
||||
#[napi(js_name = "warmupPeriod")]
|
||||
pub fn warmup_period(&self) -> u32 {
|
||||
self.inner.warmup_period() as u32
|
||||
}
|
||||
#[napi(js_name = "buckets")]
|
||||
pub fn buckets(&self) -> u32 {
|
||||
self.inner.params().0 as u32
|
||||
}
|
||||
#[napi(js_name = "utcOffsetMinutes")]
|
||||
pub fn utc_offset_minutes(&self) -> i32 {
|
||||
self.inner.params().1
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! node_seasonality_offset_profile {
|
||||
($wrapper:ident, $node_name:literal, $rust_ty:ty, $k:expr) => {
|
||||
#[napi(js_name = $node_name)]
|
||||
pub struct $wrapper {
|
||||
inner: $rust_ty,
|
||||
}
|
||||
#[napi]
|
||||
impl $wrapper {
|
||||
#[napi(constructor)]
|
||||
pub fn new(utc_offset_minutes: i32) -> Self {
|
||||
Self {
|
||||
inner: <$rust_ty>::new(utc_offset_minutes),
|
||||
}
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(
|
||||
&mut self,
|
||||
open: f64,
|
||||
high: f64,
|
||||
low: f64,
|
||||
close: f64,
|
||||
volume: f64,
|
||||
timestamp: i64,
|
||||
) -> napi::Result<Option<Vec<f64>>> {
|
||||
Ok(self
|
||||
.inner
|
||||
.update(
|
||||
wc::Candle::new(open, high, low, close, volume, timestamp)
|
||||
.map_err(map_err)?,
|
||||
)
|
||||
.map(|o| o.bins))
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
open: Vec<f64>,
|
||||
high: Vec<f64>,
|
||||
low: Vec<f64>,
|
||||
close: Vec<f64>,
|
||||
volume: Vec<f64>,
|
||||
timestamp: Vec<i64>,
|
||||
) -> napi::Result<Vec<f64>> {
|
||||
let candles = season_candles(&open, &high, &low, &close, &volume, ×tamp)?;
|
||||
let k = $k;
|
||||
let n = candles.len();
|
||||
let mut out = vec![f64::NAN; n * k];
|
||||
for (i, c) in candles.into_iter().enumerate() {
|
||||
if let Some(o) = self.inner.update(c) {
|
||||
for (j, b) in o.bins.iter().enumerate() {
|
||||
out[i * k + j] = *b;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
#[napi]
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
#[napi(js_name = "isReady")]
|
||||
pub fn is_ready(&self) -> bool {
|
||||
self.inner.is_ready()
|
||||
}
|
||||
#[napi(js_name = "warmupPeriod")]
|
||||
pub fn warmup_period(&self) -> u32 {
|
||||
self.inner.warmup_period() as u32
|
||||
}
|
||||
#[napi(js_name = "utcOffsetMinutes")]
|
||||
pub fn utc_offset_minutes(&self) -> i32 {
|
||||
self.inner.utc_offset_minutes()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
node_seasonality_offset_scalar!(SessionVwapNode, "SessionVwap", wc::SessionVwap);
|
||||
node_seasonality_offset_scalar!(OvernightGapNode, "OvernightGap", wc::OvernightGap);
|
||||
node_seasonality_offset_scalar!(SeasonalZScoreNode, "SeasonalZScore", wc::SeasonalZScore);
|
||||
node_seasonality_bucket_profile!(
|
||||
TimeOfDayReturnProfileNode,
|
||||
"TimeOfDayReturnProfile",
|
||||
wc::TimeOfDayReturnProfile
|
||||
);
|
||||
node_seasonality_bucket_profile!(
|
||||
IntradayVolatilityProfileNode,
|
||||
"IntradayVolatilityProfile",
|
||||
wc::IntradayVolatilityProfile
|
||||
);
|
||||
node_seasonality_bucket_profile!(
|
||||
VolumeByTimeProfileNode,
|
||||
"VolumeByTimeProfile",
|
||||
wc::VolumeByTimeProfile
|
||||
);
|
||||
node_seasonality_offset_profile!(
|
||||
DayOfWeekProfileNode,
|
||||
"DayOfWeekProfile",
|
||||
wc::DayOfWeekProfile,
|
||||
7
|
||||
);
|
||||
|
||||
#[napi(js_name = "AverageDailyRange")]
|
||||
pub struct AverageDailyRangeNode {
|
||||
inner: wc::AverageDailyRange,
|
||||
}
|
||||
#[napi]
|
||||
impl AverageDailyRangeNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(period: u32, utc_offset_minutes: i32) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::AverageDailyRange::new(period as usize, utc_offset_minutes)
|
||||
.map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(
|
||||
&mut self,
|
||||
open: f64,
|
||||
high: f64,
|
||||
low: f64,
|
||||
close: f64,
|
||||
volume: f64,
|
||||
timestamp: i64,
|
||||
) -> napi::Result<Option<f64>> {
|
||||
Ok(self
|
||||
.inner
|
||||
.update(wc::Candle::new(open, high, low, close, volume, timestamp).map_err(map_err)?))
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
open: Vec<f64>,
|
||||
high: Vec<f64>,
|
||||
low: Vec<f64>,
|
||||
close: Vec<f64>,
|
||||
volume: Vec<f64>,
|
||||
timestamp: Vec<i64>,
|
||||
) -> napi::Result<Vec<f64>> {
|
||||
let candles = season_candles(&open, &high, &low, &close, &volume, ×tamp)?;
|
||||
Ok(candles
|
||||
.into_iter()
|
||||
.map(|c| self.inner.update(c).unwrap_or(f64::NAN))
|
||||
.collect())
|
||||
}
|
||||
#[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 = "TurnOfMonth")]
|
||||
pub struct TurnOfMonthNode {
|
||||
inner: wc::TurnOfMonth,
|
||||
}
|
||||
#[napi]
|
||||
impl TurnOfMonthNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(n_first: u32, n_last: u32, utc_offset_minutes: i32) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::TurnOfMonth::new(n_first, n_last, utc_offset_minutes).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(
|
||||
&mut self,
|
||||
open: f64,
|
||||
high: f64,
|
||||
low: f64,
|
||||
close: f64,
|
||||
volume: f64,
|
||||
timestamp: i64,
|
||||
) -> napi::Result<Option<f64>> {
|
||||
Ok(self
|
||||
.inner
|
||||
.update(wc::Candle::new(open, high, low, close, volume, timestamp).map_err(map_err)?))
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
open: Vec<f64>,
|
||||
high: Vec<f64>,
|
||||
low: Vec<f64>,
|
||||
close: Vec<f64>,
|
||||
volume: Vec<f64>,
|
||||
timestamp: Vec<i64>,
|
||||
) -> napi::Result<Vec<f64>> {
|
||||
let candles = season_candles(&open, &high, &low, &close, &volume, ×tamp)?;
|
||||
Ok(candles
|
||||
.into_iter()
|
||||
.map(|c| self.inner.update(c).unwrap_or(f64::NAN))
|
||||
.collect())
|
||||
}
|
||||
#[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 SessionHighLowValue {
|
||||
pub high: f64,
|
||||
pub low: f64,
|
||||
}
|
||||
|
||||
#[napi(js_name = "SessionHighLow")]
|
||||
pub struct SessionHighLowNode {
|
||||
inner: wc::SessionHighLow,
|
||||
}
|
||||
#[napi]
|
||||
impl SessionHighLowNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(utc_offset_minutes: i32) -> Self {
|
||||
Self {
|
||||
inner: wc::SessionHighLow::new(utc_offset_minutes),
|
||||
}
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(
|
||||
&mut self,
|
||||
open: f64,
|
||||
high: f64,
|
||||
low: f64,
|
||||
close: f64,
|
||||
volume: f64,
|
||||
timestamp: i64,
|
||||
) -> napi::Result<Option<SessionHighLowValue>> {
|
||||
Ok(self
|
||||
.inner
|
||||
.update(wc::Candle::new(open, high, low, close, volume, timestamp).map_err(map_err)?)
|
||||
.map(|o| SessionHighLowValue {
|
||||
high: o.high,
|
||||
low: o.low,
|
||||
}))
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
open: Vec<f64>,
|
||||
high: Vec<f64>,
|
||||
low: Vec<f64>,
|
||||
close: Vec<f64>,
|
||||
volume: Vec<f64>,
|
||||
timestamp: Vec<i64>,
|
||||
) -> napi::Result<Vec<f64>> {
|
||||
let candles = season_candles(&open, &high, &low, &close, &volume, ×tamp)?;
|
||||
let n = candles.len();
|
||||
let mut out = vec![f64::NAN; n * 2];
|
||||
for (i, c) in candles.into_iter().enumerate() {
|
||||
if let Some(o) = self.inner.update(c) {
|
||||
out[i * 2] = o.high;
|
||||
out[i * 2 + 1] = o.low;
|
||||
}
|
||||
}
|
||||
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 SessionRangeValue {
|
||||
pub asia: f64,
|
||||
pub eu: f64,
|
||||
pub us: f64,
|
||||
}
|
||||
|
||||
#[napi(js_name = "SessionRange")]
|
||||
pub struct SessionRangeNode {
|
||||
inner: wc::SessionRange,
|
||||
}
|
||||
#[napi]
|
||||
impl SessionRangeNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(utc_offset_minutes: i32) -> Self {
|
||||
Self {
|
||||
inner: wc::SessionRange::new(utc_offset_minutes),
|
||||
}
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(
|
||||
&mut self,
|
||||
open: f64,
|
||||
high: f64,
|
||||
low: f64,
|
||||
close: f64,
|
||||
volume: f64,
|
||||
timestamp: i64,
|
||||
) -> napi::Result<Option<SessionRangeValue>> {
|
||||
Ok(self
|
||||
.inner
|
||||
.update(wc::Candle::new(open, high, low, close, volume, timestamp).map_err(map_err)?)
|
||||
.map(|o| SessionRangeValue {
|
||||
asia: o.asia,
|
||||
eu: o.eu,
|
||||
us: o.us,
|
||||
}))
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
open: Vec<f64>,
|
||||
high: Vec<f64>,
|
||||
low: Vec<f64>,
|
||||
close: Vec<f64>,
|
||||
volume: Vec<f64>,
|
||||
timestamp: Vec<i64>,
|
||||
) -> napi::Result<Vec<f64>> {
|
||||
let candles = season_candles(&open, &high, &low, &close, &volume, ×tamp)?;
|
||||
let n = candles.len();
|
||||
let mut out = vec![f64::NAN; n * 3];
|
||||
for (i, c) in candles.into_iter().enumerate() {
|
||||
if let Some(o) = self.inner.update(c) {
|
||||
out[i * 3] = o.asia;
|
||||
out[i * 3 + 1] = o.eu;
|
||||
out[i * 3 + 2] = o.us;
|
||||
}
|
||||
}
|
||||
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 OvernightIntradayReturnValue {
|
||||
pub overnight: f64,
|
||||
pub intraday: f64,
|
||||
}
|
||||
|
||||
#[napi(js_name = "OvernightIntradayReturn")]
|
||||
pub struct OvernightIntradayReturnNode {
|
||||
inner: wc::OvernightIntradayReturn,
|
||||
}
|
||||
#[napi]
|
||||
impl OvernightIntradayReturnNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(utc_offset_minutes: i32) -> Self {
|
||||
Self {
|
||||
inner: wc::OvernightIntradayReturn::new(utc_offset_minutes),
|
||||
}
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(
|
||||
&mut self,
|
||||
open: f64,
|
||||
high: f64,
|
||||
low: f64,
|
||||
close: f64,
|
||||
volume: f64,
|
||||
timestamp: i64,
|
||||
) -> napi::Result<Option<OvernightIntradayReturnValue>> {
|
||||
Ok(self
|
||||
.inner
|
||||
.update(wc::Candle::new(open, high, low, close, volume, timestamp).map_err(map_err)?)
|
||||
.map(|o| OvernightIntradayReturnValue {
|
||||
overnight: o.overnight,
|
||||
intraday: o.intraday,
|
||||
}))
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
open: Vec<f64>,
|
||||
high: Vec<f64>,
|
||||
low: Vec<f64>,
|
||||
close: Vec<f64>,
|
||||
volume: Vec<f64>,
|
||||
timestamp: Vec<i64>,
|
||||
) -> napi::Result<Vec<f64>> {
|
||||
let candles = season_candles(&open, &high, &low, &close, &volume, ×tamp)?;
|
||||
let n = candles.len();
|
||||
let mut out = vec![f64::NAN; n * 2];
|
||||
for (i, c) in candles.into_iter().enumerate() {
|
||||
if let Some(o) = self.inner.update(c) {
|
||||
out[i * 2] = o.overnight;
|
||||
out[i * 2 + 1] = o.intraday;
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user