RSI-14
How it's calculatedFor each of the last 14 trading sessions, we split the close-to-close move into a gain or a loss (a down day counts as a zero gain, an up day as a zero loss). We average those 14 gains and 14 losses separately, divide them (avg_gain / avg_loss = RS), then compute 100 − 100/(1+RS). It's one fresh 14-day window with no smoothing carried forward from prior days, and it needs at least 15 closes to produce a value. Source: `backend/app/services/stock_analysis/data_provider.py:956-970` (`_rsi_14`), which sets `snap.rsi_14` at `data_provider.py:499` on a `TickerSnapshot`. That snapshot is upserted into `IntelTickerSnapshot.rsi_14` by `persist_ticker_snapshot()` (`backend/app/services/stock_analysis/service.py:731`), surfaced for display at `backend/app/qtick/services/ticker_service.py:2235`, and mapped to the frontend at `qtick/frontend/src/lib/data/map/technicals.ts:23`.
How it affects judgementRSI-14 tells you how stretched the tape is, not whether the fundamentals changed. An extreme reading (above 70 or below 30) sharpens your timing question — does the thesis still hold at this price velocity? A reading above 70 adds friction to a long case (momentum-chasers are already in); below 30 weakens the near-term bear case (the tape is already pricing in the pain). Neither extreme is decisive alone — pair it with volume and the 50/200 MA structure before forming a view.
What the current value signifiesAbove 70: the stock has gained on most of the last two weeks — price is stretched above recent norms, and mean-reversion pressure often builds. Below 30: losses have dominated — the tape is compressed, often a sign of near-term exhaustion of sellers. The 40-60 band is normal drift. QTick labels 55+ "firm," 45- "soft," and uses 70/30 as the outer band cells and 60/40 as the secondary bands (`TraderTape.tsx`).
RangeFixed 0-100. Theoretical extremes: 0 (every day a loss), 100 (every day a gain, or avg_loss == 0 — the code returns 100.0 in that branch). Practical range: most large-caps oscillate 30-70. Sub-30 and above-70 are the conventionally watched zones. Penny/crypto cohorts use a wider overbought threshold of 75 (vs. 70 for normal stocks) because their tape is noisier — see `tactical_state.py:191-193`.
The catchOur stock-page RSI uses a plain 14-day simple average (`data_provider._rsi_14`), not Wilder's exponential smoothing used in charting platforms and our own tactical engine (`_wilder_rsi` in `tactical_state.py`). The simple version is slightly more reactive and will differ numerically from Bloomberg/TradingView RSI for the same ticker.
MACD
How it's calculatedTwo helpers in data_provider.py do the work. `_ema` (lines 973-978) runs a recursive EMA with k=2/(period+1), seeded from the first close. `_macd` (lines 981-993) needs at least 35 daily closes, then: EMA12 minus EMA26 = the MACD line; EMA9 of that line = the signal; line minus signal = the histogram (all rounded to 4 decimals). The three values are stored on the snapshot as macd_line/macd_signal/macd_histogram. The frontend mapper (technicals.ts:27-33) passes them through unchanged, with a null fallback when absent.
How it affects judgementWhen the MACD line sits above its signal line and the histogram is widening, short-term momentum is accelerating ahead of the longer-term trend — context that can support a bullish read. A line crossing below signal, or a shrinking histogram, weakens that read. Stale or choppy readings — the line hugging signal for weeks — add little either way.
What the current value signifiesCompare the MACD line to its signal line, not to zero alone. A line clearly above signal (positive, widening histogram) means recent momentum is outpacing the longer trend. Near-zero, or line below signal, means momentum is flat or fading. The absolute number matters less than its direction and its distance from signal.
RangeUnbounded, and it scales with price level. A $10 stock might show MACD of plus or minus 0.10; a $500 stock plus or minus 5. Histogram near zero = momentum neutral; growing magnitude = a strengthening trend in either direction. There are no fixed overbought/oversold thresholds the way RSI has.
The catchBecause it is price-scaled, you cannot compare raw MACD values across stocks, or even across time for the same stock after a large price move. It also lags: both EMAs are backward-looking, so it confirms trends rather than predicting reversals. One implementation detail: the EMA seeds from the raw first close rather than an SMA seed, so with only the minimum ~35 bars the EMA26 has barely converged and the earliest snapshots run noisier than a full-history MACD.
Bollinger bands
How it's calculatedA 20-day simple moving average of split- and dividend-adjusted closes (yfinance auto_adjust=True) is the midline. Upper and lower bands sit exactly 2 population standard deviations (ddof=0) above and below that midline. A companion z-score, (price − mean20) / std20, measures how many σ the last (nightly-recomputed) adjusted close sits from the 20-day mean. Recomputed nightly and stored in intel_technical_factors.
How it affects judgementA z-score near ±2 strengthens a mean-reversion read — historically, outer-band touches have shown a tendency to revert, but that's a population base rate, not a forecast for this name. A z-score near 0 removes the volatility-stretch argument entirely. A band squeeze — both bands narrowing toward each other — points to volatility compression that often precedes a larger move, but gives no directional clue on its own. Pairs naturally with volume and RSI to assess whether a stretch is confirmed or exhausted.
What the current value signifiesPrice at the upper band (z ≈ +2) means the last (nightly-recomputed) adjusted close sits roughly 2 standard deviations above its 20-day average — stretched high by recent-history standards. Price at the lower band (z ≈ −2) is the mirror. Price near the midline (z near 0) is statistically unremarkable. A very narrow band width signals a volatility low — the market is coiling.
RangeUpper and lower bands are price-denominated (e.g. $148 – $162 for a $155 stock) so they have no fixed numerical range. The z-score that accompanies them is the unit-free measure: roughly −3 to +3 in normal markets, with values beyond ±2 occurring only about 5% of trading days under the normal-distribution base rate (the 2σ rule; real returns are fat-tailed, so treat it as an idealization). Band width (upper − lower) contracts near vol lows and expands in high-vol regimes with no hard cap.
The catchThe 20-day window is short — a single gap or earnings spike inflates σ and makes the bands appear wide for the next 20 sessions, masking subsequent genuine stretches until the event rolls off.
VWAP 20 / VWAP 50
How it's calculatedFor each of the past 20 (or 50) daily bars, we compute typical price = (High + Low + Close) / 3, multiply by that day's volume, sum both totals, then divide: Σ(typical × volume) / Σ(volume). The result is a volume-weighted average price over that window — not the intraday session-anchored VWAP traders quote on a live feed.
How it affects judgementPrice sitting above both lines means the average share over the last 20 and 50 days changed hands cheaper than today — the window's cost basis sits below current price. When the 20 crosses below the 50, that picture shifts and the cost basis is no longer rising under price. Neither line predicts direction; they describe where the crowd's cost basis sits relative to where price is now.
What the current value signifiesCompare the displayed VWAP to the current price. If price is above VWAP 20, most buyers over the past month are in profit relative to their average cost. If price is below VWAP 50, the majority of buyers over the past quarter are underwater relative to their cost basis.
RangeThese are price-denominated, so the scale matches the stock's price. What matters is the gap: price / VWAP − 1. A 0–2% spread is tight and common; 5–10% is a meaningful divergence; beyond 15% is extended and unusual. No fixed floor or ceiling.
The catchThis is a daily-bar approximation — each bar contributes one typical-price point, not tick-level trade data. A single high-volume day (earnings, index addition) can pull the VWAP sharply and make it less representative of the broader trading period.
Typical daily move
How it's calculatedEach day's true range = max(high−low, |high−prev_close|, |low−prev_close|) — the third and fourth terms capture overnight gaps. We take a plain 14-day arithmetic average of those true ranges and report the result in dollars.
How it affects judgementCalibrates how much noise a one-day move actually carries. A $2 move in a stock with $0.30 ATR is signal; the same $2 in a $4 ATR stock is routine. Weakens any case built on a single session's action.
What the current value signifiesThe dollar amount the stock typically traverses over a full day. Compare it to the price: a $1 ATR on a $10 stock (10%) is high volatility; the same $1 on a $200 stock (0.5%) is quiet.
RangeAlways ≥ 0. Mega-caps: $0.50–$5. Mid-caps: $0.30–$3. Small or volatile names: $1–$15+. Penny stocks or crisis regimes spike far higher. No upper bound.
The catchIt's a plain 14-bar average, not Wilder's smoothed EMA. So one extreme outlier day inflates the reading at full weight for 14 bars, then drops out abruptly on day 15 rather than fading gradually.
Support & resistance
How it's calculatedFrom the prior trading day's high (H), low (L), and close (C): the pivot P = (H + L + C) / 3. R1 = 2P − L and S1 = 2P − H. The outer levels extend the same arithmetic: R2 = P + (H − L), S2 = P − (H − L), R3 = H + 2(P − L), S3 = L − 2(H − P). These are standard classic (floor-trader) pivots. Every value is rounded to two decimal places.
How it affects judgementLook at where the current price sits: between S1 and R1 (inside the prior day's projected range), above R1 (trading past the first resistance level), or testing S1 from above (sitting on the first support level). A narrow S1-to-R1 band means yesterday was a quiet, compressed day; a wide one means yesterday was volatile. None of this shifts the net signal on its own — read it alongside volume and trend direction.
What the current value signifiesPrice above P means the stock is trading above its prior-day pivot; price below P means it is trading below it. If the current price is within a few percent of R1 or S1, that level is close enough to be worth watching for a reaction. A price far outside the R3/S3 band has moved well beyond what the prior day's range projects. These same levels are reused downstream: R1 is taken as the technical target and S1 as the technical stop (ticker_service.py:3684-3685).
RangeP, R1, S1 and the outer levels are dollar prices, so they scale with the stock. The spread R1 − S1 equals the prior day's high-minus-low range exactly, since R1 − S1 = (2P − L) − (2P − H) = H − L by construction — it is an algebraic identity, not an approximation. R3 and S3 are the widest projections and are rarely reached without a news catalyst. A useful distance gauge is the gap from the current price to the nearest level as a percent of price, or in units of that prior-day range.
The catchThese are single-day levels recalculated each morning from yesterday's bar. They reset daily and carry no memory of multi-day structure, so they miss longer-term support and resistance zones entirely.
Exhaustion count
How it's calculatedEach day, we count how many consecutive trailing bars had close > close-4-bars-ago (upside) or close < close-4-bars-ago (downside), walking back from today, capped at 9. Stored as signed `td_setup`; the page shows the absolute count plus direction.
How it affects judgementA count near 9 tells you the one-way run is stretched by DeMark's definition — momentum may be tiring. It doesn't confirm a reversal, but it raises the bar for adding to a position in the same direction and can sharpen your entry timing on the opposite thesis.
What the current value signifies1–4: early in a directional sequence, nothing unusual. 5–8: the run is accumulating; watch whether each new bar still clears the close from four bars back. 9 (max): the "Setup complete" reading — the trend has met DeMark's exhaustion condition and the framework flags it as stretched.
Range0 to 9 (capped). 0 means no directional run today — either the closes are mixed, today's close exactly equals the close four bars back, or there isn't yet five bars of history. Direction is shown separately as "up" or "down". Most tickers sit between 1 and 5 on any given day; 9 is the capped maximum and the only value DeMark treats as a completed setup.
The catchThe counter resets if even one bar breaks the pattern, so a noisy stock can repeatedly reach 8 and reset without ever completing the 9 — the count alone doesn't tell you how many failed attempts preceded it.
Golden / death cross
How it's calculatedThe nightly technical engine (recompute_technical_factors.py:514-522) takes the simple mean of the last 50 and last 200 split-adjusted daily closes from intel_daily_bars (`def sma(k): return float(c[-k:].mean())`). The frontend computes the cross gap itself: gap% = (sma50/sma200 - 1) x 100 (TraderTape.tsx:28; PriceChart.tsx:117-119). Positive gap means "Golden cross," negative means "Death cross." A "forming" label fires when |gap| < 1.6% (PriceChart.tsx:126), and detectCrosses (PriceChart.tsx:98-108) flags the actual crossover bar (prev<=0 && cur>0). The backend regime label itself is set in data_provider.py:533-534 (`snap.sma_cross = 'golden_cross' if snap.sma_50 > snap.sma_200 else 'death_cross'`, duplicated in recompute_chart_patterns.py:533-535), while the SMA gap percentage is computed only in the frontend. The chart-patterns nightly engine (recompute_chart_patterns.py:489-506) separately records the actual crossover day as an event with confidence 0.65 and level=sma200.
How it affects judgementA golden cross tells you the 50-day trend has climbed above the 200-day trend — the stock's recent path is running ahead of its long-run average. That can corroborate a bullish case built on fundamentals, but only as corroboration: crosses lag, because both averages are built from past closes and are already backward-looking. A death cross in a stock you're otherwise constructive on is a friction point — it narrows the pool of momentum-driven participants and trend-following systems. The UI surfaces a golden-cross forward-win rate of 61.2% over 63 days (outcome.ts:107); the corresponding death-cross rate is not computed in the repo, so there is no symmetric figure to cite. Neither state tells you what caused the move or whether it will continue.
What the current value signifiesIf the label reads "Golden cross," the 50-day average sits above the 200-day — the stock's medium-term trend is above its long-run baseline, a state most systematic and trend strategies read as constructive. "Death cross" means the reverse: the medium-term average has dropped below the long-run level, a state many quant screens flag as negative. "Forming" (gap within 1.6%) means the two averages are converging — the regime could flip within days on a move in either direction. The gap percentage shown (e.g., "50d +4.3%") tells you how entrenched the current regime is: a gap of plus or minus 10%+ is a deep, durable trend; plus or minus 1-2% is fragile and close to flipping.
RangeCategorical output: "Golden cross," "Death cross," or "[Golden/Death] cross forming." The underlying gap (sma50/sma200 - 1) x 100 has no fixed floor or ceiling. In calm large-cap markets, gaps of plus or minus 3-8% are typical. During extended bull runs (e.g., NVDA 2023-24) or crashes (e.g., March 2020), gaps of plus or minus 20-40% are observed. The "forming" zone is defined in code as |gap| < 1.6% (PriceChart.tsx:126).
The catchBoth averages are built from the same past closes, so a cross always confirms a trend that already happened — it cannot lead a turn. A stock can spend months in a death cross while grinding higher, or flash a golden cross right before a reversal. The 61.2% / 63-day figure in the UI (outcome.ts:107) is a population average; individual names, sectors, and market regimes vary widely, and the code does not define what outcome threshold "61.2%" measures.
EMA 9
How it's calculatedA 9-period exponential moving average of split-adjusted daily closes — like an SMA but weighting the most recent closes more (smoothing 2/(9+1)). Would run in the same nightly techfactors engine as our existing EMA 12/26/50.
How it affects judgementA fast short-term trend reference. In an uptrend, price repeatedly pulling back to and holding the 9-EMA corroborates the trend is intact — it confirms what price is already doing, it's not a trigger.
Current valueThis metric’s live data pipeline is under review — the method above describes how it is intended to be calculated, but the current displayed value may not yet reflect that formula. Treat it as in progress, not a finished number.
RangeTracks the share price (a dollar level), always near recent closes; reacts faster than the 12/26/50 EMAs we already show.
The catchWhippy on its own — flips on small moves. On daily bars it spans ~2 weeks; the *intraday* 9-EMA day-traders use needs intraday data we don't serve.
EMA 20
How it's calculatedA 20-period EMA of daily closes (same engine). Reacts faster than our SMA-20, which weights all 20 days equally.
How it affects judgementA short-to-medium trend reference, often a firmer floor than the 9-EMA. A pullback that holds the 20-EMA while volume fades suggests selling pressure is easing — corroboration, not a buy call.
Current valueThis metric’s live data pipeline is under review — the method above describes how it is intended to be calculated, but the current displayed value may not yet reflect that formula. Treat it as in progress, not a finished number.
RangeTracks price; sits between the faster 9-EMA and the slower 50-EMA.
The catchReactive and lagging; we already show SMA-20 and EMA-12/26/50, so this is mainly for the retail audience that watches the 20-EMA specifically.
Stochastic RSI
How it's calculatedThe stochastic formula applied to RSI instead of price — (RSI − lowest RSI) / (highest RSI − lowest RSI) over a 14-period window, scaled 0–100, then smoothed into a fast %K and a slow %D line. Same engine as our RSI-14.
How it affects judgementA more sensitive overbought/oversold gauge than plain RSI; %K crossing up through %D from below 20 is the "momentum turning up from oversold" read — context for timing, never a buy trigger.
Current valueThis metric’s live data pipeline is under review — the method above describes how it is intended to be calculated, but the current displayed value may not yet reflect that formula. Treat it as in progress, not a finished number.
Range0–100 (the %K/%D lines); spends more time pinned at the extremes than RSI-14 because it's an oscillator built on top of another oscillator.
The catchIts sensitivity cuts both ways — earlier reads but far more false ones than RSI-14; reactive by construction. > **Not addable honestly without intraday data:** the *session* VWAP and intraday 9-EMA the review describes reset each day and need an intraday feed (our intraday bars are a cold domain, unwired; true tape needs a paid feed). Ship the daily-bar versions above; don't fake an intraday VWAP. > **The "putting it all together" confluence setup is NOT a metric — and must not ship as a fired signal.** "When several factors align, the probability improves" is an unvalidated edge claim. Either present it as an observational *conditions-present checklist* (no probability language, like our bottom-zone checklist) or backtest it on our bars (non-overlapping, survivorship-clean) first — only a result that clears the bar earns any "probability improves" copy. See `STOCK_PAGE_VALUE_CATALOG.md` §4f.