Momentum (MOM): Price Difference and Pine Script | Pineify
Momentum (MOM) is the difference between a source value now and the value N bars ago. The example plots close minus close[10]. Its sign describes that endpoint comparison; its slope describes how the comparison changes as both endpoints move. Neither quantity directly measures buying pressure or guarantees a future price move.
Inspect the Pine ScriptFormula and units
MOM(t, N) = source(t) − source(t − N). In Pine Script, ta.mom(source, N) performs this difference. With close as the source, the result has price units, not percentage units and not a bounded 0–100 scale.
Length 10 needs a current observation and the observation ten bars back: the first possible result occurs on the eleventh chart bar when both values exist. Missing endpoints produce a missing result. Interior observations do not directly enter this two-endpoint subtraction, although their existence determines bar indexing.
Ten chart bars are not always ten calendar periods. Sessions, missing bars, and nonstandard chart types affect what the source and bar spacing mean. Keep those settings consistent when comparing results.
A bullish zero crossing can occur while price falls
Consider this hypothetical sequence, using length 3 for a short worked example:
| Bar | Close | Close three bars ago | MOM |
|---|---|---|---|
| 0 | 100 | Unavailable | Unavailable |
| 1 | 90 | Unavailable | Unavailable |
| 2 | 100 | Unavailable | Unavailable |
| 3 | 99 | 100 | -1 |
| 4 | 98 | 90 | 8 |
At bar 4, close falls from 99 to 98, but MOM rises from −1 to +8. The reference endpoint falls by 10, outweighing the current one-point decline. The positive zero crossing does not mean the current bar rose or that buyers were identified.
Algebraically, change in MOM = current one-bar source change − the one-bar source change N bars ago. For N greater than 1, this is not the difference between the latest two consecutive one-bar changes. Calling every rising MOM “current acceleration” hides the moving-reference effect.
For a source that rises exactly two units on every bar, MOM(3) stays at 6 once available. A flat MOM can coexist with a persistent price increase. Negative MOM can also rise toward zero without becoming positive.
MOM, percentage ROC, and ratio momentum
Percentage ROC = 100 × (current − past) / past, when the past value is nonzero. Equivalently it is 100 × (current/past − 1). Omitting the minus one creates a ratio centered on 100, not percentage ROC centered on zero.
| Past → current | MOM | ROC | 100 × current/past |
|---|---|---|---|
| 500 → 515 | 15 | 3% | 103 |
| 50 → 65 | 15 | 30% | 130 |
| 100 → 90 | -10 | −10% | 90 |
Dividing MOM by the current price is a different normalization. For 500 → 515, it gives about 2.9126%, not the 3% return measured from the past price. Specify the denominator rather than treating normalized variants as interchangeable.
MOM is still defined by subtraction when the past source is zero; percentage ROC is not. For positive past prices and nonnegative current prices, ROC cannot fall below −100%, though it has no fixed positive ceiling. Raw MOM has no universal fixed bounds across instruments.
Official formula: TradingView Rate of Change. RSI instead normalizes smoothed gains and losses; it answers a different question and is not made invalid merely by staying near an extreme.
What this script does and does not show
The code has a fixed call to ta.mom(close, 10) and a single blue plot in a separate pane. It contains no settings inputs, horizontal zero-line drawing, crossover markers, divergence detector, alerts, or orders. Zero remains the numerical reference, but a dedicated visible zero line requires an explicit plotting addition.
The current close can update while its bar is open, so MOM can move across zero and back. This example has no bar-close gate. If an adaptation uses a confirmed event, define the event on the closing update and check the first price at which an order could actually execute.
A zero crossover typically means current MOM is strictly positive and previous MOM was nonpositive; a move from exactly zero can qualify. This is different from merely staying positive. A pivot-based divergence rule additionally needs its right-side confirmation bars before the pivot is known.
Reference: TradingView repainting and historical placement. A pivot drawn back on its original bar must not be counted as a signal already known at that time.
Lookback choices are hypotheses, not performance rankings
Length changes which old endpoint is compared with the current one. It is not a smoothing average, so increasing length does not guarantee fewer zero crossings. On a repeating 100, 101, 100, 101 sequence, MOM(2) is zero after initialization, while MOM(3) alternates between −1 and +1.
Use a defined event, symbol, timeframe, session, cost model, and evaluation period to compare choices. A longer confirmation rule changes observation time; it does not come with a fixed percentage reduction in false signals. Combining MOM with EMA, volume, or RSI adds assumptions that require separate evaluation.
If adding risk rules, keep reference prices explicit. “Entry minus 1.5 ATR” and “entry-bar low minus 1.5 ATR” are different stops unless entry equals the low. A chart indicator alone supplies neither a position-size rule nor an execution model.
Pine Script v6: implementation and limits
The full example plots ta.mom(close, 10) only. Unused generated time helpers do not add session filtering or other-timeframe behavior. The numerical zero reference is not separately drawn; any requested inputs, reference lines, or events must be added explicitly.
// 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="Momentum", overlay=false, 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
//#endregion —————————————————————————————————————————————————————————————
//#region —————————————————————————————————————————————————— Constants
// Input Groups
string P_GP_1 = ""
//#endregion —————————————————————————————————————————————————————————
//#region —————————————————————————————————————————————————— Inputs
//#endregion ———————————————————————————————————————————————————————
//#region —————————————————————————————————————————————————— Price Data
//#endregion ———————————————————————————————————————————————————————————
//#region —————————————————————————————————————————————————— Indicators
p_ind_1 = ta.mom(close, 10) // MOM
//#endregion ———————————————————————————————————————————————————————————
//#region —————————————————————————————————————————————————— Conditions
//#endregion ———————————————————————————————————————————————————————————
//#region —————————————————————————————————————————————————— Indicator Plots
// MOM
plot(p_ind_1, "MOM", color.rgb(41, 98, 255, 0), 1)
//#endregion ————————————————————————————————————————————————————————————————
//#region —————————————————————————————————————————————————— Custom Plots
//#endregion —————————————————————————————————————————————————————————————
//#region —————————————————————————————————————————————————— Alert
//#endregion ——————————————————————————————————————————————————————
Specify a checkable adaptation in Pineify
Specify raw MOM or percentage ROC, the source, lookback, visible reference line, and the exact event to detect. Ask the Pine Script AI Coding Agent for those requirements and an explicit confirmation policy. Check the synthetic endpoint examples and validate revised code before using it in a strategy.
Open Pine Script AI Coding AgentEducational indicator analysis. No measured trading performance is established here. Syntax validation does not prove formula equivalence, execution assumptions or profitability.