Indicator · Pine Script v6 · Own pane

Heikin Ashi Oscillator Trend Engine

by Focal-QuantAI

An indicator in a separate pane that turns Heikin Ashi data into a momentum oscillator.

View on GitHub

GitHub stars1on the repository
Pine versionv6current
Lines of code2,658
Settings40inputs
LicenceNone statedfile: MPL-2.0
Last update6 months ago11 Apr 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

2,658 lines of Pine Script v6 from strategies/heikin_ashi_oscillator_trend_engine/AbVe_KnGK7Iuuhwb77Ul_code.pine, published under MPL-2.0 (file header). Keep the author's credit and licence lines when you reuse it.

AbVe_KnGK7Iuuhwb77Ul_code.pine
// ==============================================================================
//  Heikin Ashi Momentum Pressure
//  Author: © MYNAMEISBRANDON
//  License: Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// ==============================================================================

//@version=6
indicator("Heikin Ashi Oscillator Trend Engine ", "HA OSC💥", overlay=false, calc_bars_count=2500)


//  Heikin ASHI FUNCTION SOURCE
// ------------------------------------------------------------------------------
//  This script incorporates a Heikin Ashi calculation function adapted from
//  open-source work by @SimpleCryptoLife in his indicator "Better Heikin Ashi" (v4).
//  ➡️https://www.tradingview.com/v/Sz73I1P6/
//  ➡️https://simplecrypto.life/free-scripts/
//
//  His work showed how Heikin Ashi values can smooth price structure and make
//  directional shifts easier to read without requiring a full chart-type change.
//
//  In this script, that HA foundation is extended beyond candle visualization
//  and repurposed into a standalone oscillator engine. The resulting output uses
//  Heikin Ashi-derived values as the base for normalization, smoothing, and
//  momentum-style interpretation in a separate pane.
//
//
//  Heikin ASHI NOTES (adapted from @SimpleCryptoLife concepts)
// ------------------------------------------------------------------------------
//  • Heikin Ashi can smooth noisy price behavior and make directional movement
//    easier to read.
//  • HA direction changes can suggest reversal risk, pauses, or continuation,
//    but they are not stand-alone trade signals.
//  • In this script, HA is used as the foundation for a separate oscillator,
//    not as a replacement for the chart's real price.
//  • The oscillator is designed to help visualize momentum, directional pressure,
//    and general trend behavior from HA-derived inputs.
//  • Because these are derived values, they should be used with price structure,
//    support/resistance, volume, and other confirmation tools.
//  • Confirmed historical values do not repaint after bar close.
//    Live bars may still update while they are forming.
//
//  DISCLAIMER
// ------------------------------------------------------------------------------
//  • For educational and informational purposes only.
//  • Not financial advice.
//  • You are fully responsible for your own trading decisions.
//  • Always test and validate before using real money. Bar replay is a great tool 💥
// ==============================================================================

// ================================================================================
// 🟢 REGION: Core Imports
// ================================================================================
//
// 📌 Purpose:
//     Loads the SimpleCryptoLife higher-timeframe helper used to stabilize
//     requested HTF oscillator values inside a lower-timeframe pane.
//
// ================================================================================

import SimpleCryptoLife/HighTimeframeSampling/4  as SCL_HTF
import SimpleCryptoLife/LibraryTimeframeHelper/1 as TF

// ================================================================================
// 🔴 END REGION: Core Imports
// ================================================================================

// ==============================================================================================
// 🟢 REGION: Group Names
// ==============================================================================================

const string G_ENGINE        = "HA Engine Configuration"
const string G_CROSS         = "Adaptive MA Crossovers (faster)"
const string G_CROSS_SMA     = "SMA 20 / 50 Crossovers (slower)"
const string G_VWA           = "Rolling Volume Weighted Average of HA Engine"
const string G_HTF           = "Higher-Timeframe Plot of HA Engine"
const string G_PIVOT         = "Left/Right Trend Pivot Overlay"
const string G_GUIDE         = "Guide Lines"
const string G_VIS           = "Visuals"
const string G_PRICE_CANDLES = "Price Chart Plotcandles"
const string G_OSC_PRESSURE  = "HA Engine Candle Pressure"
const string G_TABLE         = "Engine Table"

// ==============================================================================================
// 🔴 END REGION: Group Names
// ==============================================================================================

// ==============================================================================================
// 🟢 REGION: Tooltip Strings
// ==============================================================================================

// ──────────────────────────────────────────────
// 🟦 Subregion: Main Settings
// ──────────────────────────────────────────────
const string TT_ENGINE_MODE =
     "Chooses which version of the oscillator the script uses.\n\n" +
     "• HA Range Base:\n" +
     "  A more direct and responsive version.\n" +
     "  It can react faster to changes, but may look less smooth.\n\n" +
     "• HA Blend:\n" +
     "  A smoother version built from several blended components.\n" +
     "  It can help reduce noise, but may react a little slower."

const string TT_PLOT_LAST =
     "Number of most recent bars shown for the histogram (default is plot.style_line) and any plots still tied to the main history control."

const string TT_PLOT_LAST_CANDLES =
     "Number of most recent bars shown for the oscillator candles.\n\n" +
     "Use this to control candle history independently from the histogram."

const string TT_DYNAMIC_LOOKBACK =
     "Controls how the script chooses the lookback window used for normalization and the adaptive guide lines.\n\n" +
     "Lookback = the number of bars used as the recent reference window.\n\n" +
     "• Dynamic:\n" +
     "  The script chooses the lookback automatically based on the chart timeframe.\n\n" +
     "• Manual:\n" +
     "  The script uses your Manual Lookback value instead.\n\n" +
     "Lower lookbacks react faster.\n" +
     "Higher lookbacks react more slowly."

const string TT_MANUAL_LOOKBACK =
     "Sets the exact number of bars used in the lookback window when Lookback Mode = Manual.\n\n" +
     "Examples:\n" +
     "• 55 = the last 55 bars\n" +
     "• 144 = the last 144 bars\n" +
     "• On a 5-minute chart, 144 bars ≈ 12 hours\n" +
     "• On a 1-hour chart, 144 bars ≈ 6 days\n" +
     "• On a 1-hour chart, 55 bars ≈ 2.3 days\n\n" +
     "Smaller values make the oscillator more reactive.\n" +
     "Larger values make it smoother and slower."

const string TT_USE_CLAMP =
     "Limits extreme oscillator spikes after z-score normalization."

// const string TT_CLAMP_RANGE =
//      "Maximum absolute value allowed when Clamp is enabled."

// ──────────────────────────────────────────────
// 🟦 Subregion: Range Base Engine
// ──────────────────────────────────────────────
const string TT_RANGE_LEN =
     "Sets the smoothing length for the HA Range Base engine.\n\n" +
     "Smaller values react faster but can look noisier.\n" +
     "Larger values react more slowly but look smoother."

const string TT_RANGE_TYPE =
     "Chooses the moving average type used by the HA Range Base engine.\n\n" +
     "• EMA reacts faster to recent changes\n" +
     "• SMA is steadier and slower"

// ──────────────────────────────────────────────
// 🟦 Subregion: Blend Engine
// ──────────────────────────────────────────────
const string TT_BLEND_PAIR_SET =
     "Selects the HA Blend preset used to build the blend engine.\n\n" +
     "• Fast:\n" +
     "  Uses the tighter early Fibonacci-style ladder.\n" +
     "  Pairs: 5/8, 8/13, 13/21, 21/34\n\n" +
     "• Balanced:\n" +
     "  Uses the middle ladder.\n" +
     "  Pairs: 13/21, 21/34, 34/55, 55/89\n\n" +
     "• Slow:\n" +
     "  Uses the slower ladder.\n" +
     "  Pairs: 34/55, 55/89, 89/144, 144/233"

// ──────────────────────────────────────────────
// 🟦 Subregion: Final Smoothing
// ──────────────────────────────────────────────
const string TT_FINAL_SMOOTH =
     "Adds one more smoothing step after the selected oscillator engine is built.\n\n" +
     "This can make the oscillator look cleaner and less noisy,\n" +
     "but it may also make signals react a little later."

const string TT_FINAL_SMOOTH_LEN =
     "Sets how strong the final smoothing step is.\n\n" +
     "Smaller values keep the oscillator more responsive.\n" +
     "Larger values make it smoother and slower."

const string TT_FINAL_SMOOTH_TYPE =
     "Chooses the moving average type used for the final smoothing step.\n\n" +
     "• EMA reacts faster\n" +
     "• SMA is steadier"

// ──────────────────────────────────────────────
// 🟦 Subregion: Adaptive Crossovers
// ──────────────────────────────────────────────
const string TT_CROSS_MODE =
     "Shows or hides the main crossover lines.\n\n" +
     "These lines compare a faster oscillator average against a slower one\n" +
     "to help show shifts in momentum."

const string TT_CROSS_FILL_MODE =
     "Shows or hides the colored fill between the main fast and slow crossover lines."

const string TT_TF_MODE_CROSS =
     "Chooses how the fast and slow crossover lengths are set.\n\n" +
     "• Auto:\n" +
     "  The script adjusts the lengths for the current chart timeframe.\n\n" +
     "• Manual:\n" +
     "  The script uses the Fast Line Length and Slow Line Length values you choose."

const string TT_FAST_MA_TYPE =
     "Chooses the moving average type for the faster crossover line.\n\n" +
     "EMA usually reacts faster.\n" +
     "SMA is usually smoother."

const string TT_FAST_LEN_MANUAL =
     "Sets the length of the faster crossover line when Signal Crossover Mode = Manual.\n\n" +
     "Smaller values react faster."

const string TT_SLOW_MA_TYPE =
     "Chooses the moving average type for the slower crossover line."

const string TT_SLOW_LEN_MANUAL =
     "Sets the length of the slower crossover line when Signal Crossover Mode = Manual.\n\n" +
     "Larger values react more slowly and create a steadier baseline."

// ──────────────────────────────────────────────
// 🟦 Subregion: SMA 20 / 50 Crossovers
// ──────────────────────────────────────────────
const string TT_SIGNALS_MODE_SMA =
     "Shows or hides the fixed 20 / 50 crossover lines.\n\n" +
     "This is a more traditional crossover view that does not adapt by timeframe."

const string TT_FILL_MODE_SMA =
     "Shows or hides the fill between the 20 / 50 crossover lines."

const string TT_FAST_MA_TYPE_SMA =
     "Chooses the moving average type for the faster 20-length line."

const string TT_FAST_LEN_SMA =
     "Sets the length of the faster line in this fixed crossover pair."

const string TT_SLOW_MA_TYPE_SMA =
     "Chooses the moving average type for the slower 50-length line."

const string TT_SLOW_LEN_SMA =
     "Sets the length of the slower line in this fixed crossover pair."

// ──────────────────────────────────────────────
// 🟦 Subregion: Oscillator Volume-Weighted Average
// ──────────────────────────────────────────────
const string TT_SHOW_OSC_VWA =
     "Shows a volume-weighted average of the selected oscillator.\n\n" +
     "Higher-volume bars contribute more heavily to the average, which helps the line respond more to stronger participation."

const string TT_OSC_VWA_DISPLAY_MODE =
     "Controls how the oscillator's volume-weighted average is displayed.\n\n" +
     "• Last 'N' Bars → shows only the most recent segment\n" +
     "• None → hides the plot"

const string TT_OSC_VWA_SHOW_LAST =
     "Number of most recent bars shown for the oscillator VWA plot."

const string TT_OSC_VWA_LEN_MODE =
     "Controls whether the oscillator VWA length is selected automatically by timeframe or manually."

const string TT_OSC_VWA_LEN =
     "Lookback length used by the oscillator's volume-weighted average when Length Mode = Manual.\n\n" +
     "Higher values smooth the line more.\n" +
     "Lower values make it more reactive.\n\n" +
     "Auto reference:\n" +
     "• <1m (Seconds) → 55\n" +
     "• 1–3m          → 21\n" +
     "• 5–10m         → 34\n" +
     "• 15–30m        → 48\n" +
     "• 1–2h          → 55\n" +
     "• 4–8h          → 48\n" +
     "• 1D–21D        → 34\n" +
     "• 1W–10W        → 21\n" +
     "• 1M–12M        → 13"

// ──────────────────────────────────────────────
// 🟦 Subregion: Higher-Timeframe Oscillator
// ──────────────────────────────────────────────
const string TT_USE_DYNAMIC_HTF =
     "Chooses how the higher-timeframe oscillator reference line is selected.\n\n" +
     "• Dynamic:\n" +
     "  The script automatically steps up to a higher timeframe.\n" +
     "  Example: a 5-minute chart may use a higher reference like 15-minute.\n\n" +
     "• Manual:\n" +
     "  The script uses the exact higher timeframe you choose below."

const string TT_MANUAL_HTF_SELECTION =
     "Sets the higher timeframe used when HTF Mode = Manual.\n\n" +
     "Example:\n" +
     "• If your chart is 5m and you choose 60, the blue HTF oscillator line comes from the 1-hour timeframe.\n\n" +
     "Chart = use the current chart timeframe instead."

const string TT_HTF_OSC_DISPLAY_MODE =
     "Controls how the higher-timeframe oscillator reference line is displayed.\n\n" +
     "• Last N Bars → shows only the most recent segment\n" +
     "• None → hides the HTF oscillator reference line"

const string TT_HTF_OSC_SHOW_LAST =
     "Number of most recent bars shown when HTF Oscillator Display Mode = Last N Bars."

// ──────────────────────────────────────────────
// 🟦 Subregion: Trend Pivot Overlay
// ──────────────────────────────────────────────
const string TT_SHOW_PIVOT_OVERLAY =
     "Shows a structure line built from confirmed oscillator pivot highs and pivot lows.\n\n" +
     "This can help you see whether the oscillator is currently leaning from a recent high pivot or a recent low pivot."

const string TT_SHOW_PIVOT_OVERLAY_OBJ =
     "Shows or hides the horizontal pivot-based line object.\n\n" +
     "This is separate from the plotted Trend Pivot Overlay line, so you can show either one or both."

const string TT_PIVOT_MODE =
     "Chooses which confirmed pivot the overlay follows.\n\n" +
     "• Pivot High = always follows confirmed oscillator highs\n" +
     "• Pivot Low = always follows confirmed oscillator lows\n" +
     "• Dynamic = switches between them based on recent structure"

const string TT_LEFT_BARS =
     "Sets how many bars to the left are required to confirm a pivot.\n\n" +
     "Higher values usually make pivots stricter and less frequent."

const string TT_RIGHT_BARS =
     "Sets how many bars to the right are required before a pivot is confirmed.\n\n" +
     "Higher values delay confirmation, but can reduce false pivots."

// ──────────────────────────────────────────────
// 🟦 Subregion: Guide Lines
// ──────────────────────────────────────────────
const string TT_GUIDE_SHOW_LAST =
     "Number of most recent bars shown for the guide lines.\n\n" +
     "This lets you control guide-line history separately from the histogram and other plots."

const string TT_SHOW_ZERO =
     "Shows the zero line, which is the oscillator's center baseline.\n\n" +
     "Readings above zero are positive relative to the script's current calculation.\n" +
     "Readings below zero are negative."

const string TT_SHOW_HALF =
     "Shows the adaptive +50% and -50% guide lines.\n\n" +
     "These are not fixed levels.\n" +
     "They move with the oscillator's recent range and can help show when the oscillator is stretching away from zero."

// ──────────────────────────────────────────────
// 🟦 Subregion: Visuals
// ──────────────────────────────────────────────
const string TT_SHOW_HIST =
     "Shows or hides the main oscillator plot."

const string TT_SHOW_OSC_CANDLES =
     "Shows candle-style bars built from the oscillator instead of price.\n\n" +
     "This can make it easier to see whether the oscillator is expanding, slowing, or reversing from one bar to the next."

const string TT_OSC_CANDLE_TRANSP =
     "Controls the body transparency of the oscillator candles.\n\n" +
     "0 = solid\n" +
     "100 = invisible"

// ──────────────────────────────────────────────
// 🟦 Subregion: Engine Table
// ──────────────────────────────────────────────
const string TT_SHOW_ENGINE_TABLE =
     "Shows a compact table that summarizes the active oscillator engine.\n\n" +
     "The matching engine-detail row is highlighted so users can quickly see\n" +
     "whether the script is currently running the HA Range Base path or the\n" +
     "HA Blend path."

const string TT_ENGINE_TABLE_POS =
     "Controls where the Engine Table appears inside the oscillator pane."

const string TT_ENGINE_TABLE_TEXT =
     "Controls the text size used by the Engine Table."

// ──────────────────────────────────────────────
// 🟦 Subregion: Price Overlay Candles
// ──────────────────────────────────────────────
const string TT_PRICE_CANDLE_MODE =
     "Controls whether the script draws colored candles on the main price chart.\n\n" +
     "• Oscillator Colors:\n" +
     "  Price candles are colored by the oscillator's current color state.\n\n" +
     "• None:\n" +
     "  No overlay candles are drawn."

const string TT_PRICE_CANDLE_SHOW_LAST =
     "Sets how many of the most recent chart candles are drawn with the oscillator-color overlay."

const string TT_PRICE_CANDLE_BODY_TRANSP =
     "Controls the body transparency of the overlay candles drawn on the main price chart.\n\n" +
     "0 = solid body\n" +
     "100 = invisible body\n\n" +
     "Wicks and borders stay fully visible."

// ──────────────────────────────────────────────
// 🟦 Subregion: OSC Candle Pressure Envelope Tooltips
// ──────────────────────────────────────────────
//
// 📌 Purpose:
//     These tooltips explain the controls used to build and style the
//     oscillator candle pressure envelope.
//
// 🧠 What these settings control:
//     • Whether the envelope is shown
//     • The upper and lower trigger levels
//     • How thick the envelope is around the oscillator candles
//     • What colors are used when upper or lower pressure is active
//
const string TT_SHOW_OSC_PRESSURE_ENV =
     "Wraps the oscillator candles in a pressure-style background envelope.\n\n" +
     "The envelope is built around each oscillator candle's high/low range,\n" +
     "then only turns on when the candle reaches the trigger levels below."

const string TT_OSC_PRESSURE_UPPER =
     "Upper trigger level for the oscillator candle pressure envelope.\n\n" +
     "When the oscillator candle reaches or exceeds this level,\n" +
     "the upper pressure envelope fill turns on."

const string TT_OSC_PRESSURE_LOWER =
     "Lower trigger level for the oscillator candle pressure envelope.\n\n" +
     "When the oscillator candle reaches or falls below this level,\n" +
     "the lower pressure envelope fill turns on."

const string TT_OSC_PRESSURE_PAD =
     "Extra padding added above and below the oscillator candle range\n" +
     "when drawing the pressure envelope.\n\n" +
     "Higher values make the envelope thicker."

const string TT_OSC_PRESSURE_UPPER_COLOR =
     "Fill color used when the oscillator candle reaches the upper trigger."

const string TT_OSC_PRESSURE_LOWER_COLOR =
     "Fill color used when the oscillator candle reaches the lower trigger."

const string TT_OSC_PRESSURE_FILL_TRANSP =
     "Controls the transparency used by both oscillator pressure-envelope fills.\n\n" +
     "0 = solid fill\n" +
     "100 = invisible fill"

// ==============================================================================================
// 🔴 END REGION: Tooltip Strings
// ==============================================================================================

// ==============================================================================================
// 🟢 REGION: Inputs
// ==============================================================================================

// ──────────────────────────────────────────────
// 🟦 Subregion: Engine Configuration
// ──────────────────────────────────────────────
string in_lookbackMode = input.string(
     defval="Dynamic",
     title="Lookback Mode",
     options=["Dynamic", "Manual"],
     group=G_ENGINE,
     tooltip=TT_DYNAMIC_LOOKBACK)

bool in_useDynamicLookback =
     in_lookbackMode == "Dynamic"

int in_manualLookback = input.int(
     defval=144,
     minval=10,
     title="  ↳ Manual Lookback",
     group=G_ENGINE,
     tooltip=TT_MANUAL_LOOKBACK)

bool in_useClamp = true //input.bool(
    //  defval=true,
    //  title="Clamp Output",
    //  group=G_ENGINE,
    //  tooltip=TT_USE_CLAMP)

float in_clampRange = 15.0

string in_engineMode = input.string(
     defval="HA Blend",
     title="Engine Mode",
     options=["HA Range Base", "HA Blend"],
     group=G_ENGINE,
     tooltip=TT_ENGINE_MODE)

int in_rangeLen = input.int(
     defval=55,
     minval=1,
     title="  ↳ HA Range Base Length",
     group=G_ENGINE,
     tooltip=TT_RANGE_LEN)

string in_rangeMaType = input.string(
     defval="EMA",
     title="  ↳ HA Range Base MA Type",
     options=["EMA", "SMA"],
     group=G_ENGINE,
     tooltip=TT_RANGE_TYPE)

string in_blendPairSet = input.string(
     defval="Balanced",
     title="  ↳ HA Blend Pair Set",
     options=["Fast", "Balanced", "Slow"],
     group=G_ENGINE,
     tooltip=TT_BLEND_PAIR_SET)

string in_finalSmoothMode = input.string(
     defval="On",
     title="Final Smoothing (Both Engines)",
     options=["On", "Off"],
     group=G_ENGINE,
     tooltip=TT_FINAL_SMOOTH)

bool in_useFinalSmooth =
     in_finalSmoothMode == "On"

int in_finalSmoothLen = input.int(
     defval=3,
     minval=1,
     title="  ↳ Final Smoothing Length",
     group=G_ENGINE,
     tooltip=TT_FINAL_SMOOTH_LEN)

string in_finalSmoothType = input.string(
     defval="EMA",
     title="  ↳ Final Smoothing Type",
     options=["EMA", "SMA"],
     group=G_ENGINE,
     tooltip=TT_FINAL_SMOOTH_TYPE)

// ──────────────────────────────────────────────
// 🟦 Subregion: Adaptive Crossovers
// ──────────────────────────────────────────────
string in_crossMode = input.string(
     defval="Show Crossovers",
     title="Adaptive Crossovers",
     options=["Show Crossovers", "None"],
     group=G_CROSS,
     tooltip=TT_CROSS_MODE)

bool in_showCrossLines =
     in_crossMode == "Show Crossovers"

string in_crossFillMode = input.string(
     defval="Show Fill",
     title="Fill Crossovers",
     options=["Show Fill", "None"],
     group=G_CROSS,
     tooltip=TT_CROSS_FILL_MODE)

bool in_showCrossFill =
     in_crossFillMode == "Show Fill"

string in_tfMode_Cross = input.string(
     defval="Auto",
     title="Signal Crossover Mode",
     options=["Auto", "Manual"],
     group=G_CROSS,
     tooltip=TT_TF_MODE_CROSS)

string in_fastMaType = input.string(
     defval="EMA",
     title="Fast Line Type",
     options=["EMA", "SMA"],
     group=G_CROSS,
     tooltip=TT_FAST_MA_TYPE)

int in_fastLen_manual = input.int(
     defval=5,
     minval=1,
     title="Fast Line Length",
     group=G_CROSS,
     tooltip=TT_FAST_LEN_MANUAL)

string in_slowMaType = input.string(
     defval="EMA",
     title="Slow Line Type",
     options=["EMA", "SMA"],
     group=G_CROSS,
     tooltip=TT_SLOW_MA_TYPE)

int in_slowLen_manual = input.int(
     defval=21,
     minval=1,
     title="Slow Line Length",
     group=G_CROSS,
     tooltip=TT_SLOW_LEN_MANUAL)

// ──────────────────────────────────────────────
// 🟦 Subregion: SMA 20 / 50 Crossovers
// ──────────────────────────────────────────────
string in_SignalsMode_sma = input.string(
     defval="Show Crossovers",
     title="SMA 20/50 Crossovers",
     options=["Show Crossovers", "None"],
     group=G_CROSS_SMA,
     tooltip=TT_SIGNALS_MODE_SMA)

bool in_showSignals_sma =
     in_SignalsMode_sma == "Show Crossovers"

string in_FillMode_sma = input.string(
     defval="Show Fill",
     title="Fill SMA 20/50 Crossovers",
     options=["Show Fill", "None"],
     group=G_CROSS_SMA,
     tooltip=TT_FILL_MODE_SMA)

bool in_showFill_sma =
     in_FillMode_sma == "Show Fill"

string in_MaType_fast_sma = input.string(
     defval="SMA",
     title="Fast Line Type",
     options=["EMA", "SMA"],
     group=G_CROSS_SMA,
     tooltip=TT_FAST_MA_TYPE_SMA)

int in_Len_fast_sma = input.int(
     defval=20,
     minval=1,
     title="Fast Line Length",
     group=G_CROSS_SMA,
     tooltip=TT_FAST_LEN_SMA)

string in_MaType_slow_sma = input.string(
     defval="SMA",
     title="Slow Line Type",
     options=["EMA", "SMA"],
     group=G_CROSS_SMA,
     tooltip=TT_SLOW_MA_TYPE_SMA)

int in_Len_slow_sma = input.int(
     defval=50,
     minval=1,
     title="Slow Line Length",
     group=G_CROSS_SMA,
     tooltip=TT_SLOW_LEN_SMA)

// ──────────────────────────────────────────────
// 🟦 Subregion: Oscillator Volume-Weighted Average
// ──────────────────────────────────────────────
string in_showOscVwaMode = input.string(
     defval="Show",
     title="Show Oscillator VWA",
     options=["Show", "Hide"],
     group=G_VWA,
     tooltip=TT_SHOW_OSC_VWA)

bool in_showOscVwa =
     in_showOscVwaMode == "Show"

string in_oscVwaDisplayMode = input.string(
     defval="Last 'N' Bars",
     title="Oscillator VWA Display Mode",
     options=["Last 'N' Bars", "None"],
     group=G_VWA,
     tooltip=TT_OSC_VWA_DISPLAY_MODE)

int in_oscVwaShowLast = input.int(
     defval=55,
     minval=1,
     title="Oscillator VWA - Show Last 'N' Bars",
     group=G_VWA,
     tooltip=TT_OSC_VWA_SHOW_LAST)

string in_oscVwaLenMode = input.string(
     defval="Auto",
     title="Oscillator VWA Length Mode",
     options=["Auto", "Manual"],
     group=G_VWA,
     tooltip=TT_OSC_VWA_LEN_MODE)

int in_oscVwaLenManual = input.int(
     defval=55,
     minval=1,
     title="Oscillator VWA Length",
     group=G_VWA,
     tooltip=TT_OSC_VWA_LEN)

// ──────────────────────────────────────────────
// 🟦 Subregion: Higher-Timeframe Oscillator
// ──────────────────────────────────────────────
string in_htfMode = input.string(
     defval="Dynamic",
     title="HTF Mode",
     options=["Dynamic", "Manual"],
     group=G_HTF,
     tooltip=TT_USE_DYNAMIC_HTF)

bool in_useDynamicHtf =
     in_htfMode == "Dynamic"

string in_manualHtfSelection = input.string(
     defval="Chart",
     title="Manual Higher Timeframe",
     options=["Chart", "30S", "1", "2", "3", "4", "5", "10", "15", "30", "45", "60", "120", "240", "480", "D", "W", "2W", "M", "2M"],
     group=G_HTF,
     tooltip=TT_MANUAL_HTF_SELECTION)

string in_htfOscMode = input.string(
     defval="Last N Bars",
     title="HTF Oscillator Display Mode",
     options=["Last N Bars", "None"],
     group=G_HTF,
     tooltip=TT_HTF_OSC_DISPLAY_MODE)

int in_htfOscShowLast = input.int(
     defval=55,
     minval=1,
     maxval=1000,
     title="HTF Oscillator - Show Last 'N' Bars",
     group=G_HTF,
     tooltip=TT_HTF_OSC_SHOW_LAST)

// ──────────────────────────────────────────────
// 🟦 Subregion: Trend Pivot Overlay
// ──────────────────────────────────────────────
string in_trendOverlayMode = input.string(
     defval="Hide",
     title="Trend Pivot Overlay",
     options=["Show", "Hide"],
     group=G_PIVOT,
     tooltip=TT_SHOW_PIVOT_OVERLAY)

bool in_showTrendOverlay =
     in_trendOverlayMode == "Show"

string in_trendOverlayObjMode = input.string(
     defval="Show",
     title="Trend Pivot Overlay Object Line",
     options=["Show", "Hide"],
     group=G_PIVOT,
     tooltip=TT_SHOW_PIVOT_OVERLAY_OBJ)

bool in_showTrendOverlayObj =
     in_trendOverlayObjMode == "Show"

string in_pivotMode = input.string(
     defval="Dynamic",
     title="Pivot Anchor Mode",
     options=["Pivot High", "Pivot Low", "Dynamic"],
     group=G_PIVOT,
     tooltip=TT_PIVOT_MODE)

int in_leftBars = input.int(
     defval=21,
     minval=1,
     title="Left Bars",
     group=G_PIVOT,
     tooltip=TT_LEFT_BARS)

int in_rightBars = input.int(
     defval=5,
     minval=1,
     title="Right Bars",
     group=G_PIVOT,
     tooltip=TT_RIGHT_BARS)

color in_overlayBullColor = input.color(
     defval=color.orange,
     title="Trend Overlay Bull Color",
     group=G_PIVOT)

color in_overlayBearColor = input.color(
     defval=#f51673,
     title="Trend Overlay Bear Color",
     group=G_PIVOT)

// ──────────────────────────────────────────────
// 🟦 Subregion: Guide Lines
// ──────────────────────────────────────────────
int in_guideShowLast = input.int(
     defval=233,
     minval=1,
     title="Guide Lines Plot Last N Bars",
     group=G_GUIDE,
     tooltip=TT_GUIDE_SHOW_LAST)

string in_zeroLineMode = input.string(
     defval="Show",
     title="Zero Line",
     options=["Show", "Hide"],
     group=G_GUIDE,
     tooltip=TT_SHOW_ZERO)

bool in_showZeroLine =
     in_zeroLineMode == "Show"

string in_halfLinesMode = input.string(
     defval="Show",
     title="±50% Guide Lines",
     options=["Show", "Hide"],
     group=G_GUIDE,
     tooltip=TT_SHOW_HALF)

bool in_showHalfLines =
     in_halfLinesMode == "Show"

color in_zeroLineColor = input.color(
     defval=#dbdbdb,
     title="Zero Line Color",
     group=G_GUIDE)

color in_upperGuideColor = input.color(
     defval=#ff693b,
     title="+50% Guide Color",
     group=G_GUIDE)

color in_lowerGuideColor = input.color(
     defval=#f32753,
     title="-50% Guide Color",
     group=G_GUIDE)

/// ──────────────────────────────────────────────
// 🟦 Subregion: Visuals
// ──────────────────────────────────────────────
string in_oscCandlesMode = input.string(
     defval="Show",
     title="Oscillator Candles",
     options=["Show", "Hide"],
     group=G_VIS,
     tooltip=TT_SHOW_OSC_CANDLES)

bool in_showOscCandles =
     in_oscCandlesMode == "Show"

int in_oscCandleTransp = input.int(
     defval=65,
     minval=0,
     maxval=100,
     title="  ↳Oscillator Candle Body Transparency",
     group=G_VIS,
     tooltip=TT_OSC_CANDLE_TRANSP)

int in_plotLastCandles = input.int(
     defval=144,
     minval=1,
     title="  ↳ Oscillator Candle Plot Last N Bars",
     group=G_VIS,
     tooltip=TT_PLOT_LAST_CANDLES)

string in_histogramMode = input.string(
     defval="Show",
     title="Histogram",
     options=["Show", "Hide"],
     group=G_VIS,
     tooltip=TT_SHOW_HIST)

int in_plotLast = input.int(
     defval=21,
     minval=1,
     title="  ↳ Histogram Plot Last N Bars",
     group=G_VIS,
     tooltip=TT_PLOT_LAST)

bool in_showHistogram =
     in_histogramMode == "Show"

// string in_colorPriceBarsMode = input.string(
//      defval="Hide",
//      title="Color Price Bars",
//      options=["Show", "Hide"],
//      group=G_VIS,
//      tooltip=TT_COLOR_PRICE_BARS)

// bool in_colorPriceBars =
//      in_colorPriceBarsMode == "Show"

color in_colAboveRise = input.color(
     defval=color.orange,
     title="Above 0 Rising",
     group=G_VIS)

color in_colAboveFall = input.color(
     defval=color.new(#fff177, 0),
     title="Above 0 Falling",
     group=G_VIS)

color in_colBelowFall = input.color(
     defval=color.new(#57585c, 0),
     title="Below 0 Falling",
     group=G_VIS)

color in_colBelowRise = input.color(
     defval=color.new(#97999e, 0),
     title="Below 0 Rising",
     group=G_VIS)

color in_colAbove50Rise = input.color(
     defval=#ff5900,
     title="Above +50 Rising",
     group=G_VIS)

color in_colBelow50Fall = input.color(
     defval=#e900ca,
     title="Below -50 Falling",
     group=G_VIS)

color in_crossBullColor = input.color(
     defval=color.rgb(44, 161, 190),
     title="Cross Bull Color",
     group=G_VIS)

color in_crossBearColor = input.color(
     defval=#f51673,
     title="Cross Bear Color",
     group=G_VIS)

// ──────────────────────────────────────────────
// 🟦 Subregion: Engine Table
// ──────────────────────────────────────────────
string in_engineTableMode = input.string(
     defval="Show",
     title="Engine Table",
     options=["Show", "Hide"],
     group=G_TABLE,
     tooltip=TT_SHOW_ENGINE_TABLE)

bool in_showEngineTable =
     in_engineTableMode == "Show"

string in_engineTablePos = input.string(
     defval="Middle Right",
     title="Engine Table Position",
     options=["Top Left", "Top Center", "Top Right", "Middle Left", "Middle Center", "Middle Right", "Bottom Left", "Bottom Center", "Bottom Right"],
     group=G_TABLE,
     tooltip=TT_ENGINE_TABLE_POS)

string in_engineTableTextSize = input.string(
     defval="Normal",
     title="Engine Table Text Size",
     options=["Tiny", "Small", "Normal", "Large"],
     group=G_TABLE,
     tooltip=TT_ENGINE_TABLE_TEXT)

// ──────────────────────────────────────────────
// 🟦 Subregion: Price Chart Plotcandles
// ──────────────────────────────────────────────
string in_priceCandleMode = input.string(
     defval="Oscillator Colors",
     title="Price Chart Plotcandles",
     options=["Oscillator Colors", "None"],
     group=G_PRICE_CANDLES,
     tooltip=TT_PRICE_CANDLE_MODE)

bool in_showPriceCandles =
     in_priceCandleMode != "None"

bool in_useOscColorForPriceCandles =
     in_priceCandleMode == "Oscillator Colors"

int in_priceCandleShowLast = input.int(
     defval=233,
     minval=1,
     title="  ↳ Price Chart Plotcandles - Last N Bars",
     group=G_PRICE_CANDLES,
     tooltip=TT_PRICE_CANDLE_SHOW_LAST)

int in_priceCandleBodyTransp = input.int(
     defval=70,
     minval=0,
     maxval=100,
     title="  ↳ Price Chart Plotcandles Transparency",
     group=G_PRICE_CANDLES,
     tooltip=TT_PRICE_CANDLE_BODY_TRANSP)

/// ──────────────────────────────────────────────
// 🟦 Subregion: OSC Candle Pressure Envelope
// ──────────────────────────────────────────────
//
// 📌 Purpose:
//     Controls the pressure-style envelope that wraps the oscillator candles
//     when they reach user-defined upper or lower trigger zones.
//
// 🧠 What these settings control:
//     • Whether the envelope is shown
//     • The upper and lower trigger levels
//     • The padding used to wrap the oscillator candle range
//     • The fill colors used for upper and lower pressure states
//
string in_oscPressureEnvelopeMode = input.string(
     defval="Show",
     title="OSC Candle Pressure Envelope",
     options=["Show", "Hide"],
     group=G_OSC_PRESSURE,
     tooltip=TT_SHOW_OSC_PRESSURE_ENV)

bool in_showOscPressureEnvelope =
     in_oscPressureEnvelopeMode == "Show"

float in_oscPressureUpperTrigger = input.float(
     defval=0.50,
     step=0.05,
     title="Upper Trigger",
     group=G_OSC_PRESSURE,
     tooltip=TT_OSC_PRESSURE_UPPER)

float in_oscPressureLowerTrigger = input.float(
     defval=-0.50,
     step=0.05,
     title="Lower Trigger",
     group=G_OSC_PRESSURE,
     tooltip=TT_OSC_PRESSURE_LOWER)

float in_oscPressureEnvelopePad = input.float(
     defval=0.05,
     minval=0.0,
     step=0.01,
     title="Envelope Padding",
     group=G_OSC_PRESSURE,
     tooltip=TT_OSC_PRESSURE_PAD)

color in_oscPressureUpperFill = input.color(
     defval=#ff5900,
     title="Upper Envelope Fill",
     group=G_OSC_PRESSURE,
     tooltip=TT_OSC_PRESSURE_UPPER_COLOR)

color in_oscPressureLowerFill = input.color(
     defval=#e900ca,
     title="Lower Envelope Fill",
     group=G_OSC_PRESSURE,
     tooltip=TT_OSC_PRESSURE_LOWER_COLOR)

int in_oscPressureFillTransp = input.int(
     defval=75,
     minval=0,
     maxval=100,
     title="Fill Transparency",
     group=G_OSC_PRESSURE,
     tooltip=TT_OSC_PRESSURE_FILL_TRANSP)

// ==============================================================================================
// 🔴 END REGION: Inputs
// ==============================================================================================

// ==============================================================================================
// 🟢 REGION: Helpers
// ==============================================================================================
//
// 📌 Purpose:
//     Houses the small reusable utility functions that support the core HA engine,
//     normalization, slope logic, color logic, and Heikin Ashi calculations.
//
// 🧩 Why this matters in this script:
//     These helpers keep the main engine regions cleaner and make shared logic
//     easier to reuse consistently across the script.
//
// ==============================================================================================

// ──────────────────────────────────────────────
// 🟦 Subregion: Moving Average Selector
// ──────────────────────────────────────────────
//
// 📌 Purpose:
//     Returns either an SMA or EMA from the same source and length inputs.
//
// 🧠 Behavior:
//     • Calculates both SMA and EMA each bar
//     • Returns the one selected by `_type`
//     • Keeps TA calls outside the final ternary decision so Pine evaluates
//       them consistently bar to bar
//
// 🧩 Why this matters in this script:
//     This helper is reused anywhere the script lets the user choose between
//     SMA and EMA behavior, such as the Range Base engine and crossover engines.
//
f_ma(float _src, int _len, string _type) =>
    float _sma = ta.sma(_src, _len)
    float _ema = ta.ema(_src, _len)
    _type == "SMA" ? _sma : _ema

// ──────────────────────────────────────────────
// 🟦 Subregion: Adaptive Lookback Resolver
// ──────────────────────────────────────────────
//
// 📌 Purpose:
//     Chooses the lookback window used by normalization and adaptive guide logic.
//
// 🧠 Behavior:
//     • If `_dynamic` is false, the function returns the manual input
//     • If `_dynamic` is true, the function maps the current chart timeframe
//       by timeframe family and multiplier instead of exact string matches
//     • This keeps extended periods like 2D, 3D, 2W, and 6M inside a
//       dynamic bucket instead of falling back to manual
//
// 🧩 Why this matters in this script:
//     This keeps normalization and guide-line behavior more consistent across
//     expanded daily / weekly / monthly chart periods.
//
f_lookback(bool _dynamic, int _manual) =>
    if not _dynamic
        _manual
    else if timeframe.isseconds
        timeframe.multiplier <= 30 ? 377 :
         timeframe.multiplier <= 45 ? 233 :
         _manual
    else if timeframe.isminutes
        timeframe.multiplier <= 3 ? 233 :
         timeframe.multiplier <= 15 ? 144 :
         timeframe.multiplier <= 30 ? 89 :
         timeframe.multiplier <= 120 ? 55 :
         timeframe.multiplier <= 480 ? 34 :
         21
    else if timeframe.isdaily
        timeframe.multiplier <= 34 ? 21 : 13
    else if timeframe.isweekly
        timeframe.multiplier <= 21 ? 13 : 8
    else if timeframe.ismonthly
        timeframe.multiplier <= 12 ? 8 : 5
    else
        _manual

// ──────────────────────────────────────────────
// 🟦 Subregion: Z-Score Normalization + Clamp
// ──────────────────────────────────────────────
//
// 📌 Purpose:
//     Normalizes a source series by its rolling standard deviation, then
//     optionally clamps extreme values.
//
// 🧠 Behavior:
//     • Computes rolling standard deviation over `_len`
//     • Converts the source into a simplified z-score style reading
//     • If `_useClamp` is true, limits the final value to the range
//       `-_clamp` to `+_clamp`
//
// 🧩 Why this matters in this script:
//     This helps keep oscillator values more consistent across tickers and
//     timeframes while preventing a few extreme spikes from distorting the panel.
//
f_zscoreClamp(float _src, int _len, bool _useClamp, float _clamp) =>
    float _stdev = ta.stdev(_src, _len)
    float _z = _stdev != 0 ? _src / _stdev : 0
    _useClamp ? math.max(math.min(_z, _clamp), -_clamp) : _z

// ──────────────────────────────────────────────
// 🟦 Subregion: Quadrant Color Resolver
// ──────────────────────────────────────────────
//
// 📌 Purpose:
//     Assigns one of four colors to the oscillator based on two conditions:
//     • whether the value is above or below zero
//     • whether the value is rising or falling versus the prior bar
//
// 🧠 Behavior:
//     • Above 0 + Rising  → `_upRise`
//     • Above 0 + Falling → `_upFall`
//     • Below 0 + Falling → `_dnFall`
//     • Below 0 + Rising  → `_dnRise`
//
// 🧩 Why this matters in this script:
//     This is the shared color engine that gives the histogram and oscillator
//     candles their four-quadrant momentum look.
//
f_quadrantColor(float _src, color _upRise, color _upFall, color _dnFall, color _dnRise) =>
    bool _rising = _src > _src[1]
    bool _falling = _src < _src[1]
    _src >= 0 and _rising  ? _upRise :
     _src >= 0 and _falling ? _upFall :
     _src < 0 and _falling ? _dnFall :
     _dnRise

// ──────────────────────────────────────────────
// 🟦 Subregion: Threshold Visual Color Resolver
// ──────────────────────────────────────────────
//
// 📌 Purpose:
//     Extends the script's visual color logic to recognize the adaptive +50 / -50
//     guide zones in addition to the zero-line quadrants.
//
// 🧠 Behavior:
//     • Above +50 + Rising  → special dark orange
//     • Above 0  + Falling  → existing yellow
//     • Above 0  + Rising   → existing orange
//     • Below -50 + Falling → special purple
//     • Below 0  + Falling  → existing dark gray
//     • Below 0  + Rising   → existing light gray
//
// 🧩 Why this matters in this script:
//     This lets the histogram, candles, follow line, price-overlay candles,
//     and Engine Table all respond to the same threshold-aware color logic.
//
f_visualThresholdColor(
     float _src,
     float _upperGuide,
     float _lowerGuide,
     color _above50Rise,
     color _above0Rise,
     color _above0Fall,
     color _below0Fall,
     color _below50Fall,
     color _below0Rise) =>

    bool _rising =
         _src > _src[1]

    bool _falling =
         _src < _src[1]

    bool _above50 =
         not na(_upperGuide) and
         _upperGuide > 0 and
         _src >= _upperGuide

    bool _below50 =
         not na(_lowerGuide) and
         _lowerGuide < 0 and
         _src <= _lowerGuide

    _above50 and _rising ? _above50Rise :
     _src >= 0 and _falling ? _above0Fall :
     _src >= 0 and _rising ? _above0Rise :
     _below50 and _falling ? _below50Fall :
     _src < 0 and _falling ? _below0Fall :
     _below0Rise

// ──────────────────────────────────────────────
// 🟦 Subregion: Heiken Ashi Builder
// ──────────────────────────────────────────────
//
// 📌 Purpose:
//     Calculates the synthetic Heikin Ashi OHLC values used by the script.
//
// 🧠 Behavior:
//     • Computes HA Open from the prior HA Open / HA Close relationship
//     • Computes HA Close from the average of the real OHLC values
//     • Computes HA High and HA Low from the current high/low and HA body
//     • Returns HA direction flags for bullish / bearish state
//
// 🧩 Why this matters in this script:
//     This is the foundation of the entire oscillator. Everything else in the
//     script begins with these HA-derived values rather than raw candle structure.
//
functionHeikinAshi(_open, _close, _high, _low, _haOpen1, _haClose1) =>
    float _iHAOpen = barstate.isfirst ? (_open + _close) / 2 : (_haOpen1 + _haClose1) / 2
    float _iHAClose = (_open + _close + _high + _low) / 4
    float _iHAHigh = barstate.isfirst ? _high : math.max(_high, _iHAOpen, _iHAClose)
    float _iHALow = barstate.isfirst ? _low : math.min(_low, _iHAOpen, _iHAClose)
    bool _iIsHAUp = _iHAClose > _iHAOpen
    bool _iIsHADown = _iHAClose <= _iHAOpen
    [_iHAOpen, _iHAClose, _iHAHigh, _iHALow, _iIsHAUp, _iIsHADown]

// ──────────────────────────────────────────────
// 🟦 Subregion: Table Helpers
// ──────────────────────────────────────────────
f_tablePos(string _pos) =>
    _pos == "Top Left" ? position.top_left :
     _pos == "Top Center" ? position.top_center :
     _pos == "Top Right" ? position.top_right :
     _pos == "Middle Left" ? position.middle_left :
     _pos == "Middle Center" ? position.middle_center :
     _pos == "Middle Right" ? position.middle_right :
     _pos == "Bottom Left" ? position.bottom_left :
     _pos == "Bottom Center" ? position.bottom_center : position.bottom_right

f_tableTextSize(string _size) =>
    _size == "Tiny" ? size.tiny :
     _size == "Small" ? size.small :
     _size == "Large" ? size.large : size.normal

f_onOff(bool _v) =>
    _v ? "On" : "Off"

// ──────────────────────────────────────────────
// 🟦 Subregion: Table Contrast Text Helper
// ──────────────────────────────────────────────
//
// 📌 Purpose:
//     Chooses black or white table text based on the brightness of a cell's
//     background color.
//
// 🧠 Behavior:
//     • Estimates perceived brightness using a simple luma formula
//     • Returns black text for lighter backgrounds
//     • Returns white text for darker backgrounds
//
// 🧩 Why this matters in this script:
//     The Engine Table can now inherit live engine colors from the active
//     oscillator base, so this helper keeps the text readable regardless of
//     whether the row is currently orange, yellow, gray, or another quadrant color.
//
f_tableContrastText(color _bg) =>
    float _luma =
         color.r(_bg) * 0.299 +
         color.g(_bg) * 0.587 +
         color.b(_bg) * 0.114
    _luma >= 150 ? #000000 : color.white
    
// ==============================================================================================
// 🔴 END REGION: Helpers
// ==============================================================================================

// ==============================================================================================
// 🟢 REGION: Heikin Ashi Foundation
// ==============================================================================================

var float HAOpen = 0.0
var float HAClose = 0.0
var float HAHigh = 0.0
var float HALow = 0.0
var bool isHAUp = false
var bool isHADown = false

[r_HAOpen, r_HAClose, r_HAHigh, r_HALow, r_isHAUp, r_isHADown] =
     functionHeikinAshi(
         open,
         close,
         high,
         low,
         HAOpen[1],
         HAClose[1])

HAOpen := r_HAOpen
HAClose := r_HAClose
HAHigh := r_HAHigh
HALow := r_HALow
isHAUp := r_isHAUp
isHADown := r_isHADown

int normPeriod = f_lookback(in_useDynamicLookback, in_manualLookback)

float haRangeNormPct =
     r_HAClose != 0 ? ((r_HAHigh - r_HALow) / r_HAClose) * 100 : 0

float haRangeSource =
     haRangeNormPct

float haSignedBase =
     isHAUp ? haRangeSource : -haRangeSource

float haSignedPrepared =
     f_zscoreClamp(
         haSignedBase,
         normPeriod,
         in_useClamp,
         in_clampRange)

// ==============================================================================================
// 🔴 END REGION: Heikin Ashi Foundation
// ==============================================================================================

// ==============================================================================================
// 🟢 REGION: Range Base Engine
// ==============================================================================================

float rangeEngineCore =
     f_ma(
         haSignedPrepared,
         in_rangeLen,
         in_rangeMaType)

// ==============================================================================================
// 🔴 END REGION: Range Base Engine
// ==============================================================================================

/// ==============================================================================================
// 🟢 REGION: Blend Engine
// ==============================================================================================

// ──────────────────────────────────────────────
// 🟦 Subregion: HA Blend EMA Ladder
// ──────────────────────────────────────────────
//
// 📌 Purpose:
//     Builds the EMA ladder used by the preset-driven HA Blend engine.
//
// 🧩 Why this matters in this script:
//     HA Blend now uses one selected Fibonacci-style pair set instead of a
//     manually mixed toggle collection.
//
float ha_5 = ta.ema(haSignedPrepared, 5)
float ha_8 = ta.ema(haSignedPrepared, 8)
float ha_13 = ta.ema(haSignedPrepared, 13)
float ha_21 = ta.ema(haSignedPrepared, 21)
float ha_34 = ta.ema(haSignedPrepared, 34)
float ha_55 = ta.ema(haSignedPrepared, 55)
float ha_89 = ta.ema(haSignedPrepared, 89)
float ha_144 = ta.ema(haSignedPrepared, 144)
float ha_233 = ta.ema(haSignedPrepared, 233)

// ──────────────────────────────────────────────
// 🟦 Subregion: HA Blend Pair-Set Resolver
// ──────────────────────────────────────────────
//
// 📌 Purpose:
//     Resolves which preset pair ladder is active for HA Blend.
//
// 🧠 Presets:
//     • Fast     = 5/8, 8/13, 13/21, 21/34
//     • Balanced = 13/21, 21/34, 34/55, 55/89
//     • Slow     = 34/55, 55/89, 89/144, 144/233
//
float blendPair_1 =
     in_blendPairSet == "Fast" ? (ha_5 + ha_8) / 2 :
     in_blendPairSet == "Balanced" ? (ha_13 + ha_21) / 2 :
     (ha_34 + ha_55) / 2

float blendPair_2 =
     in_blendPairSet == "Fast" ? (ha_8 + ha_13) / 2 :
     in_blendPairSet == "Balanced" ? (ha_21 + ha_34) / 2 :
     (ha_55 + ha_89) / 2

float blendPair_3 =
     in_blendPairSet == "Fast" ? (ha_13 + ha_21) / 2 :
     in_blendPairSet == "Balanced" ? (ha_34 + ha_55) / 2 :
     (ha_89 + ha_144) / 2

float blendPair_4 =
     in_blendPairSet == "Fast" ? (ha_21 + ha_34) / 2 :
     in_blendPairSet == "Balanced" ? (ha_55 + ha_89) / 2 :
     (ha_144 + ha_233) / 2

// ──────────────────────────────────────────────
// 🟦 Subregion: HA Blend Pair Averaging
// ──────────────────────────────────────────────
//
// 📌 Purpose:
//     Averages the active preset's four pair midpoints into one blend engine.
//
float blendEngineCore =
     (blendPair_1 + blendPair_2 + blendPair_3 + blendPair_4) / 4

// ==============================================================================================
// 🔴 END REGION: Blend Engine
// ==============================================================================================

// ==============================================================================================
// 🟢 REGION: Selected Output Engine
// ==============================================================================================

float rangeEngine =
     in_useFinalSmooth ? f_ma(rangeEngineCore, in_finalSmoothLen, in_finalSmoothType) : rangeEngineCore

float blendEngine =
     in_useFinalSmooth ? f_ma(blendEngineCore, in_finalSmoothLen, in_finalSmoothType) : blendEngineCore


float osc =
     in_engineMode == "HA Range Base" ? rangeEngine : blendEngine

color rangeEngineColor =
     f_quadrantColor(
         rangeEngine,
         in_colAboveRise,
         in_colAboveFall,
         in_colBelowFall,
         in_colBelowRise)

color blendEngineColor =
     f_quadrantColor(
         blendEngine,
         in_colAboveRise,
         in_colAboveFall,
         in_colBelowFall,
         in_colBelowRise)

color oscColor =
     f_quadrantColor(
         osc,
         in_colAboveRise,
         in_colAboveFall,
         in_colBelowFall,
         in_colBelowRise)

// ==============================================================================================
// 🔴 END REGION: Selected Output Engine
// ==============================================================================================


// ==============================================================================================
// 🟢 REGION: Auxiliary Oscillator Series
// ==============================================================================================
//
// 📌 Purpose:
//     Builds all secondary oscillator-side support series that sit on top of the
//     selected unified HA oscillator (`osc`), but do not change the core engine itself.
//
// 🧠 What lives here:
//     • Adaptive crossover engine
//     • Standalone SMA 20 / 50 crossover engine
//     • Rolling oscillator VWA
//     • Pivot-overlay state + derived baseline
//     • Guide-line reference values
//     • Synthetic oscillator candle OHLC + colors
//
// 🧩 Why this matters in this script:
//     The core HA engine decides what the oscillator is.
//     This region decides how that oscillator is interpreted, smoothed visually,
//     and prepared for plots, fills, and overlays.
//
// ==============================================================================================

// ──────────────────────────────────────────────
// 🟦 Subregion: Adaptive Crossovers
// ──────────────────────────────────────────────
//
// 📌 Purpose:
//     Builds the primary timeframe-aware fast / slow crossover engine.
//
// 🧠 Behavior:
//     • Auto mode picks fast / slow lengths by chart timeframe bucket
//     • Manual mode uses the user's direct fast / slow inputs
//     • Uses the selected oscillator (`osc`) as the source
//     • Maintains a persistent bull / bear crossover state for line and fill color
//
// 🧩 Why this matters in this script:
//     This is the main crossover engine meant to adapt itself across chart speeds,
//     so lower timeframes stay responsive while higher timeframes stay more stable.
//

// Adaptive fast-length table by timeframe bucket.
int len_Fast_0 = 8
int len_Fast_1 = 5
int len_Fast_2 = 8
int len_Fast_3 = 13
int len_Fast_4 = 13
int len_Fast_5 = 21
int len_Fast_6 = 13
int len_Fast_7 = 8
int len_Fast_8 = 5

// Adaptive slow-length table by timeframe bucket.
int len_Slow_0 = 21
int len_Slow_1 = 13
int len_Slow_2 = 21
int len_Slow_3 = 34
int len_Slow_4 = 48
int len_Slow_5 = 48
int len_Slow_6 = 34
int len_Slow_7 = 21
int len_Slow_8 = 13

// Auto-select the adaptive fast length from the current chart timeframe bucket.
int crossFastLen_auto =
     timeframe.isseconds and timeframe.multiplier <= 45 ? len_Fast_0 :
     timeframe.isminutes and timeframe.multiplier <= 3 ? len_Fast_1 :
     timeframe.isminutes and timeframe.multiplier <= 10 ? len_Fast_2 :
     timeframe.isminutes and timeframe.multiplier <= 30 ? len_Fast_3 :
     timeframe.isminutes and timeframe.multiplier <= 120 ? len_Fast_4 :
     timeframe.isminutes and timeframe.multiplier <= 480 ? len_Fast_5 :
     timeframe.isdaily and timeframe.multiplier <= 34 ? len_Fast_6 :
     timeframe.isweekly and timeframe.multiplier <= 21 ? len_Fast_7 :
     timeframe.ismonthly and timeframe.multiplier <= 13 ? len_Fast_8 :
     in_fastLen_manual

// Auto-select the adaptive slow length from the current chart timeframe bucket.
int crossSlowLen_auto =
     timeframe.isseconds and timeframe.multiplier <= 45 ? len_Slow_0 :
     timeframe.isminutes and timeframe.multiplier <= 3 ? len_Slow_1 :
     timeframe.isminutes and timeframe.multiplier <= 10 ? len_Slow_2 :
     timeframe.isminutes and timeframe.multiplier <= 30 ? len_Slow_3 :
     timeframe.isminutes and timeframe.multiplier <= 120 ? len_Slow_4 :
     timeframe.isminutes and timeframe.multiplier <= 480 ? len_Slow_5 :
     timeframe.isdaily and timeframe.multiplier <= 34 ? len_Slow_6 :
     timeframe.isweekly and timeframe.multiplier <= 21 ? len_Slow_7 :
     timeframe.ismonthly and timeframe.multiplier <= 13 ? len_Slow_8 :
     in_slowLen_manual

// Resolve runtime lengths after the Auto / Manual decision.
int crossFastLen_rt =
     in_tfMode_Cross == "Auto" ? crossFastLen_auto : in_fastLen_manual

int crossSlowLen_rt =
     in_tfMode_Cross == "Auto" ? crossSlowLen_auto : in_slowLen_manual

// Build the adaptive fast / slow lines from the shared oscillator source.
float fastLine =
     f_ma(
         osc,
         crossFastLen_rt,
         in_fastMaType)

float slowLine =
     f_ma(
         osc,
         crossSlowLen_rt,
         in_slowMaType)

// Detect actual crossover events.
bool bullCross =
     ta.crossover(fastLine, slowLine)

bool bearCross =
     ta.crossunder(fastLine, slowLine)

// Persist the active crossover state so color stays stable between events.
var bool crossBullState = false
crossBullState := bullCross ? true : bearCross ? false : crossBullState

// Primary adaptive crossover colors.
color crossLineCol =
     crossBullState ? color.new(in_crossBullColor, 27) : color.new(in_crossBearColor, 27)

color crossFillCol =
     in_showCrossFill ? (crossBullState ? color.new(in_crossBullColor, 77) : color.new(in_crossBearColor, 77)) : na



// ──────────────────────────────────────────────
// 🟦 Subregion: SMA 20 / 50 Crossovers
// ──────────────────────────────────────────────
//
// 📌 Purpose:
//     Builds the standalone fixed-length crossover engine.
//
// 🧠 Behavior:
//     • Uses its own MA type inputs and lengths
//     • Runs independently of the adaptive crossover engine
//     • Lets users compare a fixed classic pair against the adaptive pair
//
// 🧩 Why this matters in this script:
//     Some users want a stable reference pair regardless of timeframe.
//     This second engine provides that without interfering with the adaptive one.
//

// Build the standalone SMA-engine fast / slow lines from the same oscillator source.
float fastLine_sma =
     f_ma(
         osc,
         in_Len_fast_sma,
         in_MaType_fast_sma)

float slowLine_sma =
     f_ma(
         osc,
         in_Len_slow_sma,
         in_MaType_slow_sma)

// Detect standalone crossover events.
bool bullCross_sma =
     ta.crossover(fastLine_sma, slowLine_sma)

bool bearCross_sma =
     ta.crossunder(fastLine_sma, slowLine_sma)

// Persist the fixed-engine crossover state.
var bool crossBullState_sma = false
crossBullState_sma := bullCross_sma ? true : bearCross_sma ? false : crossBullState_sma

// Standalone SMA-engine colors.
color crossLineCol_sma =
     crossBullState_sma ? color.new(in_crossBullColor, 25) : color.new(in_crossBearColor, 25)

color crossFillCol_sma =
     in_showFill_sma ? (crossBullState_sma ? color.new(in_crossBullColor, 75) : color.new(in_crossBearColor, 75)) : na


// ──────────────────────────────────────────────
// 🟦 Subregion: Rolling Oscillator VWA
// ──────────────────────────────────────────────
//
// 📌 Purpose:
//     Builds a volume-weighted average of the selected oscillator using the
//     same adaptive structure as the CE oscillator reference script.
//
// 🧠 Behavior:
//     • Supports Auto or Manual length selection
//     • Uses volume adjusted by its own SMA as the weighting factor
//     • Can later be displayed as Last 'N' Bars or hidden entirely
//
// 🧩 Why this matters in this script:
//     This makes the oscillator VWA more adaptive across timeframes and a bit
//     more participation-aware than the simpler rolling VWA version.
//

// Adaptive VWA length table by timeframe bucket.
int len_OscVwa_0 = 55
int len_OscVwa_1 = 21
int len_OscVwa_2 = 34
int len_OscVwa_3 = 48
int len_OscVwa_4 = 55
int len_OscVwa_5 = 48
int len_OscVwa_6 = 34
int len_OscVwa_7 = 21
int len_OscVwa_8 = 13

// Auto-select the VWA length from the current chart timeframe bucket.
int oscVwaLen_auto =
     timeframe.isseconds and timeframe.multiplier <= 45 ? len_OscVwa_0 :
     timeframe.isminutes and timeframe.multiplier <= 3 ? len_OscVwa_1 :
     timeframe.isminutes and timeframe.multiplier <= 10 ? len_OscVwa_2 :
     timeframe.isminutes and timeframe.multiplier <= 30 ? len_OscVwa_3 :
     timeframe.isminutes and timeframe.multiplier <= 120 ? len_OscVwa_4 :
     timeframe.isminutes and timeframe.multiplier <= 480 ? len_OscVwa_5 :
     timeframe.isdaily and timeframe.multiplier <= 34 ? len_OscVwa_6 :
     timeframe.isweekly and timeframe.multiplier <= 21 ? len_OscVwa_7 :
     timeframe.ismonthly and timeframe.multiplier <= 13 ? len_OscVwa_8 :
     in_oscVwaLenManual

// Resolve runtime length after the Auto / Manual decision.
int oscVwaLen =
     in_oscVwaLenMode == "Auto" ? oscVwaLen_auto : in_oscVwaLenManual

// Build a participation-adjusted volume weighting model.
float oscVwaVolAdj =
     volume / ta.sma(volume, oscVwaLen)

float oscVwaWeightedSum =
     math.sum(osc * oscVwaVolAdj, oscVwaLen)

float oscVwaWeightSum =
     math.sum(oscVwaVolAdj, oscVwaLen)

float oscVwa =
     oscVwaWeightSum != 0 ? oscVwaWeightedSum / oscVwaWeightSum : na

// CE-style constant VWA color.
color oscVwaColor =
     oscVwa > oscVwa[1] ? color.new(#00e5ff, 0) : color.new(#00e5ff, 0)


// ──────────────────────────────────────────────
// 🟦 Subregion: Trend Pivot Overlay State
// ──────────────────────────────────────────────
//
// 📌 Purpose:
//     Tracks confirmed oscillator pivots and derives the pivot-overlay baseline.
//
// 🧠 Behavior:
//     • Confirms pivots using left / right bar logic
//     • Keeps only positive pivots for highs and negative pivots for lows
//     • Stores the most recent confirmed pivot-high and pivot-low anchors
//     • Uses pivot order to infer whether the active structure is downtrend or uptrend
//     • Resolves a single overlay baseline from the chosen pivot mode
//
// 🧩 Why this matters in this script:
//     This gives the oscillator a structural bias line that can behave like a
//     regime baseline rather than just another moving average.
//

// Raw pivot detections on the oscillator itself.
float pivotLowRaw =
     ta.pivotlow(osc, in_leftBars, in_rightBars)

float pivotHighRaw =
     ta.pivothigh(osc, in_leftBars, in_rightBars)

// Keep only below-zero pivot lows and above-zero pivot highs.
// This preserves polarity-aware structure logic.
float pivotLow =
     osc[in_rightBars] < 0 ? pivotLowRaw : na

float pivotHigh =
     osc[in_rightBars] > 0 ? pivotHighRaw : na

bool isPivotLow =
     not na(pivotLow)

bool isPivotHigh =
     not na(pivotHigh)

// Persistent storage for the most recent confirmed pivot anchors.
var float overlayPivotHigh = na
var float overlayPivotLow = na
var int highPivotBar = na
var int lowPivotBar = na

// Update stored pivot-high anchor when a new oscillator high pivot confirms.
if isPivotHigh
    overlayPivotHigh := pivotHigh
    highPivotBar := bar_index - in_rightBars

// Update stored pivot-low anchor when a new oscillator low pivot confirms.
if isPivotLow
    overlayPivotLow := pivotLow
    lowPivotBar := bar_index - in_rightBars

// The overlay is ready only after both a confirmed high pivot and low pivot exist.
bool overlayReady =
     not na(overlayPivotHigh) and not na(overlayPivotLow)

// Trend direction is inferred from pivot order:
// if the latest stored high pivot is more recent than the low pivot, structure is downtrend.
bool isDowntrend =
     overlayReady ? highPivotBar > lowPivotBar : false

bool isDynamicMode =
     in_pivotMode == "Dynamic"

bool isPivotLowMode =
     in_pivotMode == "Pivot Low"

// Resolve the active overlay baseline.
// • Dynamic     → flips between pivot high / pivot low by structure
// • Pivot Low   → always use pivot low
// • Pivot High  → always use pivot high
float overlayLine =
     isDynamicMode ? (isDowntrend ? overlayPivotHigh : overlayPivotLow) :
     isPivotLowMode ? overlayPivotLow :
     overlayPivotHigh

// ──────────────────────────────────────────────
// 🟦 Subregion: Trend Pivot Overlay Line Object
// ──────────────────────────────────────────────
// Draws a true pivot-anchored horizontal segment that uses the same resolved
// baseline as the Trend Pivot Overlay plot, then colors it by oscillator position.
//
// Color model:
// • overlay above 0 = bull color
// • overlay below 0 = bear color
// • overlay exactly 0 = neutral plot color

var line overlayLineObj = na

int overlayAnchorBar =
     isDowntrend ? highPivotBar : lowPivotBar

color overlayObjColor =
     na(overlayLine) ? na :
     overlayLine > 0 ? in_overlayBearColor :
     overlayLine < 0 ? in_overlayBullColor :
     color.gray

if in_showTrendOverlayObj and overlayReady and not na(overlayLine) and not na(overlayAnchorBar)
    if na(overlayLineObj)
        overlayLineObj := line.new(
             x1=overlayAnchorBar,
             y1=overlayLine,
             x2=bar_index,
             y2=overlayLine,
             xloc=xloc.bar_index,
             color=overlayObjColor,
             width=1,
             style=line.style_solid)
    else
        line.set_xy1(overlayLineObj, overlayAnchorBar, overlayLine)
        line.set_xy2(overlayLineObj, bar_index, overlayLine)
        line.set_color(overlayLineObj, overlayObjColor)
else
    if not na(overlayLineObj)
        line.delete(overlayLineObj)
        overlayLineObj := na

// ──────────────────────────────────────────────
// 🟦 Subregion: Guide-Line Values
// ──────────────────────────────────────────────
//
// 📌 Purpose:
//     Precomputes the adaptive reference levels used by the oscillator guide plots.
//
// 🧠 How these guide lines are built:
//     • zeroLine is fixed at 0 and acts as the oscillator's neutral baseline
//     • upperGuide is set to 50% of the highest oscillator value seen over the current lookback
//     • lowerGuide is set to 50% of the lowest oscillator value seen over the current lookback
//
//     In other words, the ±50 guide lines are not hardcoded levels.
//     They are recalculated from the oscillator's own recent range:
//
//     • upperGuide = highest(osc, normPeriod) * 0.5
//     • lowerGuide = lowest(osc, normPeriod) * 0.5
//
// 🧩 Practical interpretation:
//     These lines act like adaptive "soft boundary" rails for the oscillator.
//     Because they are based on recent oscillator extremes, they expand when the
//     histogram / oscillator candles become more stretched, and contract when the
//     oscillator compresses.
//
//     This means they can behave similarly to overbought / oversold lines,
//     but only relative to the oscillator's recent behavior, not as fixed thresholds.
//
// 🧩 Why they visually move with the histogram / candles:
//     The guide lines do not read the candle plots directly.
//     They use the same underlying oscillator series (`osc`) that also drives:
//     • the histogram columns
//     • the oscillator candles
//
//     Since all of those visuals come from the same source series, the guide lines
//     naturally adapt along with the histogram and oscillator candles.
//
// 🧩 Why this matters in this script:
//     Keeping these values precomputed here makes the later plot section cleaner,
//     while also giving the script adaptive reference rails instead of static levels.
//

float zeroLine = 0.0

float upperGuide =
     ta.highest(osc, normPeriod) * 0.5

float lowerGuide =
     ta.lowest(osc, normPeriod) * 0.5

// ──────────────────────────────────────────────
// 🟦 Subregion: Threshold-Aware Visual Colors
// ──────────────────────────────────────────────
//
// 📌 Purpose:
//     Builds the shared threshold-aware visual colors used across the histogram,
//     oscillator candles, price-overlay candles, follow line, and Engine Table.
//
color rangeEngineVisualColor =
     f_visualThresholdColor(
         rangeEngine,
         upperGuide,
         lowerGuide,
         in_colAbove50Rise,
         in_colAboveRise,
         in_colAboveFall,
         in_colBelowFall,
         in_colBelow50Fall,
         in_colBelowRise)

color blendEngineVisualColor =
     f_visualThresholdColor(
         blendEngine,
         upperGuide,
         lowerGuide,
         in_colAbove50Rise,
         in_colAboveRise,
         in_colAboveFall,
         in_colBelowFall,
         in_colBelow50Fall,
         in_colBelowRise)

color oscVisualColor =
     f_visualThresholdColor(
         osc,
         upperGuide,
         lowerGuide,
         in_colAbove50Rise,
         in_colAboveRise,
         in_colAboveFall,
         in_colBelowFall,
         in_colBelow50Fall,
         in_colBelowRise)

// ──────────────────────────────────────────────
// 🟦 Subregion: Synthetic Oscillator Candle Series
// ──────────────────────────────────────────────
//
// 📌 Purpose:
//     Converts the oscillator into candle-ready OHLC values for `plotcandle()`.
//
// 🧠 Construction:
//     • open  = prior oscillator value
//     • high  = higher of prior/current oscillator value
//     • low   = lower of prior/current oscillator value
//     • close = current oscillator value
//
// 🧩 Why this matters in this script:
//     This gives the oscillator a candle-style readout, which can make acceleration,
//     deceleration, and bar-to-bar reversals easier to see than a line alone.
//

float oscOpen =
     osc[1]

float oscHigh =
     math.max(osc[1], osc)

float oscLow =
     math.min(osc[1], osc)

float oscClose =
     osc

color oscBodyColor =
     color.new(oscVisualColor, in_oscCandleTransp)

color oscEdgeColor =
     oscVisualColor


// ==============================================================================================
// 🔴 END REGION: Auxiliary Oscillator Series
// ==============================================================================================

// ==============================================================================================
// 🟢 REGION: OSC Pressure Envelope State
// ==============================================================================================
//
// 📌 Purpose:
//     Builds a continuous envelope around the active oscillator and turns
//     the fill on only when the oscillator reaches the user-defined trigger zones.
//
// ==============================================================================================

float oscPressureCenter = osc

float oscPressureUpper =
     oscPressureCenter + in_oscPressureEnvelopePad

float oscPressureLower =
     oscPressureCenter - in_oscPressureEnvelopePad

color oscPressureFillColor =
     not in_showOscPressureEnvelope ? na :
     osc >= in_oscPressureUpperTrigger ? color.new(in_oscPressureUpperFill, in_oscPressureFillTransp) :
     osc <= in_oscPressureLowerTrigger ? color.new(in_oscPressureLowerFill, in_oscPressureFillTransp) :
     na

// ==============================================================================================
// 🔴 END REGION: OSC Pressure Envelope State
// ==============================================================================================

// ================================================================================
// 🟢 REGION: Higher-Timeframe Oscillator Selector
// ================================================================================
//
// 📌 Purpose:
//     Resolves which higher timeframe should be used for the oscillator
//     reference line.
//
// 🧠 Behavior:
//     • Dynamic mode steps up to the next logical chart timeframe
//     • Manual mode uses the exact user-selected timeframe
//
// ================================================================================
f_getNextTf() =>
    string _nextTf = ""
    float _mult = timeframe.multiplier

    if timeframe.isseconds
        _nextTf :=
             timeframe.period == "1S"  ? "5S"  :
             timeframe.period == "5S"  ? "10S" :
             timeframe.period == "10S" ? "15S" :
             timeframe.period == "15S" ? "30S" :
             timeframe.period == "30S" ? "45S" :
             timeframe.period == "45S" ? "1"   : "1"

    else if timeframe.isminutes
        _nextTf :=
             _mult <= 1   ? "3"   :
             _mult <= 2   ? "4"   :
             _mult <= 3   ? "5"   :
             _mult <= 5   ? "10"  :
             _mult <= 10  ? "15"  :
             _mult <= 15  ? "30"  :
             _mult <= 30  ? "60"  :
             _mult <= 60  ? "120" :
             _mult <= 120 ? "240" :
             _mult <= 240 ? "480" : "D"

    else if timeframe.isdaily
        _nextTf := "1W"
    else if timeframe.isweekly
        _nextTf := "2W"
    else if timeframe.ismonthly
        _nextTf := "2M"
    else
        _nextTf := "1D"

    _nextTf

string selectedHtf =
     in_manualHtfSelection == "Chart" ? timeframe.period : in_manualHtfSelection

string htfTimeframe =
     in_useDynamicHtf ? f_getNextTf() : selectedHtf

// ================================================================================
// 🔴 END REGION: Higher-Timeframe Oscillator Selector
// ================================================================================

// ================================================================================
// 🟢 REGION: Higher-Timeframe Oscillator Sampling
// ================================================================================
//
// 📌 Purpose:
//     Requests the active oscillator from the selected higher timeframe and
//     stabilizes it for lower-timeframe use.
//
// 🧠 Behavior:
//     • Pulls `osc` from the selected HTF using request.security()
//     • Uses SCL_HTF.f_HTF_Float() to stabilize the returned series
//     • Prevents the HTF reference line from visually desyncing while the
//       higher-timeframe bar is still forming
//
// ================================================================================
f_secHtfOsc(_tf) =>
    request.security(
         symbol=syminfo.tickerid,
         timeframe=_tf,
         expression=osc,
         gaps=barmerge.gaps_off,
         lookahead=barmerge.lookahead_off)

float htfOscRaw =
     f_secHtfOsc(htfTimeframe)

float htfOscFixed =
     SCL_HTF.f_HTF_Float(
         _HTF=htfTimeframe,
         _source=htfOscRaw,
         _arrayHistory=2,
         _HTF_offset=0,
         _useLiveDataOnChartTF=true)

// ================================================================================
// 🔴 END REGION: Higher-Timeframe Oscillator Sampling
// ================================================================================

// ==============================================================================================
// 🟢 REGION: Histogram Follow Line
// ==============================================================================================
//
// 📌 Purpose:
//     Draws a left and right follow line from the current oscillator value.
//
// 🧠 Behavior:
//     • Right line extends forward from the current bar by X bars
//     • Left line extends backward from the current bar by X bars
//     • Both lines update every bar to stay locked to the live oscillator value
//
// ==============================================================================================

// ──────────────────────────────────────────────
// 🟦 Subregion: Inputs
// ──────────────────────────────────────────────

int in_histFollowLineWidth = 2 //input.int(
    //  defval=1,
    //  minval=1,
    //  maxval=10,
    //  title="Follow Line Width",
    //  group=G_HIST_LINE,
    //  tooltip=TT_FOLLOW_LINE_WIDTH)

int in_histLineLengthRight = 2 //input.int(
    //  defval=2,
    //  minval=0,
    //  maxval=100,
    //  title="Follow Line Length Right",
    //  group=G_HIST_LINE,
    //  tooltip=TT_FOLLOW_LINE_RIGHT)

int in_histLineLengthLeft = 1 //input.int(
    //  defval=1,
    //  minval=0,
    //  maxval=500,
    //  title="Follow Line Length Left",
    //  group=G_HIST_LINE,
    //  tooltip=TT_FOLLOW_LINE_LEFT)

// ──────────────────────────────────────────────
// 🟦 Subregion: Style Mapping
// ──────────────────────────────────────────────
lineStyle_follow = line.style_solid

// ──────────────────────────────────────────────
// 🟦 Subregion: Follow Line State
// ──────────────────────────────────────────────
color followLineColor = oscVisualColor

var line followLineRight = na
var line followLineLeft = na

int x2_followRight = bar_index + in_histLineLengthRight
int x2_followLeft = bar_index - in_histLineLengthLeft

// ──────────────────────────────────────────────
// 🟦 Subregion: Follow Line Updater
// ──────────────────────────────────────────────
f_updateFollowLine(line _line, int _x1, float _y1, int _x2, float _y2, color _col, int _width, _style) =>
    line _out = _line

    if na(_out)
        _out := line.new(
             x1=_x1,
             y1=_y1,
             x2=_x2,
             y2=_y2,
             xloc=xloc.bar_index,
             color=_col,
             width=_width,
             style=_style)
    else
        line.set_xy1(_out, _x1, _y1)
        line.set_xy2(_out, _x2, _y2)
        line.set_color(_out, _col)
        line.set_width(_out, _width)
        line.set_style(_out, _style)

    _out

// ──────────────────────────────────────────────
// 🟦 Subregion: Follow Line Updates
// ──────────────────────────────────────────────
followLineRight := f_updateFollowLine(
     followLineRight,
     bar_index,
     osc,
     x2_followRight,
     osc,
     followLineColor,
     in_histFollowLineWidth,
     lineStyle_follow)

followLineLeft := f_updateFollowLine(
     followLineLeft,
     bar_index,
     osc,
     x2_followLeft,
     osc,
     followLineColor,
     in_histFollowLineWidth,
     lineStyle_follow)

// ==============================================================================================
// 🔴 END REGION: Histogram Follow Line
// ==============================================================================================

// ==============================================================================================
// 🟢 REGION: Engine Table State
// ==============================================================================================

bool usingRangeEngine =
     in_engineMode == "HA Range Base"

bool usingBlendEngine =
     in_engineMode == "HA Blend"

string rangeRowLabelTxt =
     "Range Base " + (usingRangeEngine ? "✓" : "✕")

string blendRowLabelTxt =
     "HA Blend " + (usingBlendEngine ? "✓" : "✕")

string finalSmoothTxt =
     in_useFinalSmooth ?
     " | Smooth " + str.tostring(in_finalSmoothLen) + " " + in_finalSmoothType : ""

string rangeEngineInfoTxt =
     str.tostring(in_rangeLen) + " " + in_rangeMaType +
     finalSmoothTxt

// ──────────────────────────────────────────────
// 🟦 Subregion: HA Blend Pair-Set Table Text
// ──────────────────────────────────────────────
//
// 📌 Purpose:
//     Builds the HA Blend table text for the selected preset pair set.
//
// 🧠 Behavior:
//     • Top value row shows the selected preset
//     • Bottom value row shows the exact EMA pairs used by that preset
//
// 🧩 Why this matters in this script:
//     The HA Blend section now reflects one clean preset-driven ladder rather
//     than a long mixed list of manually enabled pairs.
//
string blendPairSetTxt =
     "Pair Set > " + in_blendPairSet

string blendPairsTxt =
     in_blendPairSet == "Fast" ? "5/8 | 8/13 | 13/21 | 21/34" :
     in_blendPairSet == "Balanced" ? "13/21 | 21/34 | 34/55 | 55/89" :
     "34/55 | 55/89 | 89/144 | 144/233"

float oscChange =
     osc - osc[1]

string oscValueTxt =
     na(osc) ? "—" : str.tostring(osc, "#.##")

string oscChangeTxt =
     na(oscChange) ? "—" :
     (oscChange > 0 ? "+" : "") + str.tostring(oscChange, "#.##")

string oscInfoTxt =
     oscValueTxt + " (" + oscChangeTxt + ")"

color oscRowBg =
     oscVisualColor

color oscRowTxt =
     f_tableContrastText(oscRowBg)

color engineHdrBg = color.rgb(0, 0, 0)
color engineHdrTxt = color.white

color engineNeutralBg = color.rgb(0, 0, 0)
color engineNeutralTxt = color.white

color rangeRowBg =
     usingRangeEngine ? rangeEngineVisualColor : engineNeutralBg

color rangeRowTxt =
     usingRangeEngine ? f_tableContrastText(rangeRowBg) : engineNeutralTxt

color blendRowBg =
     usingBlendEngine ? blendEngineVisualColor : engineNeutralBg

color blendRowTxt =
     usingBlendEngine ? f_tableContrastText(blendRowBg) : engineNeutralTxt

string chartTF_Pretty =
     TF.f_prettifyTF(timeframe.period)

string engineHdrLabelTxt =
     syminfo.ticker + " | " + chartTF_Pretty

var table engineInfoTable =
     table.new(
         f_tablePos(in_engineTablePos),
         2,
         5,
         bgcolor=color.new(color.black, 0),
         frame_color=color.new(color.white, 70),
         border_color=color.new(color.white, 80),
         border_width=1)

// ==============================================================================================
// 🔴 END REGION: Engine Table State
// ==============================================================================================

// ==============================================================================================
// 🟢 REGION: Engine Table Render
// ==============================================================================================

if barstate.islast
    _ts = f_tableTextSize(in_engineTableTextSize)

    if in_showEngineTable
        table.clear(engineInfoTable, 0, 0, 1, 4)
        table.merge_cells(engineInfoTable, 0, 0, 1, 0)
        table.merge_cells(engineInfoTable, 0, 3, 0, 4)

        table.cell(
             engineInfoTable,
             0,
             0,
             engineHdrLabelTxt,
             text_color=engineHdrTxt,
             bgcolor=engineHdrBg,
             text_size=_ts)

        table.cell(
             engineInfoTable,
             0,
             1,
             "OSC (Hist)",
             text_color=oscRowTxt,
             bgcolor=oscRowBg,
             text_size=_ts)

        table.cell(
             engineInfoTable,
             1,
             1,
             oscInfoTxt,
             text_color=oscRowTxt,
             bgcolor=oscRowBg,
             text_size=_ts)

        table.cell(
             engineInfoTable,
             0,
             2,
             rangeRowLabelTxt,
             text_color=rangeRowTxt,
             bgcolor=rangeRowBg,
             text_size=_ts)

        table.cell(
             engineInfoTable,
             1,
             2,
             rangeEngineInfoTxt,
             text_color=rangeRowTxt,
             bgcolor=rangeRowBg,
             text_size=_ts)

        table.cell(
             engineInfoTable,
             0,
             3,
             blendRowLabelTxt,
             text_color=blendRowTxt,
             bgcolor=blendRowBg,
             text_size=_ts)

        table.cell(
             engineInfoTable,
             1,
             3,
             blendPairSetTxt,
             text_color=blendRowTxt,
             bgcolor=blendRowBg,
             text_size=_ts)

        table.cell(
             engineInfoTable,
             1,
             4,
             blendPairsTxt,
             text_color=blendRowTxt,
             bgcolor=blendRowBg,
             text_size=_ts)
    else
        table.clear(engineInfoTable, 0, 0, 1, 4)

// ==============================================================================================
// 🔴 END REGION: Engine Table Render
// ==============================================================================================

// ==============================================================================================
// 🟢 REGION: Price Overlay Candle Colors
// ==============================================================================================
//
// 📌 Purpose:
//     Derives the colors used by the optional chart-overlay candles.
//
// ==============================================================================================

color priceOverlayBaseCol =
     not in_showPriceCandles ? na :
     in_useOscColorForPriceCandles ? oscVisualColor : na

color priceOverlayBodyCol =
     na(priceOverlayBaseCol) ? na : color.new(priceOverlayBaseCol, in_priceCandleBodyTransp)

color priceOverlayEdgeCol =
     priceOverlayBaseCol
// ==============================================================================================
// 🔴 END REGION: Price Overlay Candle Colors
// ==============================================================================================

// ==============================================================================================
// 🟢 REGION: Plots
// ==============================================================================================

// ──────────────────────────────────────────────
// Higher-Timeframe Oscillator
// Plots the stabilized higher-timeframe version of the active oscillator.
// ──────────────────────────────────────────────
plot(
     in_htfOscMode == "Last N Bars" ? htfOscFixed : na,
     title="Higher-Timeframe Oscillator (Last N Bars)",
     color=#0066ff,
     linewidth=1,
     show_last=in_htfOscShowLast)

// ──────────────────────────────────────────────
// Price Overlay Candles
// Draws oscillator-colored candles directly on the main chart using force_overlay.
// ──────────────────────────────────────────────
plotcandle(
     in_showPriceCandles ? open : na,
     in_showPriceCandles ? high : na,
     in_showPriceCandles ? low : na,
     in_showPriceCandles ? close : na,
     title="Price Overlay Candles (Last N Bars)",
     color=priceOverlayBodyCol,
     wickcolor=priceOverlayEdgeCol,
     bordercolor=priceOverlayEdgeCol,
     show_last=in_priceCandleShowLast,
     force_overlay=true)

// ──────────────────────────────────────────────
// Main Oscillator Histogram / Line
// Plots the active HA oscillator itself using the script's main color engine.
// ──────────────────────────────────────────────
plot(
     in_showHistogram ? osc : na,
     title="Unified HA Oscillator Histogram",
     style=plot.style_line,
     color=oscVisualColor,
     linewidth=2,
     show_last=in_plotLast)

// ──────────────────────────────────────────────
// Oscillator Candles
// Draws synthetic candles from oscillator OHLC instead of price OHLC.
// ──────────────────────────────────────────────
plotcandle(
     open=in_showOscCandles ? oscOpen : na,
     high=in_showOscCandles ? oscHigh : na,
     low=in_showOscCandles ? oscLow : na,
     close=in_showOscCandles ? oscClose : na,
     title="Oscillator Candles",
     color=oscBodyColor,
     wickcolor=oscEdgeColor,
     bordercolor=oscEdgeColor,
     show_last=in_plotLastCandles)

// ──────────────────────────────────────────────
// OSC Pressure Envelope
// Continuous envelope around osc with state-based fill.
// ──────────────────────────────────────────────
plotOscPressureUpper = plot(
     in_showOscPressureEnvelope ? oscPressureUpper : na,
     title="OSC Pressure Envelope Upper",
     color=color.new(color.white, 100),
     linewidth=1,
     show_last=in_plotLastCandles)

plotOscPressureLower = plot(
     in_showOscPressureEnvelope ? oscPressureLower : na,
     title="OSC Pressure Envelope Lower",
     color=color.new(color.white, 100),
     linewidth=1,
     show_last=in_plotLastCandles)

fill(
     plotOscPressureUpper,
     plotOscPressureLower,
     color=oscPressureFillColor,
     title="OSC Pressure Background")

// ──────────────────────────────────────────────
// Adaptive Fast Line
// Primary fast crossover line built from the adaptive crossover engine.
// ──────────────────────────────────────────────
plotFast = plot(
     in_showCrossLines ? fastLine : na,
     title="Fast Line",
     color=crossLineCol,
     linewidth=1,
     show_last=in_plotLast)

// ──────────────────────────────────────────────
// Adaptive Slow Line
// Primary slow crossover line built from the adaptive crossover engine.
// ──────────────────────────────────────────────
plotSlow = plot(
     in_showCrossLines ? slowLine : na,
     title="Slow Line",
     color=crossLineCol,
     linewidth=1,
     show_last=in_plotLast)

// ──────────────────────────────────────────────
// Adaptive Cross Fill
// Fills the space between the adaptive fast and slow crossover lines.
// ──────────────────────────────────────────────
fill(
     plotFast,
     plotSlow,
     color=crossFillCol,
     title="Cross Fill")

// ──────────────────────────────────────────────
// SMA Engine Fast Line
// Fast line for the standalone SMA 20 / 50 crossover engine.
// ──────────────────────────────────────────────
plotFast_sma = plot(
     in_showSignals_sma ? fastLine_sma : na,
     title="Fast Line (SMA Engine)",
     color=crossLineCol_sma,
     linewidth=1,
     show_last=in_plotLast)

// ──────────────────────────────────────────────
// SMA Engine Slow Line
// Slow line for the standalone SMA 20 / 50 crossover engine.
// ──────────────────────────────────────────────
plotSlow_sma = plot(
     in_showSignals_sma ? slowLine_sma : na,
     title="Slow Line (SMA Engine)",
     color=crossLineCol_sma,
     linewidth=1,
     show_last=in_plotLast)

// ──────────────────────────────────────────────
// SMA Engine Fill
// Fills the space between the standalone SMA-engine fast and slow lines.
// ──────────────────────────────────────────────
fill(
     plotFast_sma,
     plotSlow_sma,
     color=crossFillCol_sma,
     title="Signal Fill (SMA Engine)")

// ──────────────────────────────────────────────
// Oscillator VWA
// Plots the adaptive volume-weighted average of the active oscillator.
// ──────────────────────────────────────────────
plot(
     in_showOscVwa and in_oscVwaDisplayMode == "Last 'N' Bars" ? oscVwa : na,
     title="Oscillator VWA (Last N Bars)",
     color=oscVwaColor,
     linewidth=1,
     style=plot.style_circles,
     show_last=in_oscVwaShowLast)

// ──────────────────────────────────────────────
// Trend Overlay Plot Color
// Colors the plotted Trend Pivot Overlay by whether it sits above or below zero.
// ──────────────────────────────────────────────
color overlayPlotColor =
     na(overlayLine) ? na :
     overlayLine > 0 ? in_overlayBearColor :
     overlayLine < 0 ? in_overlayBullColor :
     color.gray

// ──────────────────────────────────────────────
// Trend Pivot Overlay
// Plots the active pivot-based baseline derived from confirmed oscillator pivots.
// ──────────────────────────────────────────────
plot(
     in_showTrendOverlay and overlayReady ? overlayLine : na,
     title="Trend Pivot Overlay",
     color=overlayPlotColor,
     linewidth=1,
     style=plot.style_stepline,
     show_last=in_plotLast)

// ──────────────────────────────────────────────
// Zero Line
// Fixed neutral baseline for the oscillator.
// ──────────────────────────────────────────────
plot(
     in_showZeroLine ? zeroLine : na,
     title="Zero Line",
     color=in_zeroLineColor,
     linewidth=1,
     style=plot.style_line,
     show_last=in_guideShowLast)

// ──────────────────────────────────────────────
// Upper +50% Guide
// Adaptive upper guide set to 50% of the recent positive oscillator extreme.
// ──────────────────────────────────────────────
plot(
     in_showHalfLines ? upperGuide : na,
     title="+50% Guide",
     color=in_upperGuideColor,
     linewidth=1,
     style=plot.style_line,
     show_last=in_guideShowLast)

// ──────────────────────────────────────────────
// Lower -50% Guide
// Adaptive lower guide set to 50% of the recent negative oscillator extreme.
// ──────────────────────────────────────────────
plot(
     in_showHalfLines ? lowerGuide : na,
     title="-50% Guide",
     color=in_lowerGuideColor,
     linewidth=1,
     style=plot.style_line,
     show_last=in_guideShowLast)

// ==============================================================================================
// 🔴 END REGION: Plots
// ==============================================================================================


// // ==============================================================================================
// // 🟢 REGION: Price Bar Coloring
// // ==============================================================================================

// barcolor(
//      in_colorPriceBars ? oscColor : na,
//      title="Oscillator Price Bar Color")

// // ==============================================================================================
// // 🔴 END REGION: Price Bar Coloring
// // ==============================================================================================


// plotchar(
//      osc >= in_oscPressureUpperTrigger,
//      title="Upper Trigger Active",
//      char="▲",
//      location=location.top,
//      color=color.orange)

// plotchar(
//      osc <= in_oscPressureLowerTrigger,
//      title="Lower Trigger Active",
//      char="▼",
//      location=location.bottom,
//      color=color.fuchsia)

Overview

Heikin Ashi Oscillator Trend Engine offers lookback modes, an engine choice including a Heikin Ashi blend, optional final smoothing, and adaptive fast and slow crossovers, plus a separate SMA 20 and 50 crossover with fills. A volume-weighted average of the oscillator and a higher-timeframe oscillator view can be shown. It imports two helper libraries for timeframe handling. The author notes live bars may update until they close.

Details

Published by

Focal-QuantAI on GitHub

Draws

Separate pane below the chart

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.

Settings

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

SettingTypeDefaultVariable
Lookback ModestringDynamicin_lookbackMode
  ↳ Manual Lookbackint144in_manualLookback
Engine ModestringHA Blendin_engineMode
  ↳ HA Range Base Lengthint55in_rangeLen
  ↳ HA Range Base MA TypestringEMAin_rangeMaType
  ↳ HA Blend Pair SetstringBalancedin_blendPairSet
Final Smoothing (Both Engines)stringOnin_finalSmoothMode
  ↳ Final Smoothing Lengthint3in_finalSmoothLen
  ↳ Final Smoothing TypestringEMAin_finalSmoothType
Adaptive CrossoversstringShow Crossoversin_crossMode
Fill CrossoversstringShow Fillin_crossFillMode
Signal Crossover ModestringAutoin_tfMode_Cross
Show all 40 settings
SettingTypeDefaultVariable
Fast Line TypestringEMAin_fastMaType
Fast Line Lengthint5in_fastLen_manual
Slow Line TypestringEMAin_slowMaType
Slow Line Lengthint21in_slowLen_manual
SMA 20/50 CrossoversstringShow Crossoversin_SignalsMode_sma
Fill SMA 20/50 CrossoversstringShow Fillin_FillMode_sma
Fast Line TypestringSMAin_MaType_fast_sma
Fast Line Lengthint20in_Len_fast_sma
Slow Line TypestringSMAin_MaType_slow_sma
Slow Line Lengthint50in_Len_slow_sma
Show Oscillator VWAstringShowin_showOscVwaMode
Oscillator VWA Display ModestringLast 'N' Barsin_oscVwaDisplayMode
Oscillator VWA - Show Last 'N' Barsint55in_oscVwaShowLast
Oscillator VWA Length ModestringAutoin_oscVwaLenMode
Oscillator VWA Lengthint55in_oscVwaLenManual
HTF ModestringDynamicin_htfMode
Manual Higher TimeframestringChartin_manualHtfSelection
HTF Oscillator Display ModestringLast N Barsin_htfOscMode
HTF Oscillator - Show Last 'N' Barsint55in_htfOscShowLast
Trend Pivot OverlaystringHidein_trendOverlayMode
Trend Pivot Overlay Object LinestringShowin_trendOverlayObjMode
Pivot Anchor ModestringDynamicin_pivotMode
Left Barsint21in_leftBars
Right Barsint5in_rightBars
Trend Overlay Bull Colorcolorcolor.orangein_overlayBullColor
Trend Overlay Bear Colorcolor#f51673in_overlayBearColor
Guide Lines Plot Last N Barsint233in_guideShowLast
Zero LinestringShowin_zeroLineMode
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

Schaff Trend Cycle

by everget
947 stars

This indicator plots the Schaff Trend Cycle in a separate pane. It subtracts a slow exponential moving average from a fast one, applies a stochastic cycle twice, and smooths each result to form a 0–100 oscillator. Line color follows the slope, or the upper and

Indicator v4 Oscillators & momentumTrend
Updated 12 days ago · GPL-3.0 GitHub repository of Schaff Trend Cycle

Beep Boop

by magic8bot
409 stars

This indicator calculates a modified MACD oscillator and signal line alongside a trend EMA filter, plotting the output as a stylized column histogram in a separate pane. Instead of plotting true MACD distance, it quantifies positive and negative histogram mome

Indicator v4 Oscillators & momentumTrend
Updated 3 years ago · MIT GitHub repository of Beep Boop

[RS]Standardized Trend Map

by ricardosantos79
261 stars

Standardized Trend Map calculates series momentum relative to active price range across 40 distinct lookback periods ranging from 2 to 80 bars on an exponential moving average source. Each standardized calculation rounds to an integer values of -1, 0, or 1 to

Indicator v4 TrendOscillators & momentum
Updated 4 years ago · None stated GitHub repository of [RS]Standardized Trend Map
228 stars

Multi-Timeframe Analysis Suite calculates trend direction and momentum conditions across five user-selected timeframes using moving averages, RSI, and Stochastic oscillators. It evaluates moving average slope and oscillator levels to compute trend alignment, m

Indicator v6 Screeners & dashboardsTrend
Updated 3 months ago · None stated GitHub repository of Multi-Timeframe Analysis Suite

ADX Histogram

by harryguiacorn
220 stars

This indicator calculates and displays the Average Directional Index (ADX) as a column histogram in a separate pane. It evaluates market trend strength by calculating directional movement metrics based on specified smoothing and DI length inputs. When the ADX

Indicator v5 TrendOscillators & momentum
Updated 1 year ago · None stated GitHub repository of ADX Histogram

More Oscillators & momentum scripts

Related on ForexR

Trade Your Charts With a Regulated Broker

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

Compare brokers