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.
| Price P | Length L | k | D | Weight 1/D | New MD |
|---|---|---|---|---|---|
| 110 | 10 | 1 | 14.6410 | 0.068301 | 100.6830 |
| 90 | 10 | 1 | 6.5610 | 0.152416 | 98.4758 |
| 110 | 10 | 0.6 | 8.7846 | 0.113836 | 101.1384 |
| 90 | 10 | 0.6 | 3.9366 | 0.254026 | 97.4597 |
| 50 | 10 | 1 | 0.6250 | 1.600000 | 20.0000 |
| 50 | 10 | 0.6 | 0.3750 | 2.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.
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.
| Setting | Code value | Effect |
|---|---|---|
| Source | close | Chart closing price; changes on an open bar. |
| N | 14 | Direct denominator parameter; fixed in the function call. |
| Separate k | None | Equivalent to k=1 if length is still 14. |
| Seed/restart | EMA(source, length) | Used when the previous MD is unavailable. |
| Signals and alerts | None | No entries, exits or confirmed-cross rule. |
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 ————————————————————————————————————————————————————————
How to use McGinley Dynamic on TradingView
- Match the symbol, timeframe, source, session and chart type. N counts observations in the calculation; changing the timeframe changes those observations.
- 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.
- 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.
- 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.
- 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 AgentMcGinley Dynamic questions
Sources and related guides
- TradingView: McGinley Dynamic (definition and period setting).
- John R. McGinley Jr.: McGinley Dynamics, 1997 (author's paper, mirrored scan).
- TA-Lib: MCGD (implementation and boundary choices).
- TradingView: bar states (open and confirmed bars).
Sources checked October 17, 2026. The calculation table is an original hypothetical example, not a backtest.