Didi Index

Parameters: 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, and long_length must 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 NaN placeholders.
  • 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

Comparison:
View:
Placeholder data (no recorded benchmarks for this indicator)

Across sizes, Rust CPU runs about 1.14× faster than Tulip C in this benchmark.

Loading chart...

AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU)

Related Indicators