TradingView indicator guides

McGinley Dynamic indicator: formula and limits

The McGinley Dynamic indicator smooths price with a changing recursive weight. For positive prices and a positive previous line value, the fourth-power denominator reduces that weight when price is above the line and increases it when price is below. The line can lag, and a sufficiently large drop can make it overshoot.

Use this guide to check a TradingView implementation bar by bar. The supplied Pine Script uses close, N=14 and an EMA initialization branch. It has no separate k multiplier, input controls or trading signals.

No-k form used in the supplied script

MD[t] = MD[t-1] + (P[t] - MD[t-1]) / (N × (P[t] / MD[t-1])⁴)

P is the source price; N is a positive smoothing parameter. This formula has a division by the previous MD and by the full denominator.

N and k describe a convention, not interchangeable settings

John R. McGinley Jr. described the fourth-power form in his 1997 MTA Journal article. He set N to 60% of the conventional moving-average length being emulated: a target length of 20 gives N=12. The article also distinguishes the fourth-power revision from an earlier formula. Read McGinley's paper, pages 16 to 18.

A version written with denominator k × L × (P/previous MD)⁴ can represent that convention with k=0.6. It matches the N form only when N=kL and the source, seed and subsequent data also match. N=14 and k=0.6, L=14 have different effective denominators, 14 and 8.4 before the ratio term. Neither label alone identifies the calculation.

For a positive ratio r, the effective weight is α=1/(N × r⁴). When r is above 1, α is below 1/N. When r is between 0 and 1, α is above 1/N. This responds to the price-to-line ratio; the formula does not directly measure volume, trend efficiency or volatility.

Near r=1, N=14 gives a weight near 1/14, about 7.14%. An EMA(14) uses 2/15, about 13.33%. Calling MD always faster or zero lag is therefore unsupported. These are local coefficient comparisons, not a ranking of trading performance.

Worked example: the same gap produces unequal updates

These are independent hypothetical one-step calculations, each starting with previous MD=100. L=10 and D=kL(P/100)⁴. The k=1 rows are the no-k formula with N=10; they illustrate the mechanism rather than the supplied script's fixed N=14. Values are rounded only for display.

Hypothetical McGinley updates from previous MD 100
Price PLength LkDWeight 1/DNew MD
11010114.64100.068301100.6830
901016.56100.15241698.4758
110100.68.78460.113836101.1384
90100.63.93660.25402697.4597
501010.62501.60000020.0000
50100.60.37502.666667-33.3333

For price 90 and k=1, D=10 × 0.9⁴=6.561, so MD=100−10/6.561=98.4758. The equal-sized rise to 110 gives only a 0.6830 increase. An EMA(10) starting at 100 would move 1.8182 in either direction because its weight is fixed at 2/11.

For price 50 and k=1, D=0.625, so MD=100−50/0.625=20. That is below the current price. With k=0.6, the result is −33.3333 despite both input prices being positive. This is arithmetic overshoot, not a bearish forecast.

For positive P and previous MD, a nonzero gap overshoots when D<1. In the no-k form, that means P/previous MD < N^(−1/4). With N=10 the threshold is about 0.56234. A large upward ratio instead increases D and reduces the fraction of the gap closed.

Zero, negative prices and initialization need explicit rules

  • A previous MD of zero makes P/MD undefined. A source of zero with nonzero previous MD makes the full denominator zero. The supplied Pine code has no guard for either division.
  • A negative price or previous MD can still produce a positive fourth power, but that does not make the smoothing interpretation reliable. Crossing zero can create very small ratios and extreme updates. Use a method designed for signed data when smoothing spreads or oscillators.
  • A very small positive ratio can produce a huge update, including a nonpositive next MD. A very large ratio produces a small weight; extreme values can also exceed numerical range. A price split, bad tick or data discontinuity deserves inspection before treating a jump as a signal.
  • The code computes an EMA on every bar and uses it whenever the previous MD is unavailable. It does not wait for an N-bar SMA seed. A later unavailable MD also re-enters this branch, so this is a restart rule as well as a startup rule.

Choose an invalid-data policy before adapting the script: stop with an error, leave a gap, hold the last valid value, or reset on a valid positive observation. Also decide whether to reject a nonpositive result. Clamping D to at least 1 prevents arithmetic overshoot but changes the formula. Record such changes when comparing implementations.

TA-Lib's MCGD documentation describes its own first-price seed and invalid-step handling. Those rules are implementation choices and are not present in the Pine code below. Different starting history and seeds can affect later recursive values; a single matching bar does not establish equivalence.

Pine Script: inspect the existing N=14 example

This existing Pineify example plots one blue MD line. The code is preserved below, including generated helper functions. Copy it into TradingView's Pine Editor and add it to a chart. Its unguarded calculation is intended for ordinary positive price data; inspect the boundary cases before adapting it to other series.

SettingCode valueEffect
SourcecloseChart closing price; changes on an open bar.
N14Direct denominator parameter; fixed in the function call.
Separate kNoneEquivalent to k=1 if length is still 14.
Seed/restartEMA(source, length)Used when the previous MD is unavailable.
Signals and alertsNoneNo entries, exits or confirmed-cross rule.
Pine Script v6

Copy code

// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Pineify

//======================================================================//
//                    ____  _            _  __                          //
//                   |  _ (_)_ __   ___(_)/ _|_   _                    //
//                   | |_) | | '_ \ / _ \ | |_| | | |                   //
//                   |  __/| | | | |  __/ |  _| |_| |                   //
//                   |_|   |_|_| |_|\___|_|_|  \__, |                   //
//                                             |___/                    //
//======================================================================//

//@version=6
indicator(title="McGinley dynamic", overlay=true, max_labels_count=500)

//#region —————————————————————————————————————————————————— Custom Code

//#endregion ————————————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Common Dependence

p_comm_time_range_to_unix_time(string time_range, int date_time = time, string timezone = syminfo.timezone) =>
    int start_unix_time = na
    int end_unix_time = na
    int start_time_hour = na
    int start_time_minute = na
    int end_time_hour = na
    int end_time_minute = na
    if str.length(time_range) == 11
        // Format: hh:mm-hh:mm
        start_time_hour := math.floor(str.tonumber(str.substring(time_range, 0, 2)))
        start_time_minute := math.floor(str.tonumber(str.substring(time_range, 3, 5)))
        end_time_hour := math.floor(str.tonumber(str.substring(time_range, 6, 8)))
        end_time_minute := math.floor(str.tonumber(str.substring(time_range, 9, 11)))
    else if str.length(time_range) == 9
        // Format: hhmm-hhmm
        start_time_hour := math.floor(str.tonumber(str.substring(time_range, 0, 2)))
        start_time_minute := math.floor(str.tonumber(str.substring(time_range, 2, 4)))
        end_time_hour := math.floor(str.tonumber(str.substring(time_range, 5, 7)))
        end_time_minute := math.floor(str.tonumber(str.substring(time_range, 7, 9)))
    start_unix_time := timestamp(timezone, year(date_time, timezone), month(date_time, timezone), dayofmonth(date_time, timezone), start_time_hour, start_time_minute, 0)
    end_unix_time := timestamp(timezone, year(date_time, timezone), month(date_time, timezone), dayofmonth(date_time, timezone), end_time_hour, end_time_minute, 0)
    [start_unix_time, end_unix_time]

p_comm_time_range_to_start_unix_time(string time_range, int date_time = time, string timezone = syminfo.timezone) =>
    int start_time_hour = na
    int start_time_minute = na
    if str.length(time_range) == 11
        start_time_hour := math.floor(str.tonumber(str.substring(time_range, 0, 2)))
        start_time_minute := math.floor(str.tonumber(str.substring(time_range, 3, 5)))
    else if str.length(time_range) == 9
        start_time_hour := math.floor(str.tonumber(str.substring(time_range, 0, 2)))
        start_time_minute := math.floor(str.tonumber(str.substring(time_range, 2, 4)))
    timestamp(timezone, year(date_time, timezone), month(date_time, timezone), dayofmonth(date_time, timezone), start_time_hour, start_time_minute, 0)

p_comm_time_range_to_end_unix_time(string time_range, int date_time = time, string timezone = syminfo.timezone) =>
    int end_time_hour = na
    int end_time_minute = na
    if str.length(time_range) == 11
        end_time_hour := math.floor(str.tonumber(str.substring(time_range, 6, 8)))
        end_time_minute := math.floor(str.tonumber(str.substring(time_range, 9, 11)))
    else if str.length(time_range) == 9
        end_time_hour := math.floor(str.tonumber(str.substring(time_range, 5, 7)))
        end_time_minute := math.floor(str.tonumber(str.substring(time_range, 7, 9)))
    timestamp(timezone, year(date_time, timezone), month(date_time, timezone), dayofmonth(date_time, timezone), end_time_hour, end_time_minute, 0)

p_comm_timeframe_to_seconds(simple string tf) =>
    float seconds = 0
    tf_lower = str.lower(tf)
    value = str.tonumber(str.substring(tf_lower, 0, str.length(tf_lower) - 1))
    if str.endswith(tf_lower, 's')
        seconds := value
    else if str.endswith(tf_lower, 'd')
        seconds := value * 86400
    else if str.endswith(tf_lower, 'w')
        seconds := value * 604800
    else if str.endswith(tf_lower, 'm')
        seconds := value * 2592000
    else
        seconds := str.tonumber(tf_lower) * 60
    seconds

p_custom_sources() =>
    [open, high, low, close, volume]

//#endregion —————————————————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Ta Dependence

p_ta_md(series float source, simple int length) =>
    mg = 0.0
    ema_mg = ta.ema(source, length)
    mg := na(mg[1]) ? ema_mg : mg[1] + (source - mg[1]) / (length * math.pow(source/mg[1], 4))
    mg

//#endregion —————————————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Constants

// Input Groups
string P_GP_1      =      ""

//#endregion —————————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Inputs

//#endregion ———————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Price Data



//#endregion ———————————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Indicators

p_ind_1      =      p_ta_md(close, 14) // MD


//#endregion ———————————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Conditions

//#endregion ———————————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Indicator Plots

// MD
plot(p_ind_1, "MD", color.rgb(41, 98, 255, 0), 1)

//#endregion ————————————————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Custom Plots

//#endregion —————————————————————————————————————————————————————————————


//#region —————————————————————————————————————————————————— Alert

//#endregion ————————————————————————————————————————————————————————
Existing TradingView chart preview of the McGinley Dynamic overlay
Existing chart preview. The script plots a blue MD line; it does not plot trade markers. Use the numerical example to verify the formula, since a screenshot cannot establish initialization or invalid-price handling.

How to use McGinley Dynamic on TradingView

  1. Match the symbol, timeframe, source, session and chart type. N counts observations in the calculation; changing the timeframe changes those observations.
  2. Identify the denominator convention and seed. Compare several closed-bar values with a manual recurrence using the same prior MD, rather than judging whether two plotted lines look close.
  3. Read price above or below MD as a relationship to a smoothed series. A cross alone has no stated win rate, stop-loss distance or expected return.
  4. Specify whether a condition can fire during an open bar or only at its close. TradingView's bar-state documentation defines barstate.isconfirmed for this distinction on the chart timeframe.
  5. Test any strategy separately with entries, exits, position size, commissions and slippage. Verify the calculation before comparing trading outcomes.

This is an educational calculation reference, not investment advice. Adaptive smoothing does not establish a trading edge.

Write the implementation rules in Pineify

Give the Pine Script AI Coding Agent the source, N/k convention, seed, invalid-data policy and confirmation time. Ask for inspectable per-bar values before adding a strategy. The supplied example has fixed settings; configurable inputs and protection rules require a code change and new validation.

Open Pine Script AI Coding Agent

McGinley Dynamic questions

McGinley Dynamic is a recursive price smoother. Its update divides the price-to-line difference by N times the fourth power of the current price divided by the previous line value. The changing weight is asymmetric for positive prices: it is smaller above the line and larger below it.

Check what the length means. McGinley described N as 60% of the ordinary moving-average length being emulated. A formula written with k times L can express that convention with k=0.6 and N=kL. This page uses N directly, with no separate k. Applying 0.6 again to an already adjusted N changes the calculation.

No. Near a price-to-line ratio of 1, the no-k formula has a weight near 1/N, while an EMA of length N uses 2/(N+1). With N=10 and a previous value of 100, price 110 gives MD about 100.6830 and EMA about 101.8182. Relative response depends on the ratio and parameter convention.

Yes. If the positive denominator is less than 1, the update weight exceeds 1 and the line moves beyond the current price. For previous MD=100, price=50 and N=10, the denominator is 0.625 and the next MD is 20. A more aggressive coefficient can even make the line negative.

Compare source, timeframe, feed, session and chart type, then inspect the N versus k-length convention, initialization, available history and handling of zero or missing prices. This example seeds from an EMA when the previous MD is unavailable; another script may seed from the first price or an SMA.

No. The supplied code plots a line from close and contains no signal or alert condition. The line can change on the open bar. A separate rule can require barstate.isconfirmed before recording a crossover on the chart timeframe; that does not remove differences caused by revised data or starting history.

Sources and related guides

Sources checked October 17, 2026. The calculation table is an original hypothetical example, not a backtest.