Indicator · Pine Script v6 · Own pane

Simplified Percentile Clustering

by Nguyenthang2292· code credited to InvestorUnknown

An indicator that combines several normalised oscillators, namely RSI, CCI, Fisher, DMI, Z-Score and a moving-average ratio, into one composite, then groups values into clusters over a rolling lookback.

View on GitHub

GitHub stars1on the repository
Pine versionv6current
Lines of code453
Settings24inputs
LicenceMIT
Last update6 months ago2 May 2026

Add It to Your TradingView Chart

  1. 1Copy the scriptPress Copy script above, or the copy button on the code below.
  2. 2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
  3. 3Paste over the templateCreate a new indicator, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart, then adjust the settings with the gear icon next to its name.

Source Code

453 lines of Pine Script v6 from modules/simplified_percentile_clustering/source_pine.txt, published under MIT. Keep the author's credit and licence lines when you reuse it.

source_pine.txt
// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © InvestorUnknown
//
//                                                                                  {||}                   
//                                                       ,                          {||}          
//                                                  ,,,,,                           {||}
//                                                ,,,,,       ,       ,,            {||}       
//                                    ,         ,,,, ,       ,,     ,,,             {||}       
//             .                   , ,         ,,,,  ,     ,,,,   .,,               {||}            ╔╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╗
//             ,,                 ,       ,,   ,,,,,,,  ,  ,      ,                 {||}            ╠╬╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╬╣  
//             ,,                 ,,   ,  ,,  ,,,,,, ,,,,    , ,                    {||}            ╠╣  /$$$$$$                                           /$$                         ╠╣
//              .,         ,      ,,,  ,,,,,,,,,,,,,, ,,  ,,  , ,         ,,        {||}            ╠╣ |_  $$_/                                          | $$                         ╠╣
//                           ,  .  ,, ,,,,,,,,,,,,, ,    ,,, , ,,    ,   ,          {||}            ╠╣   | $$   /$$$$$$$  /$$    /$$ /$$$$$$   /$$$$$$$ /$$$$$$    /$$$$$$   /$$$$$$  ╠╣
//                   ,,           ,,, ,,,,,,,,,,,,,,,,,,,,,,  ,,,   ,,              {||}            ╠╣   | $$  | $$__  $$|  $$  /$$//$$__  $$ /$$_____/|_  $$_/   /$$__  $$ /$$__  $$ ╠╣
//               , ,   ,,,     .,,,,,,,,,,,, ,,,  ,,,,,,,,   ,,,    ,,              {||}            ╠╣   | $$  | $$  \ $$ \  $$/$$/| $$$$$$$$|  $$$$$$   | $$    | $$  \ $$| $$  \__/ ╠╣      
//         .,     , ,,  ,,    ,,, ,,,,,,, ,,  ,,, ,,,,, ,,, ,  ,,   ,,              {||}            ╠╣   | $$  | $$  | $$  \  $$$/ | $$_____/ \____  $$  | $$ /$$| $$  | $$| $$       ╠╣     
//            ,   ,,,,,  ,    ,,,, ,, , ,,,,,,,,,,,,,,,,,,,,,, ,,  ,,               {||}            ╠╣  /$$$$$$| $$  | $$   \  $/  |  $$$$$$$ /$$$$$$$/  |  $$$$/|  $$$$$$/| $$       ╠╣   
//               .    //./ /// ,,,,,,,,,,,,,,,. ,,,,,,,,,,,,,,,,,,                  {||}            ╠╣ |______/|__/  |__/    \_/    \_______/|_______/    \___/   \______/ |__/       ╠╣
//                ,  /         ,., ,,,,,,,,,,, ,,,,,,,   ,,,,,,,                    {||}            ╠╣                                                                                ╠╣
//            .  ,,,  ,/ ///./   ,,,.,,,,,,,,,,,,,,,      ,, , ,                    {||}            ╠╣                                                                                ╠╣
//             ,,,,,,  //./ , /    .,,.,,, ,,,,,, ,.     ,,,,,,,                    {||}            ╠╣                                                                                ╠╣
//              ,,,,   //  *, / / ,,,,,,,,,,,,          ,, ,,,,,                    {||}            ╠╣    /$$   /$$           /$$                                                     ╠╣
//               ,,  // ////.*/// / ,.,,,,,.,, ,,  ,,,, ,,,,,,                      {||}            ╠╣   | $$  | $$          | $$                                                     ╠╣
//                   ,  /////    //  , ,,,,,, ,,,, ,,,,,  ,,, / /.                  {||}            ╠╣   | $$  | $$ /$$$$$$$ | $$   /$$ /$$$$$$$   /$$$$$$  /$$  /$$  /$$ /$$$$$$$    ╠╣
//              ,,   ,         ////// ,,,,,,,,,  ,,,,,,,,/ ///  / //                {||}            ╠╣   | $$  | $$| $$__  $$| $$  /$$/| $$__  $$ /$$__  $$| $$ | $$ | $$| $$__  $$   ╠╣
//                         ///// .// ,,,,,,  ,, ,,,, ,,, ///*  //*///               {||}            ╠╣   | $$  | $$| $$  \ $$| $$$$$$/ | $$  \ $$| $$  \ $$| $$ | $$ | $$| $$  \ $$   ╠╣
//                           //  .           ,, .// ,,      ///, ///                {||}            ╠╣   | $$  | $$| $$  | $$| $$_  $$ | $$  | $$| $$  | $$| $$ | $$ | $$| $$  | $$   ╠╣
//                        //////        ,,,,    ///// ,.        ,                   {||}            ╠╣   |  $$$$$$/| $$  | $$| $$ \  $$| $$  | $$|  $$$$$$/|  $$$$$/$$$$/| $$  | $$   ╠╣
//                   *///////. //              /  */////*                           {||}            ╠╣    \______/ |__/  |__/|__/  \__/|__/  |__/ \______/  \_____/\___/ |__/  |__/   ╠╣ 
//                         .,,  // ,,,,,,,,,, //* ,,,  //////                       {||}            ╠╬╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╦╬╣
//                           ,,,,,   ,,,,,, ,.,,,,,,,                               {||}            ╚╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╩╝
//                               ,,,,,,,,,,,, ,,                                    {||}          
//                                  ,,,,,,,,,                                       {||}                                                                                                                                  
//                                                                                  {||} 
//                                                                                  {||} 
//
// Indicator Overview
// ------------------
//   A compact, streaming-friendly clustering heuristic intended for trend analysis.
//   It computes simple "cluster centers" per feature using percentiles and the
//   running mean, then assigns each bar to the closest center. Optionally, in
//   "Clusters" (combined) mode it fuses distances across multiple features to
//   compute a multi-dimensional proximity / cluster assignment.
//
// Key design decisions:
//   - K limited to 2 or 3 for stability and interpretability.
//   - Uses percentiles (lower/upper) + running mean to form deterministic centers
//     — a light-weight alternative to iterative clustering (k-means) that is
//     streaming-friendly and cheap to compute.
//   - Allows selective feature fusion (binary on/off) for combined distance.
//   - Produces a `real_clust` value that smoothly interpolates between centers
//     (useful for visualizing 'proximity-to-flip').
//
// Notes:
//   - This is NOT k-means. It's a percentile + mean center heuristic designed to
//     favor stability and low compute on live series.
//   - Good for feature engineering and visual regime detection. If you need
//     centroid updates based on iterative assignment, consider a k-means
//     adaptation (outside the scope of this simplified heuristic).
//
// ----------------------------------------------------------------------------
//@version=6
indicator("Simplified Percentile Clustering", "SPC", overlay = false, max_lines_count=500, max_labels_count=500)

//-#-#-#-#-#- STRINGS -#-#-#-#-#- {
var string g1           = "Feature Settings"
var string g2           = "Clustering Settings"
var string g3           = "Plot Settings"

var string k_tip        = "Number of cluster centers to derive per feature. Each cluster represents a distinct regime.
 Limiting K to 2 or 3 ensures computational stability and interpretability. \n
 Typical values:\n • 2 → Binary states (e.g., Long vs. Short)\n • 3 → Adds a neutral/mid cluster."
var string lookback_tip = "Number of historical bars used for percentile and mean calculations. Controls how far back the algorithm looks when
 estimating cluster centers. Higher values = smoother, more stable clusters; Lower values = faster response to regime changes."
var string p_low_tip    = "Defines the lower boundary of the distribution used to determine
 the lowest cluster center. Increase to make clusters less sensitive to extreme low values."
var string p_high_tip   = "Defines the upper boundary of the distribution used to determine
 the highest cluster center. Decrease to make clusters less sensitive to extreme high values."
var string plot_tip     = "Select which feature or output to visualize:\n
 • Clusters → Combined multi-feature fusion mode showing overall regime proximity.\n
 • RSI, CCI, Fisher, DMI, Z-Score, MAR → Single-feature clustering view.\n
 Use 'Clusters' mode to see how multiple standardized features collectively define regime transitions."
//}

//-#-#-#-#-#- INPUTS -#-#-#-#-#- {
// For each feature the user can enable/disable it and choose whether to
// standardize it using a rolling Z-score (based on `lookback`). Standardizing
// makes features comparable when fused.
use_rsi = input.bool(true, "Use?", group = g1, inline = "L1"), rsi_len = input.int(14, "RSI Length",     group = g1, inline = "L1"), rsi_t = input.bool(true, "Standardize", group = g1, inline = "L1")
use_cci = input.bool(true, "Use?", group = g1, inline = "L2"), cci_len = input.int(20, "CCI Length",     group = g1, inline = "L2"), cci_t = input.bool(true, "Standardize", group = g1, inline = "L2")
use_fis = input.bool(true, "Use?", group = g1, inline = "L3"), fis_len = input.int(9, "Fisher Length",   group = g1, inline = "L3"), fis_t = input.bool(true, "Standardize", group = g1, inline = "L3")
use_dmi = input.bool(true, "Use?", group = g1, inline = "L4"), dmi_len = input.int(9, "DMI Length",      group = g1, inline = "L4"), dmi_t = input.bool(true, "Standardize", group = g1, inline = "L4")
use_zsc = input.bool(true, "Use?", group = g1, inline = "L5"), zsc_len = input.int(20, "Z-Score Length", group = g1, inline = "L5")
use_mar = input.bool(true, "Use?", group = g1, inline = "L6"), mar_len = input.int(14, "MA Length",      group = g1, inline = "L6"), 
 ma_type = input.string("SMA", "", options = ["SMA", "EMA"], group = g1, inline = "L6"), mar_t = input.bool(true, "Standardize", group = g1, inline = "L6")

// --- Clustering parameters ---
// k: number of cluster centers (2 or 3). Lookback: how many historical values are
// kept for percentile/median calculations. Percentiles determine lower/upper
// thresholds from which centers are derived.
simple int k        = input.int(2, "K Clusters", minval=2, maxval=3, group = g2, tooltip = k_tip)
simple int lookback = input.int(1000, "Lookback",                    group = g2, tooltip = lookback_tip)
simple float p_low  = input.float(5.0, "Lower Percentile",           group = g2, tooltip = p_low_tip)
simple float p_high = input.float(95.0, "Upper Percentile",          group = g2, tooltip = p_high_tip)

// --- Plot control ---
// main_plot: which series to visualize. "Clusters" activates the combined
// multi-indicator clustering visualization (the fusion behavior).
simple string main_plot = input.string("Clusters", "Main Plot", options = ["Clusters", "RSI", "CCI", "Fisher", "DMI", "Z-Score", "MAR"], group = g3, tooltip = plot_tip)
simple bool   plot_cl   = input.bool(false, "Plot Cluster Line", group = g3)
//}

//-#-#-#-#-#- HELPER FUNCTIONS -#-#-#-#-#- {

// z_score: rolling z-score standardization.
z_score(src, length) =>
    mean    = ta.sma(src, length)
    stdev   = ta.stdev(src, length)
    (src - mean) / stdev

// get_percentile: returns the value at `pct` percentile from a sorted array.
// Implementation note: the script stores the sliding values in an array, sorts a
// copy and then chooses index floor((n-1)*pct/100). This is a deterministic
// and inexpensive approximation when interpolation isn't required.
get_percentile(arr, pct) =>
    sz      = array.size(arr)
    if sz == 0
        na
    else
        idx = math.floor((sz - 1) * pct / 100)
        array.get(arr, idx)

// round_: safe clamp used by the fisher transform helper to avoid infinite
round_(val) => val > .99 ? .999 : val < -.99 ? -.999 : val

// fisher: fisher transform applied to hl2 over `len` bars.
fisher(len) =>
    high_   = ta.highest(hl2, len)
    low_    = ta.lowest(hl2, len)
    value   = 0.0
    value   := round_(.66 * ((hl2 - low_) / (high_ - low_) - .5) + .67 * nz(value[1]))
    fish1   = 0.0
    fish1   := .5 * math.log((1 + value) / (1 - value)) + .5 * nz(fish1[1])

// dmi_difference: simplified DMI difference that returns (plus - minus).
dmi_difference(len) =>
    up      = ta.change(high)
    down    = -ta.change(low)
    plusDM  = na(up) ? na : (up > down and up > 0 ? up : 0)
    minusDM = na(down) ? na : (down > up and down > 0 ? down : 0)
    trur    = ta.rma(ta.tr, len)
    plus    = fixnan(100 * ta.rma(plusDM, len) / trur)
    minus   = fixnan(100 * ta.rma(minusDM, len) / trur)
    diff    = plus - minus

// on_off: convert boolean to 1/0 for weighted averaging in the combined distance.
on_off(bool t_f) =>
    out     = t_f ? 1 : 0
//}

//-#-#-#-#-#- FEATURE CALCULATIONS -#-#-#-#-#- {
// For each feature we compute either the raw value or the standardized (z-score)
// variant depending on the user's toggle. When standardizing we use the same
// `lookback` as used for percentile center computation so the comparability is
// consistent across features.

rsi         = ta.rsi(close, rsi_len)
rsi_z       = z_score(rsi, lookback)
rsi_val     = rsi_t ? rsi_z : rsi

cci         = ta.cci(close, cci_len)
cci_z       = z_score(cci, lookback)
cci_val     = cci_t ? cci_z : cci

fisher      = fisher(fis_len)
fisher_z    = z_score(fisher, lookback)
fisher_val  = fis_t ? fisher_z : fisher

dmi         = dmi_difference(dmi_len)
dmi_z       = z_score(dmi, lookback)
dmi_val     = dmi_t ? dmi_z : dmi

zsc_val     = z_score(close, zsc_len)   // z-score of price itself (rolling)

ma          = switch ma_type
    "SMA" => ta.sma(close, mar_len)
    "EMA" => ta.ema(close, mar_len)
mar         = close / ma                // price normalized by moving average
mar_z       = z_score(mar, lookback)
mar_val     = mar_t ? mar_z : mar
//}

//-#-#-#-#-#- BUILD & SORT PER-FEATURE ARRAYS -#-#-#-#-#- {
// get_centers: builds a sliding array of historical values for a single
// feature, sorts it (copy), computes lower/upper percentiles and a center
// near the mean. Then derives k centers as:
//   k=2 -> [avg(low_pct, mean), avg(high_pct, mean)]
//   k=3 -> [avg(low_pct, mean), mean, avg(high_pct, mean)]
//
// Rationale:
//   - The lower percentile captures the 'lower basin' of the distribution,
//     the upper percentile the 'upper basin', and the mean approximates a
//     central tendency. Averaging them produces stable and interpretable centers
//     that do not rely on iterative centroid updates.
get_centers(x, lookback, p_high, p_low, k) =>
    var float[] x_arr = array.new_float()
    array.push(x_arr, x)

    // keep array within lookback window
    if array.size(x_arr) > lookback
        array.shift(x_arr)

    // work on a copy to preserve the primary array's insertion order
    x_sorted    = array.copy(x_arr), array.sort(x_sorted)

    x_high      = get_percentile(x_sorted, p_high)
    x_low       = get_percentile(x_sorted, p_low)
    x_mid       = ta.sma(x, lookback)

    x_k0_center = math.avg(x_low, x_mid)
    x_k1_center = math.avg(x_high, x_mid)

    x_centers   = switch k
        2 => array.from(x_k0_center, x_k1_center)
        3 => array.from(x_k0_center, x_mid, x_k1_center)

    x_centers

// compute centers for each feature
rsi_centers = get_centers(rsi_val, lookback, p_high, p_low, k)
cci_centers = get_centers(cci_val, lookback, p_high, p_low, k)
fis_centers = get_centers(fisher_val, lookback, p_high, p_low, k)
dmi_centers = get_centers(dmi_val, lookback, p_high, p_low, k)
zsc_centers = get_centers(zsc_val, lookback, p_high, p_low, k)
mar_centers = get_centers(mar_val, lookback, p_high, p_low, k)
//}

//-#-#-#-#-#- ASSIGN NEAREST CENTER -#-#-#-#-#- {
var string[] cluster_names = array.from("k0", "k1", "k2")

// Pick which feature / centers to visualize based on `main_plot`.
// If main_plot == "Clusters" we operate in combined (multi-feature fusion) mode.
// For single-feature modes, `src_val` is set to that feature value and `centers`
// to its corresponding centers.
float[] centers = na
float src_val   = na

if main_plot == "RSI"
    centers := rsi_centers
    src_val := rsi_val
else if main_plot == "CCI"
    centers := cci_centers
    src_val := cci_val
else if main_plot == "Fisher"
    centers := fis_centers
    src_val := fisher_val
else if main_plot == "DMI"
    centers := dmi_centers
    src_val := dmi_val
else if main_plot == "Z-Score"
    centers := zsc_centers
    src_val := zsc_val
else if main_plot == "MAR"
    centers := mar_centers
    src_val := mar_val
else  // "Clusters" = combined mode
    // base loop reference to make the loop boundaries come from one center array
    centers := rsi_centers
    src_val := na  // placeholder — combined mode computes distances from all features


// initialize distance trackers for picking the closest and second-closest centers
float min_dist          = na
float second_min_dist   = na
string curr_cluster     = na
string second_cluster   = na

// Core loop: compute distance from `src` to each candidate center. In single-
// feature modes we compute the simple absolute difference. In combined mode we
// compute average of feature-wise absolute diffs for selected features.
//
// Important: the combined distance is a fusion of normalized feature distances
// using binary (on/off) weighting derived from `use_*` toggles. This is simple
// and interpretable: chosen features equally influence the fused distance.
for i = 0 to array.size(centers) - 1
    float dist = na

    if main_plot == "RSI"
        dist := math.abs(rsi_val - array.get(rsi_centers, i))
    else if main_plot == "CCI"
        dist := math.abs(cci_val - array.get(cci_centers, i))
    else if main_plot == "Fisher"    
        dist := math.abs(fisher_val - array.get(fis_centers, i))
    else if main_plot == "DMI"
        dist := math.abs(dmi_val - array.get(dmi_centers, i))
    else if main_plot == "Z-Score"
        dist := math.abs(zsc_val - array.get(zsc_centers, i))
    else if main_plot == "MAR"
        dist := math.abs(mar_val - array.get(mar_centers, i))

    else // "Clusters" = combined RSI + CCI + ... distance fusion
        // compute per-feature absolute distances to that feature's center
        rsi_d = math.abs(rsi_val - array.get(rsi_centers, i))
        cci_d = math.abs(cci_val - array.get(cci_centers, i))
        fis_d = math.abs(fisher_val - array.get(fis_centers, i))
        dmi_d = math.abs(dmi_val - array.get(dmi_centers, i))
        zsc_d = math.abs(zsc_val - array.get(zsc_centers, i))
        mar_d = math.abs(mar_val - array.get(mar_centers, i))

        // fused distance: weighted average using on_off toggles as weights.
        // Denominator ensures proper averaging for the number of enabled features.
        dist := (rsi_d * on_off(use_rsi) + cci_d * on_off(use_cci) + fis_d * on_off(use_fis) + dmi_d * on_off(use_dmi) + zsc_d * on_off(use_zsc) + mar_d * on_off(use_mar)) / 
         (on_off(use_rsi) + on_off(use_cci) + on_off(use_fis) + on_off(use_dmi) + on_off(use_zsc) + on_off(use_mar))

    // Maintain the two smallest distances so we can compute relative proximity
    if na(min_dist) or dist < min_dist
        second_min_dist := min_dist
        second_cluster  := curr_cluster

        min_dist        := dist
        curr_cluster    := array.get(cluster_names, i)
    else if na(second_min_dist) or dist < second_min_dist
        second_min_dist := dist
        second_cluster  := array.get(cluster_names, i)


// numeric cluster index (0/1/2) for plotting discrete cluster bands
cluster_val     = curr_cluster == "k0" ? 0 : curr_cluster == "k1" ? 1 : curr_cluster == "k2" ? 2 : na

// Compute normalized proximity between the closest and second closest cluster:
// rel_pos = min_dist / (min_dist + second_min_dist)
// Interpretation:
//   - rel_pos → 0 : current point is very near the chosen (closest) center.
//   - rel_pos → 0.5 : equidistant between chosen and second center (ambiguous).
//   - rel_pos → 1 : nearer to second center than chosen.
// If second_min_dist == 0 we treat rel_pos as 0 to avoid division by zero.
rel_pos         = second_min_dist == 0 ? 0 : min_dist / (min_dist + second_min_dist)

// second cluster numeric index
second_val      = second_cluster == "k0" ? 0 : second_cluster == "k1" ? 1 : 2

// real_clust: a continuous interpolation between the two nearest cluster indices.
// This produces a visually smooth 'track' between centers and helps show how
// close the current point is to flipping cluster.
real_clust      = cluster_val + (second_val - cluster_val) * rel_pos
//}

//-#-#-#-#-#- PLOTTING & COLORS -#-#-#-#-#- {
// get_color: returns plotting color per cluster. Uses k to decide color layout.
// We choose muted transparencies for nice overlay visuals on charts.
get_color(curr_cluster, k) =>
    if k == 2
        curr_color = switch curr_cluster
            "k0" => color.new(color.red, 20)
            "k1" => color.new(color.green, 20)
            => color.new(color.gray, 80)
        curr_color
    else
        curr_color = switch curr_cluster
            "k0" => color.new(color.red, 20)
            "k1" => color.new(color.orange, 20)
            "k2" => color.new(color.green, 20)
            => color.new(color.gray, 80)
        curr_color

curr_color  = get_color(curr_cluster, k)

// plot_val: series to draw depending on main_plot mode. In "Clusters" mode we
// draw `real_clust` (the interpolated numeric representation). For single
// feature modes we draw the raw/standardized feature value itself.
plot_val    = switch main_plot
    "Clusters" => real_clust
    "RSI" => rsi_val
    "CCI" => cci_val
    "Fisher" => fisher_val
    "DMI" => dmi_val
    "Z-Score" => zsc_val
    "MAR" => mar_val
// For visualization we also expose the individual cluster centers as step-plots
// so the user can see how the center levels evolve over time.
plot_k0_center = switch main_plot
    "Clusters" => 0
    "RSI" => array.get(rsi_centers, 0)
    "CCI" => array.get(cci_centers, 0)
    "Fisher" => array.get(fis_centers, 0)
    "DMI" => array.get(dmi_centers, 0)
    "Z-Score" => array.get(zsc_centers, 0)
    "MAR" => array.get(mar_centers, 0)
plot_k1_center = switch main_plot
    "Clusters" => 1
    "RSI" => array.get(rsi_centers, 1)
    "CCI" => array.get(cci_centers, 1)
    "Fisher" => array.get(fis_centers, 1)
    "DMI" => array.get(dmi_centers, 1)
    "Z-Score" => array.get(zsc_centers, 1)
    "MAR" => array.get(mar_centers, 1)
plot_k2_center = switch main_plot
    "Clusters" => k > 2 ? 2 : 0
    "RSI" => k > 2 ? array.get(rsi_centers, 2) : na
    "CCI" => k > 2 ? array.get(cci_centers, 2) : na
    "Fisher" => k > 2 ? array.get(fis_centers, 2) : na
    "DMI" => k > 2 ? array.get(dmi_centers, 2) : na
    "Z-Score" => k > 2 ? array.get(zsc_centers, 2) : na
    "MAR" => k > 2 ? array.get(mar_centers, 2) : na

if na(plot_val)
    curr_color := color.new(color.gray, 80)
barcolor(curr_color)   // color bars according to cluster assignment

// guide lines for cluster rows (only for "Clusters" mode)
plot(not na(plot_val) and main_plot == "Clusters" ? 0.5 : na, color = color.new(color.gray, 70), title = "Guide Line 1")
plot(not na(plot_val) and main_plot == "Clusters" and k == 3 ? 1.5 : na, color = color.new(color.gray, 70), title = "Guide Line 2")

// Plot choices:
// - For single-feature modes: plot the feature (as circles for readability).
// - For "Clusters" we plot the interpolated `real_clust` and separate histograms
//   for each discrete cluster index to emphasize the categorical bands.
plot(main_plot == "Clusters" ? (plot_cl ? plot_val : na) : plot_val, color = color.new(curr_color, 50), title = "Clusers line")
plot(plot_val, color = curr_color, style = plot.style_circles, title = "Cluster Circles")
plot(main_plot == "Clusters" and cluster_val == 0 ? plot_val :  na, color = curr_color, style = plot.style_histogram, histbase = 0)
plot(main_plot == "Clusters" and cluster_val == 1 ? plot_val :  na, color = curr_color, style = plot.style_histogram, histbase = 1)
plot(main_plot == "Clusters" and cluster_val == 2 ? plot_val :  na, color = curr_color, style = plot.style_histogram, histbase = 2)

// show the centers as steplines for visual reference
plot(not na(plot_val) and k >= 1 ? plot_k0_center : na, color=color.new(color.red, 30), style=plot.style_stepline, title="k0 Cluster Center", linewidth = 2)
plot(not na(plot_val) and k >= 2 ? plot_k1_center : na, color=k <= 2 ? color.new(color.green, 30) : color.new(color.orange, 30), style=plot.style_stepline, title="k1 Cluster Center", linewidth = 2)
plot(not na(plot_val) and k == 3 ? plot_k2_center : na, color=color.new(color.green, 30), style=plot.style_stepline, title="k2 Cluster Center", linewidth = 2)
//}

//-#-#-#-#-#- ALERT -#-#-#-#-#- {
if not (curr_cluster == curr_cluster[1])
    alert("SPC: Market regime shifted → Now in cluster " + str.tostring(curr_cluster), freq = alert.freq_once_per_bar_close)
//}

// ----------------------------------------------------------------------------
// FINAL NOTES 
//
// Usage tips:
//  - Use the "Clusters" main_plot with a subset of features enabled to test
//    how individual features affect fused distances and flips.
//  - Increase `lookback` to get more stable percentile centers; decrease to react
//    faster to regime shifts.
//  - The percentile parameters p_low and p_high allow tuning of what constitutes
//    the "lower basin" and "upper basin". Lower p_low makes lower center smaller,
//    higher p_high makes upper center larger.
//
// Final remark:
//  This indicator deliberately trades off strict statistical clustering for a
//  deterministic, streaming-friendly heuristic that is cheap to compute and easy
//  to interpret. It's well-suited for visual regime/trend analysis and feature
//  engineering in lightweight systems.
//
// ----------------------------------------------------------------------------

Overview

Lower and upper percentile settings mark extreme zones, and the number of clusters is adjustable. Each component can be switched on or off and standardised. It draws in a separate pane and can plot either the clusters or a cluster line.

Details

Published by

Nguyenthang2292 on GitHub

Code credit

InvestorUnknown, as written in the script's header

Type

Indicator, Pine Script v6

Draws

Separate pane below the chart

Category

AI / machine learning, Oscillators & momentum

Markets

Any symbol

Timeframes

Any timeframe

Risk Notes

No repainting, lookahead or stacked entries were found in its code or README. That says nothing about how well it works: test it first.

Topics

algorithmic-tradingbacktesting-enginefinancial-analysisfintechquantitative-tradingtradingbot

Settings

The inputs you can change from the gear icon on the chart. Defaults are the author's.

SettingTypeDefaultVariable
Use?booltrueuse_rsi
RSI Lengthint14rsi_len
Standardizebooltruersi_t
Use?booltrueuse_cci
CCI Lengthint20cci_len
Standardizebooltruecci_t
Use?booltrueuse_fis
Fisher Lengthint9fis_len
Standardizebooltruefis_t
Use?booltrueuse_dmi
DMI Lengthint9dmi_len
Standardizebooltruedmi_t
Show all 24 settings
SettingTypeDefaultVariable
Use?booltrueuse_zsc
Z-Score Lengthint20zsc_len
Use?booltrueuse_mar
MA Lengthint14mar_len
ma_typestringSMAma_type
Standardizebooltruemar_t
K Clustersint2k
Lookbackint1000lookback
Lower Percentilefloat5.0p_low
Upper Percentilefloat95.0p_high
Main PlotstringClustersmain_plot
Plot Cluster Lineboolfalseplot_cl
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.

Similar Pine Scripts

This indicator combines a dynamic Seasonal Random Index (SRI) with a dynamic Karobein Oscillator and a pattern-similarity search algorithm in a separate pane. It normalizes SRI into seasoned and deseasoned series, shading the area green or red to signal trend

Indicator v4 Oscillators & momentumTrend
Updated 1 year ago · None stated GitHub repository of Similarity Search, Karobein and Seasonal Random Index

This indicator plots an oscillator in a separate pane. It builds several SuperTrend series across a user-defined factor range and step, using ATR as the band width. On each bar it measures the distance between the close and each SuperTrend output, then groups

Indicator v5 TrendVolatility & bands
Updated 2 years ago · GPL-3.0 GitHub repository of AI SuperTrend Clustering Oscillator [LuxAlgo]
7 stars

Lorentzian Classification is a machine learning indicator that applies a k-nearest neighbors algorithm using Lorentzian distance to analyze market patterns. It processes configurable technical indicators, including RSI, WaveTrend, CCI, and ADX, as feature vect

Indicator v6 AI / machine learningOscillators & momentum
Updated 1 year ago · MPL-2.0 GitHub repository of Lorentzian Classification
2 stars

The ANN MACD Future Forecast is an experimental indicator serving as a true deep learning sample on TradingView. It calculates Bollinger Bands and Squeeze Momentum using custom functions for SMA, EMA, standard deviation, highest and lowest values. The script p

Indicator v4 AI / machine learningVolatility & bands
Updated 7 years ago · MIT GitHub repository of ANN MACD Future Forecast (SPY 1D)

This indicator plots an optimal RSI in a separate pane and uses a k-nearest neighbour machine learning routine to adjust lookback and RSI count. It can mark RSI crossings, draw adaptive Bollinger bands around the RSI, and show tables of current settings. Users

Indicator v5 Oscillators & momentumVolatility & bands
Updated 2 years ago · None stated GitHub repository of Machine Learning: Optimal RSI [YinYangAlgorithms]

More indicators

Trade Your Charts With a Regulated Broker

Compare brokers that connect to TradingView, with spreads, minimum deposits and leverage side by side.

Compare brokers