Williams %R (WILLR)

Parameters: period = 14

Overview

Larry Williams developed the %R indicator in 1973 as a momentum oscillator that reveals where the current close sits within the recent trading range, inverting the stochastic oscillator concept to run from 0 to -100 rather than 0 to 100. The indicator calculates how far the current close is from the highest high over the lookback period, expressing this as a percentage of the total range between the highest high and lowest low. Williams designed %R to identify overbought conditions when readings approach zero, indicating prices are near the top of their recent range, and oversold conditions when readings approach -100, showing prices near the bottom. His indicator gained credibility through Williams' own trading success, including his legendary 1987 World Cup Championship performance where he turned $10,000 into over $1.1 million in twelve months. The %R excels at spotting potential reversal points because it responds quickly to price changes while maintaining enough smoothness to filter out minor fluctuations. Williams emphasized watching for failures of %R to reach extreme levels during trends, as these failures often signal weakening momentum before price reversals become apparent, making the indicator particularly effective for timing market turns when combined with price pattern analysis.

Implementation Examples

Compute Williams' %R from slices or candles:

use vectorta::indicators::willr::{willr, WillrInput, WillrParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using with high/low/close slices
let high = vec![1.0, 2.0, 3.0, 4.0];
let low = vec![0.5, 1.5, 2.5, 3.5];
let close = vec![0.75, 1.75, 2.75, 3.75];
let params = WillrParams { period: Some(14) }; // Default is 14
let input = WillrInput::from_slices(&high, &low, &close, params);
let result = willr(&input)?;

// Using with Candles (defaults: period=14; sources high/low/close)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = WillrInput::with_default_candles(&candles);
let result = willr(&input)?;

// Access values
for value in result.values {
    println!("%R: {}", value);
}

API Reference

Input Methods
// From high/low/close slices
WillrInput::from_slices(&[f64], &[f64], &[f64], WillrParams) -> WillrInput

// From candles (uses high/low/close sources)
WillrInput::from_candles(&Candles, WillrParams) -> WillrInput

// With default parameters (period=14)
WillrInput::with_default_candles(&Candles) -> WillrInput
Parameters Structure
#[derive(Debug, Clone)]
pub struct WillrParams {
    pub period: Option<usize>, // Default: 14
}
Output Structure
#[derive(Debug, Clone)]
pub struct WillrOutput {
    pub values: Vec<f64>, // %R values (-100..0). Warmup indices are NaN.
}
Validation, Warmup & NaNs
  • period > 0 and period ≤ len; otherwise WillrError::InvalidPeriod.
  • Finds first index with finite H/L/C; requires at least period valid points after that or WillrError::NotEnoughValidData.
  • Warmup region (before first_valid + period - 1) is filled with NaN.
  • If a window’s high == low, the value is 0.0; any NaN in the window yields NaN.
Error Handling
use vectorta::indicators::willr::{willr, WillrError, WillrInput, WillrParams};

let input = WillrInput::from_slices(&high, &low, &close, WillrParams { period: Some(14) });
match willr(&input) {
    Ok(out) => process(out.values),
    Err(WillrError::AllValuesNaN) => eprintln!("All input values are NaN"),
    Err(WillrError::InvalidPeriod { period, data_len }) => eprintln!("Invalid period: {} (len={})", period, data_len),
    Err(WillrError::NotEnoughValidData { needed, valid }) => eprintln!("Not enough valid data: needed={}, valid={}", needed, valid),
    Err(WillrError::EmptyOrMismatched) => eprintln!("Empty or mismatched input slices"),
    Err(WillrError::OutputLenMismatch { .. }) => unreachable!(),
}

Python Bindings

Basic Usage

Calculate Williams' %R using NumPy arrays (period default 14):

import numpy as np
from vectorta import willr

# Prepare H/L/C arrays as float64
high = np.array([1.0, 2.0, 3.0, 4.0], dtype=np.float64)
low = np.array([0.5, 1.5, 2.5, 3.5], dtype=np.float64)
close = np.array([0.75, 1.75, 2.75, 3.75], dtype=np.float64)

# With explicit period and optional kernel ("auto", "scalar", "avx2", ...)
values = willr(high, low, close, period=14, kernel="auto")
print(values)
Streaming Real-time Updates
from vectorta import WillrStream

stream = WillrStream(period=14)
for h, l, c in hlc_feed:
    wr = stream.update(h, l, c)
    if wr is not None:
        handle(wr)
Batch Parameter Sweep
import numpy as np
from vectorta import willr_batch

high = np.array([...], dtype=np.float64)
low = np.array([...], dtype=np.float64)
close = np.array([...], dtype=np.float64)

res = willr_batch(high, low, close, period_range=(5, 30, 5), kernel="auto")
values = res["values"]          # shape: (num_periods, len)
periods = res["periods"]        # list of periods used
print(values.shape, periods)
CUDA Acceleration

GPU-accelerated batch (device arrays, f32 inputs):

import numpy as np
from vectorta import willr_cuda_batch_dev

high = np.array([...], dtype=np.float32)
low = np.array([...], dtype=np.float32)
close = np.array([...], dtype=np.float32)

gpu_out = willr_cuda_batch_dev(high, low, close, period_range=(5, 30, 1), device_id=0)
# gpu_out is a device array wrapper; convert/copy as needed in your environment

JavaScript/WASM Bindings

Basic Usage

Compute Williams' %R in JS/TS:

import { willr_js } from 'vectorta-wasm';

const high = new Float64Array([/* ... */]);
const low = new Float64Array([/* ... */]);
const close = new Float64Array([/* ... */]);

const values = willr_js(high, low, close, 14);
console.log('%R:', values);
Memory-Efficient Operations

Zero-copy into WASM memory for large arrays:

import { willr_alloc, willr_free, willr_into, memory } from 'vectorta-wasm';

const len = close.length;
const hPtr = willr_alloc(len);
const lPtr = willr_alloc(len);
const cPtr = willr_alloc(len);
const outPtr = willr_alloc(len);

new Float64Array(memory.buffer, hPtr, len).set(high);
new Float64Array(memory.buffer, lPtr, len).set(low);
new Float64Array(memory.buffer, cPtr, len).set(close);

// Compute in-place: (high_ptr, low_ptr, close_ptr, out_ptr, len, period)
willr_into(hPtr, lPtr, cPtr, outPtr, len, 14);

const out = new Float64Array(memory.buffer, outPtr, len).slice();

willr_free(hPtr, len); willr_free(lPtr, len);
willr_free(cPtr, len); willr_free(outPtr, len);
console.log(out);
Batch Processing

Sweep periods and get full result matrix:

import { willr_batch } from 'vectorta-wasm';

const cfg = { period_range: [5, 30, 5] };
const out = willr_batch(high, low, close, cfg);

// out: { values: Float64Array, combos: { period?: number }[], rows: number, cols: number }
console.log(out.rows, out.cols, out.combos);

Performance Analysis

Comparison:
View:

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

Loading chart...

AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05

Related Indicators