Adaptive Market Profile indicator – Auto Detect & Dynamic Activity Zones

stable
By bIyni3 in Other Published August 2025 👁 882 views 💬 0 comments

Description

WARNING: TAKES A LOOONG TIME T O CALCULATE EVEN ON 1 DAY! Created based on description of https://www.tradingview.com/script/TMLC5Wma-Adaptive-Market-Profile-Auto-Detect-Dynamic-Activity-Zones/ Generated with Grok + HaasScript AI Guide by Cosmo The Adaptive Market Profile Indicator is a sophisticated technical analysis tool designed for the HaasOnline Trade Server, built with HaasScript. It dynamically constructs a regression channel by automatically selecting the optimal lookback period based on the Pearson correlation coefficient (R), ensuring the channel aligns with the strongest market trends. Within this channel, the indicator calculates a market profile, identifying key price levels with high trading activity (based on volume or price touches) and plots these as parallel lines for easy visualization. This indicator is ideal for traders seeking to identify support and resistance zones, understand market structure, and pinpoint high-probability price levels for entries or exits. Its adaptive nature makes it versatile across various markets and timeframes, with customizable settings for fine-tuning. Features Adaptive Period Selection: Automatically determines the best lookback period (between user-defined min and max periods) using Pearson's R for optimal trend detection. Regression Channel: Plots upper, middle, and lower channel lines based on linear regression and standard deviation, adjustable via a deviation multiplier. Market Profile: Divides the channel into configurable zones (sections) and calculates activity based on volume or price touches, highlighting the most active price levels. Customizable Active Lines: Displays up to a user-defined number of high-activity price levels as parallel lines within the channel. Log Scale Support: Optional logarithmic price scaling for compatibility with log-based charts. Debug Mode: Includes verbose logging to aid in troubleshooting and understanding calculations. Performance Reporting: Outputs the optimal period and trend strength (R) via custom reports in the HaasOnline UI. Inputs General Settings Auto Trend Channel Period (Boolean, default: true): Enable/disable automatic period detection. Manual Period (Number, default: 50): Fixed lookback period if auto-detection is disabled. Min Scan Period (Number, default: 10): Minimum period for auto-detection. Max Scan Period (Number, default: 200): Maximum period for auto-detection. Deviation Multiplier (Number, default: 2.0): Multiplier for channel width (standard deviation * multiplier). Profile Settings Profile Sections (Number, default: 10): Number of zones to divide the channel into for market profile calculation. Use Volume for Profile (Boolean, default: true): Use volume data (true) or price touches (false) for profile calculation. Number of Active Lines (Number, default: 3): Number of high-activity price levels to plot. Log Scale (Boolean, default: false): Enable logarithmic price scaling. Debug Mode (Boolean, default: false): Enable verbose logging for debugging. Outputs Plotted Lines: Upper, middle, and lower regression channel lines (colored Red, Blue, and Green). Up to Number of Active Lines high-activity price levels (colored Green, Yellow, Orange, etc.). Custom Reports: Optimal Period: The selected lookback period. Trend Strength (R): The Pearson correlation coefficient for the chosen period.
HaasScript
-- Adaptive Market Profile Indicator
-- Automatically detects optimal regression channel period using Pearson's R and displays a dynamic market profile.

-- Inputs
InputGroupHeader("General Settings")
local auto_period = Input("Auto Trend Channel Period", true, "Enable automatic period detection based on trend strength.")
local manual_period = Input("Manual Period", 50, "Fixed period if auto is disabled.")
local min_period = Input("Min Scan Period", 10, "Minimum period for auto scanning.")
local max_period = Input("Max Scan Period", 200, "Maximum period for auto scanning.")
local dev_multiplier = Input("Deviation Multiplier", 2.0, "Multiplier for channel width (std dev * multiplier).")

InputGroupHeader("Profile Settings")
local sections = Input("Profile Sections", 10, "Number of zones to divide the channel into.")
local use_volume = Input("Use Volume for Profile", true, "If true, use volume; else, count touches (closes in zone).")
local num_active = Input("Number of Active Lines", 3, "Number of most active zones to plot as parallel lines.")
local log_scale = Input("Log Scale", false, "Perform calculations on log(prices) for log charts.")
local debug_mode = Input("Debug Mode", false, "Enable verbose logging for debugging.")

-- Utility Functions
local function sub_array(arr, len)
    local sub = {}
    for i = 1, len do
        sub[i] = arr[#arr - len + i] or 0
    end
    return sub
end

local function pearson_r(prices)
    local n = #prices
    if n < 2 then return 0 end
    local sum_x, sum_y, sum_xy, sum_x2, sum_y2 = 0, 0, 0, 0, 0
    for i = 1, n do
        local x = i
        local y = tonumber(prices[i]) or 0 -- Convert to number, fallback to 0
        sum_x = sum_x + x
        sum_y = sum_y + y
        sum_xy = sum_xy + x * y
        sum_x2 = sum_x2 + x * x
        sum_y2 = sum_y2 + y * y
        if debug_mode then
            Log("Pearson: Price[" .. i .. "] = " .. y)
        end
    end
    local mean_x = sum_x / n
    local mean_y = sum_y / n
    local cov = (sum_xy - n * mean_x * mean_y) / (n - 1)
    local var_x = (sum_x2 - n * mean_x * mean_x) / (n - 1)
    local var_y = (sum_y2 - n * mean_y * mean_y) / (n - 1)
    if var_x == 0 or var_y == 0 then return 0 end
    local r = cov / math.sqrt(var_x * var_y)
    if debug_mode then
        Log("Pearson R: " .. Round(r, 4))
    end
    return r
end

local function linear_regression(prices)
    local n = #prices
    local sum_x, sum_y, sum_xy, sum_x2 = 0, 0, 0, 0
    for i = 1, n do
        local x = i
        local y = tonumber(prices[i]) or 0 -- Convert to number, fallback to 0
        sum_x = sum_x + x
        sum_y = sum_y + y
        sum_xy = sum_xy + x * y
        sum_x2 = sum_x2 + x * x
        if debug_mode then
            Log("Regression: Price[" .. i .. "] = " .. y)
        end
    end
    local denom = (n * sum_x2 - sum_x * sum_x)
    if denom == 0 then return 0, prices[1] or 0 end
    local slope = (n * sum_xy - sum_x * sum_y) / denom
    local intercept = (sum_y - slope * sum_x) / n
    if debug_mode then
        Log("Slope: " .. Round(slope, 4) .. ", Intercept: " .. Round(intercept, 4))
    end
    return slope, intercept
end

local function get_std_residuals(prices, slope, intercept)
    local n = #prices
    local sum_sq = 0
    for i = 1, n do
        local predicted = intercept + slope * i
        local price = tonumber(prices[i]) or 0 -- Convert to number, fallback to 0
        sum_sq = sum_sq + (price - predicted) ^ 2
        if debug_mode then
            Log("StdDev: Price[" .. i .. "] = " .. price .. ", Predicted = " .. predicted)
        end
    end
    local std = n > 2 and math.sqrt(sum_sq / (n - 2)) or 0
    if debug_mode then
        Log("Standard Deviation: " .. Round(std, 4))
    end
    return std
end

-- Main Logic
local prices = ClosePrices()
local volumes = GetVolume()

-- Ensure prices and volumes are valid
if #prices < 2 or #volumes < 2 then
    LogError("Insufficient data for calculation. Prices: " .. #prices .. ", Volumes: " .. #volumes)
    return
end

-- Apply log scale if enabled
if log_scale then
    local temp_prices = {}
    for i = 1, #prices do
        local price = tonumber(prices[i]) or 0
        temp_prices[i] = price > 0 and math.log(price) or 0
        if debug_mode then
            Log("Log Scale: Price[" .. i .. "] = " .. price .. ", Log = " .. temp_prices[i])
        end
    end
    prices = temp_prices
end

-- Auto period detection
local best_len = manual_period
local best_r = 0
if auto_period then
    for len = min_period, max_period do
        if len > #prices then break end
        local sub = sub_array(prices, len)
        local r = math.abs(pearson_r(sub))
        if r > best_r then
            best_r = r
            best_len = len
        end
    end
end

if best_len > #prices then best_len = #prices end
if best_len < 2 then best_len = 2 end
if debug_mode then
    Log("Selected Period: " .. best_len .. ", Pearson R: " .. Round(best_r, 4))
end

local sub_prices = sub_array(prices, best_len)
local sub_volumes = sub_array(volumes, best_len)

local slope, intercept = linear_regression(sub_prices)
local std = get_std_residuals(sub_prices, slope, intercept)
local dev = std * dev_multiplier

local n = best_len
local middle = intercept + slope * n
local upper = middle + dev
local lower = middle - dev

-- Reverse log scale for plotting
if log_scale then
    middle = math.exp(middle)
    upper = math.exp(upper)
    lower = math.exp(lower)
end

-- Plot the Channel
Plot(0, "Upper Channel", upper, Red)
Plot(0, "Middle Channel", middle, Blue)
Plot(0, "Lower Channel", lower, Green)
if debug_mode then
    Log("Plotting Channels: Upper = " .. Round(upper, 4) .. ", Middle = " .. Round(middle, 4) .. ", Lower = " .. Round(lower, 4))
end

-- Market Profile Calculation
local bins = {}
for i = 1, sections do bins[i] = 0 end

for j = 1, best_len do
    local past_price = tonumber(sub_prices[j]) or 0
    local predicted = intercept + slope * j
    local lower_at_j = predicted - dev
    local upper_at_j = predicted + dev
    if upper_at_j > lower_at_j then
        local rel_pos = (past_price - lower_at_j) / (upper_at_j - lower_at_j)
        local bin_idx = math.floor(rel_pos * sections) + 1
        if bin_idx < 1 then bin_idx = 1 elseif bin_idx > sections then bin_idx = sections end
        local add = use_volume and (tonumber(sub_volumes[j]) or 0) or 1
        bins[bin_idx] = bins[bin_idx] + add
        if debug_mode then
            Log("Profile: Price[" .. j .. "] = " .. past_price .. ", Bin[" .. bin_idx .. "] += " .. add)
        end
    end
end

-- Find Top N Active Bins
local bin_list = {}
for i = 1, sections do
    table.insert(bin_list, {idx = i, val = bins[i]})
end
table.sort(bin_list, function(a, b) return a.val > b.val end)

-- Plot Active Lines (using only 4 arguments: chartId, name, value, color)
local colors = {Green, Yellow, Orange, Red, Purple}
for k = 1, math.min(num_active, sections) do
    local bin_idx = bin_list[k].idx
    local f = (bin_idx - 0.5) / sections
    local active_line = lower + f * (upper - lower)
    Plot(0, "Active Line " .. k, active_line, colors[k] or White) -- Removed Dashed to avoid 5th argument
    if debug_mode then
        Log("Plotting Active Line " .. k .. ": Value = " .. Round(active_line, 4) .. ", Color = " .. tostring(colors[k] or White))
    end
end

-- Reporting
Finalize(function()
    CustomReport("Optimal Period", best_len)
    CustomReport("Trend Strength (R)", Round(best_r, 4))
end)

0 Comments

Sign in to leave a comment.

No comments yet. Be the first!