Quant Charts ATR Indicator Example (Python and Rust)

Average True Range in Quant Charts. The Python version runs on OHLC bars via ta.atr; the Rust version computes ATR on TBBO ticks. Both are complete, runnable files.

Python (OHLC): atr.py

# qc-api: 1.0.7
# DISCLAIMER: This software is for educational and informational purposes only and does not constitute
# financial advice, investment advice, or trading advice. Past performance is not indicative of future
# results. Trading futures and other financial instruments involves substantial risk of loss. You are
# solely responsible for your own trading decisions. Quant Charts LLC assumes no liability for any
# losses incurred. All rights reserved. (c) Quant Charts LLC

"""ATR: Average True Range with regime tags (Python / OHLC).

Required columns: high, low, close.
Data mode: OHLC.

Baseline indicator. Plots ATR as a line and emits three regime tags
(low_atr / medium_atr / high_atr) defined relative to the day's median ATR.
Use these tags as filter / record sources on any strategy.

Default thresholds (multipliers of the day's median ATR):
  low_atr    : ATR < 0.7 * median
  medium_atr : 0.7 * median <= ATR <= 1.4 * median
  high_atr   : ATR > 1.4 * median
"""

import numpy as np
from quant_charts import indicator, input, plot, define_tag, ta


@indicator(
    name="ATR",
    description="ATR line + low/medium/high regime tags relative to day's median",
    overlay=False,
    data_mode="ohlc",
    required_columns=["high", "low", "close"],
)
class Atr:
    period = input.int(14, "ATR Period", min=2, max=500,
                       tooltip="ATR lookback in bars.")
    low_mult = input.float(0.7, "Low Threshold (x median)", min=0.0, max=10.0, step=0.05,
                           tooltip="ATR below this many medians = low_atr.")
    high_mult = input.float(1.4, "High Threshold (x median)", min=0.0, max=10.0, step=0.05,
                            tooltip="ATR above this many medians = high_atr.")
    line_color = input.color("#E0AF68", "Line Color")

    def calculate(self, df):
        high_arr = np.asarray(df["high"], dtype=np.float64)
        low_arr = np.asarray(df["low"], dtype=np.float64)
        close_arr = np.asarray(df["close"], dtype=np.float64)

        atr = ta.atr(high_arr, low_arr, close_arr, self.period)

        finite = atr[np.isfinite(atr)]
        median_atr = float(np.median(finite)) if finite.size else float("nan")
        low_thresh = median_atr * self.low_mult if np.isfinite(median_atr) else float("nan")
        high_thresh = median_atr * self.high_mult if np.isfinite(median_atr) else float("nan")

        plot(atr, "ATR", color=self.line_color, linewidth=2)

        finite_atr = np.isfinite(atr)
        low_atr = (atr < low_thresh) & finite_atr
        high_atr = (atr > high_thresh) & finite_atr
        medium_atr = finite_atr & ~low_atr & ~high_atr

        define_tag("low_atr", f"ATR(<{self.low_mult:.2f}x median)", color="#7AA2F7")
        define_tag("medium_atr", f"{self.low_mult:.2f}x .. {self.high_mult:.2f}x median", color="#A1A1AA")
        define_tag("high_atr", f"ATR(>{self.high_mult:.2f}x median)", color="#F7768E")

        return {
            "low_atr": low_atr,
            "medium_atr": medium_atr,
            "high_atr": high_atr,
        }

Rust (TBBO): atr.rs

//! qc-api: 1.0.7
// DISCLAIMER: This software is for educational and informational purposes only and does not constitute
// financial advice, investment advice, or trading advice. Past performance is not indicative of future
// results. Trading futures and other financial instruments involves substantial risk of loss. You are
// solely responsible for your own trading decisions. Quant Charts LLC assumes no liability for any
// losses incurred. All rights reserved. (c) Quant Charts LLC

//! ATR (Rust / OHLC bars).
//!
//! Required columns: high, low, close. Data mode: OHLC bars.
//!
//! Wilder ATR over `period` bars. The chart timeframe pill picks the bar
//! cadence (1m, 5m, ...). Three regime tags fire based on ATR vs the day's
//! median bar-ATR. Renders only in OHLC chart views.

use qc_strategy_api::prelude::*;

#[indicator(
    name = "ATR (Rust)",
    description = "Wilder ATR with low/medium/high regime tags. OHLC-mode.",
    overlay = false,
    data_mode = "ohlc",
    timeframe = "1m"
)]
#[tag(name = "low_atr",    label = "Low ATR",    color = "#7AA2F7", description = "Bar ATR below low_mult x median")]
#[tag(name = "medium_atr", label = "Medium ATR", color = "#A1A1AA", description = "Bar ATR between low_mult and high_mult x median")]
#[tag(name = "high_atr",   label = "High ATR",   color = "#F7768E", description = "Bar ATR above high_mult x median")]
#[derive(Default)]
pub struct Atr {
    #[param(default = 14, min = 2, max = 500, label = "ATR Period (bars)",
            tooltip = "ATR lookback in bars at the indicator's declared timeframe.")]
    pub period: usize,

    #[param(default = 0.7, min = 0.0, max = 10.0, step = 0.05, label = "Low Threshold (x median)",
            tooltip = "Bar ATR below this many medians = low_atr.")]
    pub low_mult: f64,

    #[param(default = 1.4, min = 0.0, max = 10.0, step = 0.05, label = "High Threshold (x median)",
            tooltip = "Bar ATR above this many medians = high_atr.")]
    pub high_mult: f64,
}

impl OhlcIndicator for Atr {
    fn calculate(&self, data: &BarData, _prep: &DayPrep) -> IndicatorOutput {
        let n = data.len();
        if n == 0 || self.period < 2 {
            return IndicatorOutput::new().with_overlay(false);
        }

        // Wilder ATR over bar high/low/close.
        let mut atr = vec![f64::NAN; n];
        if n > self.period {
            let mut tr = vec![0.0; n];
            tr[0] = (data.high[0] - data.low[0]).max(0.0);
            for i in 1..n {
                let h = data.high[i];
                let l = data.low[i];
                let pc = data.close[i - 1];
                tr[i] = (h - l).max((h - pc).abs()).max((l - pc).abs());
            }
            let p = self.period as f64;
            let seed: f64 = tr[..self.period].iter().sum::<f64>() / p;
            atr[self.period - 1] = seed;
            for i in self.period..n {
                atr[i] = (atr[i - 1] * (p - 1.0) + tr[i]) / p;
            }
        }

        // Regime thresholds from the day's median bar ATR.
        let mut finite: Vec<f64> = atr.iter().copied().filter(|v| v.is_finite()).collect();
        let median_atr = if finite.is_empty() {
            f64::NAN
        } else {
            finite.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
            finite[finite.len() / 2]
        };
        let low_thresh = median_atr * self.low_mult;
        let high_thresh = median_atr * self.high_mult;

        let mut low_atr = vec![false; n];
        let mut medium_atr = vec![false; n];
        let mut high_atr = vec![false; n];
        for i in 0..n {
            let v = atr[i];
            if !v.is_finite() {
                continue;
            }
            if v < low_thresh {
                low_atr[i] = true;
            } else if v > high_thresh {
                high_atr[i] = true;
            } else {
                medium_atr[i] = true;
            }
        }

        IndicatorOutput::new()
            .with_overlay(false)
            .plot_line("ATR", atr, "#E0AF68")
            .with_tag("low_atr", low_atr)
            .with_tag("medium_atr", medium_atr)
            .with_tag("high_atr", high_atr)
    }
}
Machine-readable: full API at /llms-full.txt · examples on GitHub.