TradingView indicators

Ulcer Index on TradingView: formula and limits

The Ulcer Index indicator measures the root mean square of percentage drawdowns. This guide implements a rolling version: each close is compared with its own N-bar highest close, and the latest N squared drawdowns are averaged before taking the square root. Use the worked table and Pine Script to check the values on your chart. A low reading does not establish that a trade is safe.

Input: closing pricesWindow unit: chart barsOutput: percentage points

Define the peak before calculating UI

For each bar t, define H(t) as the highest close from t−N+1 through t, including the current bar. Set D(t) = 100 × (Close(t) / H(t) − 1). Then calculate UI(t) as the square root of the mean of D² from t−N+1 through t. Each historical drawdown keeps the peak calculated at its own bar; do not apply today's peak retrospectively to every close.

H(t) = max(Close[t−N+1 … t])
D(t) = 100 × (Close(t) / H(t) − 1)
UI(t) = √((D(t)² + … + D(t−N+1)²) / N)

This rolling formula follows StockCharts' calculation. Peter G. Martin's original algorithm keeps the highest value reached since the start of the measurement period. Those peak definitions can give different results. State which one you use before comparing a chart indicator with a portfolio report.

Squaring gives a 10% drawdown four times the contribution of a 5% drawdown: 100 versus 25. Taking the square root restores the percentage scale. UI still aggregates a path; it is neither the current drawdown nor a forecast of a future loss.

Worked example: the old peak leaves the window

These are invented closing prices for a calculation check, not market data or a backtest. Use N = 3 for both windows. The script waits for a full peak window, followed by three valid drawdowns.

Hypothetical three-bar rolling Ulcer Index calculation
BarClose3-bar peakDrawdown %Squared drawdownUI %
1100Not readyNot readyNot readyNot ready
2100Not readyNot readyNot readyNot ready
310010000Not ready
490100-10100Not ready
590100-101008.1650
69090008.1650
79090005.7735
89090000.0000

At bar 5, UI = √((0 + 100 + 100) / 3) = 8.1650. At bar 6, the peak becomes 90 and the new drawdown is zero, but the average still contains two squared drawdowns of 100. At bar 7 only one remains: √(100 / 3) = 5.7735. By bar 8 UI is zero although price is still 10% below the old 100 peak. A falling rolling UI can reflect an expiring window without a price recovery.

Pine Script v6 with explicit confirmation

Paste this indicator into TradingView's Pine Editor and add it to a chart. The Data Window exposes the peak and drawdown for comparison. The default N = 14 is an example setting; the editable reference line does not affect the calculation. The script creates no orders.

Pine Script v6

//@version=6
indicator("Ulcer Index: rolling close", overlay=false, precision=4)

length = input.int(14, "Window (chart bars)", minval=1)
reference = input.float(5.0, "Reference line (not a trade rule)", minval=0.0)
confirmedOnly = input.bool(true, "Show only confirmed bars")

// Require N consecutive positive closes for each rolling peak.
validCloses = math.sum(not na(close) and close > 0 ? 1.0 : 0.0, length)
peak = ta.highest(close, length)
drawdown = validCloses == length ? 100.0 * (close / peak - 1.0) : na

// Require N consecutive valid drawdowns; do not bridge missing values.
validDrawdowns = math.sum(not na(drawdown) ? 1.0 : 0.0, length)
squaredSum = math.sum(nz(drawdown * drawdown, 0.0), length)
ui = validDrawdowns == length ? math.sqrt(squaredSum / length) : na
visibleUI = not confirmedOnly or barstate.isconfirmed ? ui : na

plot(visibleUI, "Ulcer Index (%)", color=color.blue, linewidth=2, style=plot.style_linebr)
hline(reference, "User reference", color=color.gray)
plot(peak, "Rolling highest close", display=display.data_window)
plot(drawdown, "Drawdown (%)", display=display.data_window)

crossedAbove = ta.crossover(ui, reference)
alertcondition(barstate.isconfirmed and crossedAbove, "UI crossed reference at close", "Ulcer Index crossed above the user reference on a closed bar.")

With positive, available closes, the first full UI needs 2N−1 bars: 27 bars when N = 14, or 5 when N = 3. An invalid close interrupts initialization. Both rolling sums run on every bar, including the unfinished bar; the confirmation setting controls visibility afterward.

Window units and when a reading is final

SettingMeaning in this scriptCheck before comparing
Window NN chart bars in each of the two windowsDaily, weekly and hourly bars cover different histories.
Reference lineVisual level and alert comparison onlyThere is no universal risk cutoff of 3, 5 or 8.
Confirmed barsHide the open bar until its closing updateTurning this off displays provisional values.
Price seriesPositive chart closesMatch symbol, adjustment settings, session and chart type.

TradingView indicators recalculate during an open realtime bar. Its close, rolling peak and drawdown can change. This example uses barstate.isconfirmed to publish the current reading at bar close and to gate the reference-crossing alert. Create the alert with the script's named condition and choose Once Per Bar Close. This behavior is documented in TradingView bar states and the execution model.

Using ui[1] instead would show the previous bar's value, with one bar of delay. The code uses the chart timeframe and does not request higher-timeframe data. Adding such a request requires a separate confirmation design; this example does not establish its behavior.

Ulcer Index, standard deviation and maximum drawdown

MeasureCalculation questionBoundary
Rolling Ulcer IndexHow large are recent percentage drawdowns, after squaring and averaging?Depends on rolling peak and averaging windows; retains no permanent high-water mark.
Standard deviation of returnsHow dispersed are returns around their mean?Includes deviations in both directions. Specify the return interval and annualization.
Maximum drawdownWhat is the largest peak-to-later-trough percentage decline in the chosen period?Summarizes the worst decline, without averaging all underwater observations. It can also update on a rolling basis.

A price-based UI describes the asset's closes. A strategy equity curve also depends on exposure, entries, exits and costs. To compare strategy risk, first align the equity sampling and peak convention. UI alone provides no return target or evidence that an entry will work.

Check the script before adding trade rules

  1. Fix the symbol, chart type, timeframe, session and price adjustments. Record N and the number of available bars.
  2. At a closed bar, compare the Data Window peak with the highest close in that bar's own N-bar window.
  3. Recalculate each of the last N drawdowns using its own peak. Square, average and take the square root; compare with the plotted UI.
  4. Inspect a new high and a bar where an old high expires. Use the worked example to distinguish price recovery from window expiry.
  5. Check the open bar with confirmed mode on and off. Define exits, position size, commission and slippage separately before turning the indicator into a strategy.

Describe the exact implementation to Pineify

Ask the Pine Script AI Coding Agent to preserve the rolling peak, positive-close checks, full-window initialization and bar-close confirmation. Request the intermediate values so you can compare the result bar by bar before adding trading logic.

Build a Pine v6 Ulcer Index using each bar's N-bar highest close. Average N squared percentage drawdowns and take the square root. Wait for full valid windows. Plot only confirmed bars by default. Show peak and drawdown in the Data Window. Do not add entry or exit rules.
Open Pine Script AI Coding Agent

Educational calculation example. The indicator and reference alert do not establish suitability, profitability or a safe level of exposure.

Ulcer Index questions

Ulcer Index measures the root mean square of percentage drawdowns. This page uses a rolling highest close for each bar, then averages the latest N squared drawdowns. It describes downside price behavior; it does not forecast the next decline or measure the risk of a short position.

No. UI is an aggregate of squared drawdowns. A value of 5 can result from different paths, and the current drawdown can be above or below 5%. The reference line in the script is an editable display setting, not a universal buy, sell, or position-sizing threshold.

With rolling peaks, an old high can leave the peak window. Later bars then have smaller or zero drawdowns against that lower peak. As earlier squared drawdowns leave the averaging window, UI falls. A running high-water-mark implementation would retain the old high and produce different values.

The script counts 14 chart bars. On a daily chart those are 14 daily bars; on a 1-hour chart they are 14 hourly bars. Session gaps and market calendars affect elapsed time. The peak window and drawdown averaging window each use N bars, so the first fully initialized value requires 2N minus 1 consecutive valid bars.

Yes. The current close changes during an open realtime bar, so its peak, drawdown and UI can change. The script hides the unfinished UI by default and gates its alert with barstate.isconfirmed. Reading ui[1] instead uses the preceding bar and adds one bar of delay.

No. It means the drawdowns included in this calculation are zero. The calculation does not include future losses, liquidity, leverage, transaction costs, or a short position's exposure to rising prices. Even a price below an older high can eventually have a zero rolling UI, as the worked example shows.

Sources and related reading

Sources checked October 17, 2026. Calculation table is an original hypothetical example.