Plot weighted average of two ETFs on TradingView Supercharts

Summary

This incident examines why a seemingly simple request—plotting a weighted average of two ETF percent‑change series in TradingView Supercharts—fails when implemented naively. Although the user expected Pine Script to directly combine percent‑scaled overlays, the underlying data model of TradingView makes this non‑trivial. The postmortem breaks down what went wrong, why it happens, and how senior engineers typically solve it.

Root Cause

The failure stemmed from a misunderstanding of how TradingView handles percent‑scaled charts:

  • Percent‑scaled charts do not expose the percent‑change values to Pine Script.
  • Pine Script always receives raw price series, not the transformed percent‑scale values shown on the chart.
  • Attempting to compute 0.3 * ETF1 + 0.7 * ETF2 using raw prices produces a meaningless result.
  • The user expected Pine Script to operate on the visualized percent‑change curves, but Pine Script only operates on data, not chart transformations.

Why This Happens in Real Systems

This is a classic example of UI transformations not matching backend data:

  • Charting platforms often apply visual transformations (percent scale, log scale, normalization) that are not part of the underlying data pipeline.
  • Engineers assume the plotted values are the same values available to scripts.
  • TradingView’s architecture separates:
    • Data series (raw OHLC)
    • Visual transforms (percent, log, compare mode)
  • Pine Script only sees the former.

Real-World Impact

This mismatch leads to:

  • Incorrect portfolio‑weight calculations
  • Confusing or misleading overlays
  • Scripts that “look right” but compute the wrong thing
  • Hours wasted debugging a problem caused by a hidden architectural constraint

Example or Code (if necessary and relevant)

Below is the correct pattern: manually compute percent‑change yourself, then compute the weighted average.

//@version=5
indicator("Weighted ETF Portfolio", overlay=true)

// Input tickers
etf1 = input.symbol("SPY", "ETF 1")
etf2 = input.symbol("QQQ", "ETF 2")

// Weights
w1 = input.float(0.3, "Weight 1")
w2 = input.float(0.7, "Weight 2")

// Request close prices
c1 = request.security(etf1, timeframe.period, close)
c2 = request.security(etf2, timeframe.period, close)

// Compute percent change from 1 year ago
c1_start = c1[252]
c2_start = c2[252]

pct1 = (c1 - c1_start) / c1_start
pct2 = (c2 - c2_start) / c2_start

// Weighted portfolio percent change
portfolio = w1 * pct1 + w2 * pct2

plot(portfolio, color=color.orange, title="Weighted Portfolio")

How Senior Engineers Fix It

Experienced engineers recognize the architectural constraint and work with the data model, not against it:

  • They recreate percent‑change manually instead of relying on the chart’s percent‑scale transform.
  • They compute the weighted portfolio using normalized series, not raw prices.
  • They validate the output by comparing:
    • Raw price series
    • Normalized series
    • Weighted composite
  • They avoid assumptions about what the UI exposes to scripts.

Why Juniors Miss It

Juniors often fall into predictable traps:

  • They assume what they see is what the script sees.
  • They expect Pine Script to access visual transformations.
  • They don’t yet recognize the separation between data pipelines and rendering pipelines.
  • They try to combine series without normalizing them first.
  • They underestimate how often charting tools hide complexity behind simple UI features.

This incident is a perfect reminder that visualization ≠ data, and scripts must explicitly compute what the UI only displays.

Leave a Comment