feat: derivatives funding & open-interest indicators (part 1 of 3) (#126)

* feat(derivatives): DerivativesTick input type + InvalidDerivatives error

* feat(derivatives): FundingRate indicator (core)

* feat(derivatives): FundingRateMean indicator (core)

* feat(derivatives): FundingRateZScore indicator (core)

* feat(derivatives): FundingBasis indicator (core)

* feat(derivatives): OpenInterestDelta indicator (core)

* feat(derivatives): Python, Node and WASM bindings for funding & OI-delta indicators

* test(derivatives): Python and Node tests for funding & OI-delta indicators

* bench(derivatives): synthetic-tick bench + derivatives fuzz target

* docs(derivatives): README family row + counter 232->237, CHANGELOG entry
This commit is contained in:
kingchenc
2026-06-01 21:26:37 +02:00
committed by GitHub
parent fae60e0d54
commit 5eb820a9c7
24 changed files with 2317 additions and 32 deletions
+65 -8
View File
@@ -33,14 +33,15 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Through
use std::hint::black_box;
use wickra::{
Adx, Atr, Autocorrelation, BatchExt, BollingerBands, BollingerOutput, CalmarRatio, Candle, Cci,
ClassicPivots, ConnorsRsi, DepthSlope, EffectiveSpread, Ema, EmpiricalModeDecomposition,
Engulfing, Frama, HilbertDominantCycle, HurstExponent, Ichimoku, IchimokuOutput, Indicator,
Jma, KylesLambda, Level, LinearRegression, MacdIndicator, MacdOutput, Mama, MamaOutput,
MaxDrawdown, Microprice, Obv, OrderBook, OrderBookImbalanceFull, OrderBookImbalanceTop1,
ParkinsonVolatility, Ppo, Psar, RollingVwap, Rsi, SharpeRatio, Side, SignedVolume, Sma, Stc,
SuperTrend, SuperTrendOutput, TdSequential, TdSequentialOutput, Trade, TradeImbalance,
TradeQuote, TtmSqueeze, TtmSqueezeOutput, ValueArea, ValueAreaOutput, ValueAtRisk, Vwap,
VwapStdDevBands, VwapStdDevBandsOutput, WaveTrend, YangZhangVolatility, T3,
ClassicPivots, ConnorsRsi, DepthSlope, DerivativesTick, EffectiveSpread, Ema,
EmpiricalModeDecomposition, Engulfing, Frama, FundingRate, FundingRateZScore,
HilbertDominantCycle, HurstExponent, Ichimoku, IchimokuOutput, Indicator, Jma, KylesLambda,
Level, LinearRegression, MacdIndicator, MacdOutput, Mama, MamaOutput, MaxDrawdown, Microprice,
Obv, OrderBook, OrderBookImbalanceFull, OrderBookImbalanceTop1, ParkinsonVolatility, Ppo, Psar,
RollingVwap, Rsi, SharpeRatio, Side, SignedVolume, Sma, Stc, SuperTrend, SuperTrendOutput,
TdSequential, TdSequentialOutput, Trade, TradeImbalance, TradeQuote, TtmSqueeze,
TtmSqueezeOutput, ValueArea, ValueAreaOutput, ValueAtRisk, Vwap, VwapStdDevBands,
VwapStdDevBandsOutput, WaveTrend, YangZhangVolatility, T3,
};
use wickra_data::csv::CandleReader;
@@ -181,6 +182,32 @@ where
group.finish();
}
fn bench_derivatives_input<I, F, O>(
c: &mut Criterion,
name: &str,
ticks: &[DerivativesTick],
make: F,
) where
F: Fn() -> I,
I: Indicator<Input = DerivativesTick, Output = O>,
{
let mut group = c.benchmark_group(name);
for &n in SIZES {
let n = n.min(ticks.len());
let series = &ticks[..n];
group.throughput(Throughput::Elements(n as u64));
group.bench_with_input(BenchmarkId::new("streaming", n), series, |b, ticks| {
b.iter(|| {
let mut ind = make();
for tick in ticks {
black_box(ind.update(*tick));
}
});
});
}
group.finish();
}
fn bench_scalar_multi<I, F, O>(c: &mut Criterion, name: &str, prices: &[f64], make: F)
where
F: Fn() -> I,
@@ -384,6 +411,36 @@ fn benches(c: &mut Criterion) {
.collect();
bench_tradequote_input(c, "effective_spread", &quotes, EffectiveSpread::new);
bench_tradequote_input(c, "kyles_lambda", &quotes, || KylesLambda::new(50).unwrap());
// === Family — Derivatives ===
// No derivatives feed ships with the repo, so synthesise a tick per candle:
// the close drives the mark price, funding tracks the candle's body, and
// open interest follows volume. FundingRate is the cheapest (passthrough);
// FundingRateZScore carries a rolling window and is the most expensive.
let ticks: Vec<DerivativesTick> = candles
.iter()
.map(|candle| {
let funding = (candle.close - candle.open) / candle.open * 0.01;
DerivativesTick::new_unchecked(
funding,
candle.close,
candle.close * 0.999,
candle.close * 1.001,
candle.volume * 100.0,
candle.volume * 0.6,
candle.volume * 0.4,
candle.volume * 0.5,
candle.volume * 0.5,
0.0,
0.0,
candle.timestamp,
)
})
.collect();
bench_derivatives_input(c, "funding_rate", &ticks, FundingRate::new);
bench_derivatives_input(c, "funding_rate_zscore", &ticks, || {
FundingRateZScore::new(50).unwrap()
});
}
criterion_group!(name = wickra_benches; config = Criterion::default(); targets = benches);