Didi Index
short_length = 3 | medium_length = 8 | long_length = 20 Overview
Didi Index is a moving-average relationship study that uses three simple averages instead of one. A short SMA and a long SMA are both divided by a shared medium SMA, producing two normalized lines that oscillate around the medium trend baseline. That normalization keeps the lines comparable across different price scales while still preserving their relative separation.
The main interpretation comes from the relationship between those two normalized lines. When the normalized short line rises through the normalized long line, the indicator marks a crossover event. When it falls back under the normalized long line, it marks a crossunder event. The medium length therefore acts as the shared anchor, while the short and long lengths determine how quickly those event markers can appear.
Defaults: Didi Index uses `short_length = 3`, `medium_length = 8`, and `long_length = 20`.
Implementation Examples
Compute the normalized short and long lines plus their event markers from a slice or a candle source field.
use vector_ta::indicators::didi_index::{
didi_index,
DidiIndexInput,
DidiIndexParams,
};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};
let output = didi_index(&DidiIndexInput::from_slice(
&close,
DidiIndexParams::default(),
))?;
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let candle_output = didi_index(&DidiIndexInput::with_default_candles(&candles))?;
println!("short = {:?}", output.short.last());
println!("long = {:?}", output.long.last());
println!("crossover = {:?}", candle_output.crossover.last()); API Reference
Input Methods ▼
// From candles and a named source field
DidiIndexInput::from_candles(&Candles, &str, DidiIndexParams)
-> DidiIndexInput
// From a slice
DidiIndexInput::from_slice(&[f64], DidiIndexParams)
-> DidiIndexInput
// From candles with default parameters
DidiIndexInput::with_default_candles(&Candles)
-> DidiIndexInput Parameters Structure ▼
pub struct DidiIndexParams {
pub short_length: Option<usize>, // default 3
pub medium_length: Option<usize>, // default 8
pub long_length: Option<usize>, // default 20
} Output Structure ▼
pub struct DidiIndexOutput {
pub short: Vec<f64>,
pub long: Vec<f64>,
pub crossover: Vec<f64>,
pub crossunder: Vec<f64>,
} Validation, Warmup & NaNs ▼
- The source slice must be non-empty and contain at least one finite value.
short_length,medium_length, andlong_lengthmust each be greater than zero and no larger than the data length.- The indicator needs at least
max(short_length, medium_length, long_length)valid values before the normalized lines can become finite. - Warmup is exposed by the stream as
max(short_length, medium_length, long_length) - 1. - If the shared medium SMA becomes zero or non-finite, the normalized lines and event flags are emitted as
NaNplaceholders. - Streaming resets on invalid source values and resumes only after the moving-average windows refill.
- Batch mode rejects invalid integer ranges and non-batch kernels.
Builder, Streaming & Batch APIs ▼
// Builder
DidiIndexBuilder::new()
.short_length(usize)
.medium_length(usize)
.long_length(usize)
.kernel(Kernel)
.apply_slice(&[f64])
DidiIndexBuilder::new()
.apply(&Candles, &str)
DidiIndexBuilder::new()
.into_stream()
// Stream
DidiIndexStream::try_new(DidiIndexParams)
DidiIndexStream::update(f64) -> Option<(f64, f64, f64, f64)>
DidiIndexStream::get_warmup_period() -> usize
// Batch
DidiIndexBatchBuilder::new()
.short_length_range((start, end, step))
.medium_length_range((start, end, step))
.long_length_range((start, end, step))
.kernel(Kernel)
.apply_slice(&[f64]) Error Handling ▼
pub enum DidiIndexError {
EmptyInputData,
AllValuesNaN,
InvalidShortLength { short_length: usize, data_len: usize },
InvalidMediumLength { medium_length: usize, data_len: usize },
InvalidLongLength { long_length: usize, data_len: usize },
NotEnoughValidData { needed: usize, valid: usize },
OutputLengthMismatch {
expected: usize,
short_got: usize,
long_got: usize,
crossover_got: usize,
crossunder_got: usize,
},
InvalidRange { start: String, end: String, step: String },
InvalidKernelForBatch(Kernel),
} Python Bindings
Python exposes a tuple-returning single-run function, a streaming class, and a batch function. The single-run binding returns four NumPy arrays in order: short, long, crossover, and crossunder. Batch returns those same matrices plus the tested SMA lengths and the final rows and cols shape.
import numpy as np
from vector_ta import (
didi_index,
didi_index_batch,
DidiIndexStream,
)
data = np.asarray(close_values, dtype=np.float64)
short, long, crossover, crossunder = didi_index(
data,
short_length=3,
medium_length=8,
long_length=20,
kernel="auto",
)
stream = DidiIndexStream(short_length=3, medium_length=8, long_length=20)
print(stream.warmup_period)
print(stream.update(data[-1]))
batch = didi_index_batch(
data,
short_length_range=(3, 5, 2),
medium_length_range=(8, 10, 2),
long_length_range=(20, 24, 4),
kernel="auto",
)
print(batch["short_lengths"], batch["rows"], batch["cols"]) JavaScript/WASM Bindings
The WASM layer exposes an object-returning single-run wrapper, a batch wrapper with explicit range config, and lower-level allocation and in-place exports. The standard JavaScript path returns typed arrays for all four outputs. The batch wrapper adds combo objects, tested length axes, and the final rows and cols shape.
import init, {
didi_index_js,
didi_index_batch_js,
} from "/pkg/vector_ta.js";
await init();
const data = new Float64Array(closeValues);
const result = didi_index_js(data, 3, 8, 20);
console.log(result.short, result.crossover);
const batch = didi_index_batch_js(data, {
short_length_range: [3, 5, 2],
medium_length_range: [8, 10, 2],
long_length_range: [20, 24, 4],
});
console.log(batch.short_lengths, batch.medium_lengths, batch.rows, batch.cols); CUDA Bindings (Rust)
Additional details for the CUDA bindings can be found inside the VectorTA repository.
Performance Analysis
Across sizes, Rust CPU runs about 1.14× faster than Tulip C in this benchmark.
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU)