How it works

Everything you need to understand the picks, scores, and stats.

How a pick is made

Each weekday morning at 9:30 AM ET, the scheduler runs through a two-pass process:

Pass 1 — bulk pre-screen. All ~500 S&P 500 tickers plus sector ETFs are downloaded in a single batched request. Each stock is given a quick pre-score based on 5-day momentum and volume. The top 60 advance to the next pass. This keeps the detailed API calls to a manageable number while covering the full index.

Pass 2 — full scoring. For each of the 60 candidates, 4 months of price history plus fundamentals and news are fetched. Fourteen factors are computed and combined into a single composite score (0.000–1.000). All scores are then multiplied by a market regime discount (varies by regime and VIX level) so candidates aren't over-scored when the broad market is working against them. The highest scorer becomes the day's recommendation.

No AI or external service is involved. You can read and modify every piece of logic in picker.py — especially WEIGHTS at the top of the file, which you can edit freely to experiment.

The fourteen scoring factors

Each factor is normalised to 0–1 before being multiplied by its weight. The weights shown are the baseline defaults in WEIGHTS — the actual weight applied to any given stock may differ slightly because of the two-mode blending described in the next section. Run python backtest.py to see how weights perform historically.

5-day Momentum
12%
% price change over the last 5 trading sessions. Near-term price direction is one of the strongest predictors of the next day's move.
100%+5% or more
50%flat (0%)
0%−5% or worse
Sector-Relative Momentum
10%
The stock's 5-day momentum minus its sector ETF's 5-day momentum. A stock up 3% in a sector that's up 5% is underperforming; one up 3% in a sector down 1% is clearly leading. This filters out pure beta and rewards genuine outperformance.
100%+5% above sector
50%in line with sector
0%−5% below sector
RSI Setup
11%
14-day Relative Strength Index. Uses a smooth asymmetric curve (not discrete buckets) peaked at RSI 38 — the oversold-recovery zone where bounce setups are most reliable. The curve falls gradually on both sides so RSI 37 and RSI 39 score nearly the same.
100%RSI 38 (sweet spot)
76%RSI 25 — deeply oversold
81%RSI 50 — neutral
56%RSI 65 — trending but extended
32%RSI 80 — overbought
16%RSI 90 — extremely overbought
EPS Growth
10%
Year-over-year earnings-per-share growth. Growing earnings give the market a fundamental reason to push prices higher, even in the near term around analyst updates and sentiment shifts.
100%+40% YoY or more
50%+10% YoY
0%−20% YoY or worse
52-Week High Proximity
8%
Where the current price sits relative to its 52-week high. Academic research (George & Hwang 2004) shows stocks near their 52-week high systematically outperform — investors anchor on that level and a breakout above it tends to attract momentum buying.
100%At or above 52-wk high (breakout)
88%Within 3% below the high
65%3–10% below the high
40%10–25% below the high
15%>25% below (deep in range)
OBV Ratio
7%
A smarter volume signal than raw volume ratio. Compares total volume traded on up-days to total volume on down-days over the past 20 sessions (an On-Balance Volume proxy). A ratio above 1.0 means buyers are committing more size than sellers — a stronger confirmation of bullish intent than simply "volume was high today."
95%Up-volume 3× down-volume or more
85%Up-volume 2× down-volume
72%Up-volume 1.5× down-volume
60%Roughly balanced (ratio ≈ 1.0)
20%Down-volume 2.5× up-volume
Bollinger %B
7%
Where the current price sits within the 20-day Bollinger Bands (2 std dev). %B = 0 means at the lower band; %B = 1 means at the upper band. Scored with a peak near the lower band (%B ≈ 0.15) — the oversold-bounce zone — falling off toward the upper band.
100%%B ≈ 0.15 (near lower band) — sweet spot
70%%B = 0.00 (at lower band)
59%%B = 0.50 (middle)
30%%B = 0.75 (upper area)
0%%B = 1.00 (at upper band)
MACD Signal
9%
Standard MACD (12, 26, 9). Scored on the histogram's direction and whether a bullish crossover just occurred. A fresh crossover (histogram flipping from negative to positive) is the strongest signal.
100%Fresh bullish crossover (just turned positive)
85%Histogram positive and growing
65%Histogram positive but slowing
50%Histogram negative but recovering
15%Histogram negative and falling
Revenue Growth
7%
Year-over-year revenue growth. Supports the earnings story — a company growing its top line is harder to dismiss as a one-off beat.
100%+30% YoY or more
50%+10% YoY
0%−10% YoY or worse
Volume Ratio
5%
Today's volume ÷ 20-day average volume. A quick check for whether today saw unusual activity. Given a lower weight than before because OBV Ratio captures volume quality more precisely — this factor mainly catches big single-day spikes that OBV might dilute.
100%2.5× average or more
38%1.0× (average day)
0%0.5× or less
Gap Direction
4%
Today's open vs yesterday's close, expressed as a percentage. An overnight gap-up on a stock already showing positive momentum confirms that the thesis is playing out in real time. A gap-down is a warning that the setup may be fading.
90%Gap-up >+1% (strong confirmation)
72%Gap-up +0.3% to +1%
55%Roughly flat (within ±0.3%)
35%Gap-down −0.3% to −1%
15%Gap-down >−1% (bearish signal)
SMA-50 Proximity
4%
How far (%) the price sits from its 50-day simple moving average. Being near the SMA scores best — the stock is either setting up a breakout above it, or a mean-reversion bounce off it. Being far extended above it is a pullback risk.
100%Within ±5% — breakout/bounce zone
65%5–15% above or below
25%>15% extended either direction
Earnings Proximity
3%
How many days until the next earnings announcement. Earnings create near-term catalysts: stocks often move sharply in the days before and after a report. This rewards stocks with an imminent catalyst while remaining neutral when no earnings are nearby.
90%Earnings in 1–3 days — imminent catalyst
70%Earnings in 4–7 days
65%Earnings just reported (1–3 days ago)
40%No near-term earnings
Short Interest
3%
Short interest as a fraction of float. High short interest combined with upward price momentum elevates short-squeeze risk, which can produce outsized 1-day returns as shorts are forced to cover. Without price confirmation, high short interest on a falling stock is not a positive signal.
88%>20% short float and price moving up
72%10–20% short float and price moving up
52%5–10% short float (direction neutral)
45%<5% short float — minimal squeeze risk
55%>20% short float but price falling

Two-mode weight blending

The same stock can be a great buy for two completely different reasons: it might be a trending breakout (strong momentum, near 52-week high, high RSI) or an oversold bounce (deeply oversold RSI, near lower Bollinger band, price below SMA). The "right" weights for each case are different — RSI matters much more for a bounce candidate, while 52-week high proximity matters much more for a trend candidate.

To handle this, the picker computes a trend-mode score (0–1) for each stock based on four signals: whether it's above its 50-day SMA, its 5-day momentum direction, its RSI level (>60 = trending, <45 = oversold), and its proximity to the 52-week high. The final weights used to score that stock are then linearly blended between two pre-set weight tables:

Trend weights (mode = 1.0)
Emphasise 52-week high proximity (13%), momentum (16%), MACD (11%), and OBV ratio (9%). RSI and Bollinger carry very low weight because a trending stock is expected to have an elevated RSI and price near the upper band.
Mean-reversion weights (mode = 0.0)
Emphasise RSI setup (17%), Bollinger %B (12%), and sector-relative momentum (9%). 52-week high proximity is de-emphasised because a bounce candidate is expected to be well below its high.

Most stocks fall somewhere in between and receive a blend. The baseline WEIGHTS shown in the factor table above are the starting point for the interpolation, not the final weights — but they're a reasonable representation of the average effective weight across all candidates on a typical day.

Adaptive weight adjustment (live)

Each morning, before scoring any candidates, the picker analyses the last 30 resolved picks and tilts the baseline weights toward signals that have scored higher in winning picks versus losing ones. The adjustment is capped at ±25% per signal (learning rate 0.25) to prevent runaway changes from a small sample, and the weights are re-normalised to sum to 1.0 afterward.

The table below shows the current live adjustment — positive deltas mean the signal is being up-weighted because it scored higher in recent wins; negative means it's being down-weighted.

Signal Baseline Live (adapted) Δ
gap_direction 4.00% 4.99% +0.99%
revenue_growth 7.00% 6.40% -0.60%
rsi_setup 11.00% 10.44% -0.56%
macd_signal 9.00% 8.49% -0.51%
sector_relative 10.00% 10.40% +0.40%
earnings 3.00% 3.27% +0.27%
obv_ratio 7.00% 7.24% +0.24%
eps_growth 10.00% 9.78% -0.22%
volume_ratio 5.00% 5.16% +0.16%
vs_sma50 4.00% 3.88% -0.12%
week_52_high 8.00% 7.92% -0.08%
momentum_5d 12.00% 12.04% +0.04%
bollinger 7.00% 6.98% -0.02%
short_interest 3.00% 3.00% 0.00%

Market regime & VIX filter

Before scoring any individual stock, the picker checks the broad market using SPY vs its 50-day SMA and 5-day momentum. This produces a regime classification and a base score multiplier applied to every candidate equally. The VIX fear gauge then adds a further overlay on top.

Step 1 — SPY regime (base multiplier)

Bullish ×1.00
SPY above SMA-50 and 5d momentum ≥ −0.5%. No discount — full signal strength.
Neutral ×0.95
Mixed signals. Mild 5% discount reflects reduced conviction.
Bearish ×0.85
SPY >3% below SMA-50 and falling. 15% discount — market headwind overrides individual signals.

Step 2 — VIX overlay (additional multiplier, applied on top of Step 1)

Normal — no change
VIX ≤ 25. Market volatility is within a typical range. No additional discount.
Elevated ×0.90
VIX 25–30. Heightened uncertainty. Extra 10% discount stacked on top of the regime multiplier.
High ×0.80
VIX 30–40. Significant fear in the market. Extra 20% discount applied.
Extreme ×0.70 🚨
VIX > 40. Panic mode. Extra 30% discount applied and a "skip today" warning is shown — consider waiting for volatility to subside.

The two discounts stack multiplicatively. For example, a Neutral regime (×0.95) combined with Elevated VIX (×0.90) gives an effective multiplier of 0.95 × 0.90 = ×0.855. The multiplier compresses scores downward so the relative ranking of stocks doesn't change, but the absolute confidence level will be lower on high-volatility days.

Market Pulse gauge

The Market Pulse gauge on the home page shows a composite sentiment score from 0 to 100, where 0 is Extreme Fear and 100 is Extreme Greed. It is a quick read on how the broad market "feels" right now, independent of any individual stock pick.

The score is computed from three components each time the page loads:

VIX level
35%
Inverted: low VIX = greed, high VIX = fear. Scaled so VIX ≤ 10 → score 100 and VIX ≥ 40 → score 0. If VIX data is unavailable, this component is omitted and its weight redistributed.
SPY vs 50-day SMA
30%
How far SPY sits above or below its 50-day moving average. SPY 5% above SMA → score 100 (greed); 5% below → score 0 (fear). Values within that range are interpolated linearly.
SPY 5-day momentum
35%
SPY's % price change over the last 5 trading sessions. Positive momentum contributes toward greed; negative toward fear. Carries the highest weight because it's the most responsive to recent sentiment shifts.

Why are there two VIX numbers? You may notice the VIX value shown in the Market Pulse gauge differs from the VIX recorded on a pick card. That's expected: the pick-card VIX was captured at the moment the scheduler ran that morning and is stored permanently with the pick. The Market Pulse gauge fetches VIX live each time the page loads, so it reflects the current level — which will differ as the trading day progresses. Older picks may also show no VIX at all because VIX tracking was added after those picks were made.

Confidence levels

Confidence is calibrated on live resolved picks and updated as data accumulates. The current rules reflect findings from the first 34 resolved picks (May 2026).

High
score 0.68–0.84
AND not overbought/extended

The observed sweet-spot range (67–75% win rate in live picks). Overbought picks in this range — near 52-week high and RSI sub-score below 0.35 — are downgraded to Medium.

Medium
score 0.50–0.68 or ≥ 0.84
OR overbought flag set

Scores ≥ 0.84 are capped at Medium — very high scores correlated with extended/overbought stocks that only won 33% of the time in live data. The score-gap-to-#2 is no longer a gate: it showed no predictive value (losses averaged a higher gap than wins).

Low
score < 0.500
(regardless of other factors)

Today's best candidate, but no stock in the sample scored strongly — often a sign of a bearish market regime, elevated VIX, or a broadly mixed session. Treat with extra caution.

Confidence reflects signal quality as learned from live outcomes, not raw probability. The thresholds will be recalibrated once ~75 resolved picks accumulate — at that point a logistic regression on actual outcomes replaces these heuristic rules.

Stats explained

Win / Loss / Flat
A pick is a Win if the stock closes more than +0.1% above entry the next trading day. A Loss is a close more than −0.1% below entry. Flat is anything within ±0.1% — essentially unchanged. The ±0.1% band excludes noise from bid/ask spread and minor rounding.
Win Rate
Wins ÷ resolved picks (Flat counts as resolved but not as a win). A random baseline for any single stock on any day is roughly 50%, so consistently above that suggests the scoring is adding value. Use python backtest.py to check historical win rate before trusting live numbers built from a small sample.
Avg Return / Pick
Arithmetic mean of each pick's next-day % change. Matters more than win rate alone — a strategy that wins 60% of the time but loses 3% on each loss and gains 0.5% on each win is still net negative. You want both a high win rate and a positive average return.
Total Return
Sum of every pick's next-day % change. This is a simple cumulative tally, not a compound return, so it slightly understates what you'd get if you actually reinvested gains. Still a quick way to see if the system is net positive over its lifetime.
Current Streak
Consecutive wins or losses counting backward from the most recent resolved pick. Flat results break a streak. Streaks are psychologically compelling but largely noise over small sample sizes — don't over-index on them.
Pending
A pick is pending while the next trading day hasn't closed yet, or on market holidays. Results update automatically the next time any page loads — no manual action needed.

Backtesting

backtest.py simulates running the daily picker over the past year using a fixed set of stocks, then reports what the win rate and average return would have been historically. This lets you validate changes to WEIGHTS before relying on them with live picks.

Run it with: python backtest.py  (defaults: 60 stocks, 1 year). Or: python backtest.py --stocks 100 --days 504 for a wider, longer test.

What the backtest currently replicates

The backtest implements the live system's scoring architecture in three areas that were previously absent: market regime detection (historical SPY vs 50-day SMA + VIX level, producing the same 0.70–1.00 score multiplier applied live, and skipping VIX > 40 days entirely); two-mode weight blending (each stock is assessed for trend vs. mean-reversion character and scored with the same TREND_WEIGHTS / MEAN_REVERSION_WEIGHTS blend used live); and sector-relative momentum (the stock's 5-day return minus its sector ETF's 5-day return, computed from historical ETF data). Together these cover approximately 77% of the live scoring weight with full fidelity.

Assumption: current GICS sector assignments are used for the full backtest window. Sector reassignments typically affect 5–10 S&P 500 stocks per year, so the risk for any sampled stock is low.

What the backtest does not yet replicate

Four signals — EPS growth, revenue growth, earnings proximity, and short interest — still use neutral placeholder scores (0.45–0.50) rather than real historical data. Their combined live weight is approximately 23%. The weight is present in the score but contributes no predictive signal, which is equivalent to how the live system behaves when data for these signals is missing for a given ticker.

Three structural differences from the live system also remain: the backtest draws a random fixed sample of 60 stocks rather than pre-screening all 500 by momentum and volume; it uses static weights rather than the live system's adaptive weight adjustment (which tilts weights based on the last 30 resolved picks); and it uses today's S&P 500 constituents rather than historical point-in-time member lists, introducing modest survivorship bias.

Planned improvements: Phase 2 will wire in EPS growth and revenue growth from historical earnings data, and earnings proximity from past announcement dates. Phase 3 will replace the random sample with a simulated pre-screen matching the live two-pass architecture.

Known limitations

Even after pre-screening all 500 stocks, only the top 60 get full scoring. The true best candidate of the day might finish in positions 61–500 of the pre-screen occasionally — particularly if it has weak short-term momentum but strong fundamentals. The backtest currently uses a random 60-stock sample rather than a simulated pre-screen, so backtest win rates may differ from live performance for this reason.

Fundamental data (EPS growth, revenue growth) from Yahoo Finance can be stale or missing for some tickers. When a figure is absent, a neutral default is applied rather than skipping the stock. Check picker.py → get_stock_data() to see exactly how missing values are handled. The backtest currently applies neutral defaults for all four fundamental signals; Phase 2 will replace these with historical data.

The weights were set by judgment, not by optimising against historical data. Run the backtest and experiment with WEIGHTS in picker.py to see whether different allocations improve your win rate over time. Note that optimising weights against the same backtest period used to evaluate them will overfit — always hold out a validation window.

The live system's adaptive weight adjustment — which tilts signal weights based on the last 30 resolved picks — is not replicated in the backtest because it would require replaying historical pick decisions. Its effect is bounded at ±25% per signal by the learning rate, so deviation from base weights is modest.

Glossary

Quick definitions for technical terms used throughout the app. Click any term to expand.

RSI — Relative Strength Index Technical
A momentum oscillator from 0–100 that measures how quickly a stock has been moving up vs. down over the past 14 days. RSI above 70 is traditionally considered overbought (may be due for a pullback); RSI below 30 is considered oversold (may be due for a bounce). This app peaks its RSI score at RSI 38 — the zone just above deeply oversold — because that's where recovery setups tend to be most reliable without the uncertainty of being fully oversold.
MACD — Moving Average Convergence Divergence Technical
A trend-following indicator built from two exponential moving averages of price: the MACD line (12-day EMA minus 26-day EMA) and the signal line (9-day EMA of the MACD line). The histogram is the difference between them. A bullish crossover — where the MACD line crosses above the signal line — is the main signal this app watches for, especially when the histogram flips from negative to positive.
Bollinger Bands & %B Technical
Bollinger Bands are dynamic price envelopes: a 20-day simple moving average as the middle band, with upper and lower bands placed 2 standard deviations above and below it. %B (percent B) measures where the current price sits within those bands: %B = 0 means the price is at the lower band, %B = 1 means it's at the upper band. A %B near 0 signals the stock is near its recent low — a potential bounce setup.
OBV — On-Balance Volume Technical
A volume-based indicator that adds a day's volume to a running total when price closes up, and subtracts it when price closes down. This app uses an OBV ratio: the total volume traded on up-days divided by the total volume traded on down-days over the past 20 sessions. A ratio above 1.0 means more volume is flowing into the stock on good days than bad days — a sign of underlying buying pressure even if the stock hasn't made a big price move yet.
SMA — Simple Moving Average Technical
The arithmetic mean of a stock's closing prices over a fixed window of days. The 50-day SMA is a widely-watched level: institutions often treat it as a key support/resistance line. When SPY (the S&P 500 ETF) is above its 50-day SMA, the broad market trend is considered bullish; when it's below, the market regime is cautious. Individual stocks near their own 50-day SMA are in a "decision zone" — either breaking out above it or bouncing off it.
VIX — CBOE Volatility Index Market
Often called the "fear gauge," the VIX measures the market's expectation of S&P 500 volatility over the next 30 days, derived from options pricing. VIX below 20 = calm market; VIX 20–30 = elevated uncertainty; VIX above 30 = significant fear; VIX above 40 = panic/crisis territory. This app applies extra score discounts when VIX rises, because high volatility makes any directional bet riskier.
Market Regime Market
A characterization of the current broad-market environment: Bullish (SPY above its 50-day SMA, positive momentum), Neutral (mixed signals), or Bearish (SPY below its 50-day SMA, falling). All stock scores are multiplied by a regime discount factor before ranking: Bullish = full strength (×1.00), Neutral = 5% discount (×0.95), Bearish = 15% discount (×0.85). VIX then adds a further discount on top. This way the system naturally becomes more conservative when the market environment is unfavorable.
Conformal Prediction Interval Statistics
A statistically rigorous range for where next-day price could land, with a formal 90% coverage guarantee — meaning, if the historical data satisfies exchangeability (a mild assumption), the true next-day return will fall inside the interval at least 90% of the time. Unlike confidence intervals from parametric models, conformal intervals make no assumption about the shape of the return distribution. The interval is computed from the last 252 daily returns as calibration data. Hit rate on the Conformal page shows whether the empirical coverage is tracking the 90% target.
EPS Growth — Earnings Per Share Growth Fundamental
The year-over-year percentage change in a company's earnings per share. EPS = net income ÷ shares outstanding. It's the most common measure of profitability growth. Strong EPS growth signals that a company is becoming more profitable, which often attracts institutional buying and supports a rising stock price. This app scores EPS growth on a curve from −20% YoY (score 0) to +40% YoY (score 100%). Data comes from Yahoo Finance and may occasionally be stale or missing for some tickers.
Short Interest & Short Squeeze Market Structure
Short interest is the percentage of a company's tradeable shares (the "float") currently sold short — meaning traders have borrowed and sold the shares, betting the price will fall. A short squeeze happens when a heavily-shorted stock starts rising: short sellers are forced to buy shares to close their positions, which pushes the price up further, forcing more short sellers to buy, and so on. This app rewards high short interest only when the stock is already moving up, because that's the condition most likely to trigger a squeeze.
Momentum Technical
The tendency for assets that have recently performed well to continue performing well (and vice versa for underperformers). Momentum is one of the most robust and widely documented factors in finance. This app uses two momentum signals: 5-day momentum (absolute price change over the last week) and sector-relative momentum (how much better or worse the stock is doing vs. its sector ETF). The sector-relative version filters out market-wide moves and isolates genuine stock-specific strength.
Sector ETF Market
Exchange-traded funds that hold stocks from a single S&P 500 sector. Used in this app to measure how each sector is performing relative to the broad market (SPY). The 11 sectors and their ETF tickers: Technology (XLK), Financials (XLF), Energy (XLE), Health Care (XLV), Consumer Discretionary (XLY), Consumer Staples (XLP), Industrials (XLI), Materials (XLB), Real Estate (XLRE), Utilities (XLU), Communication Services (XLC).
Monte Carlo Simulation Statistics
A technique that runs thousands of random experiments to estimate probabilities. In the backtest, Monte Carlo is used to test whether the picker's returns are due to skill or luck: it randomly shuffles the order of actual pick returns 2,000 times and asks "what percentile does our actual return rank at?" If the picker's return is in the 80th percentile of random orderings, it means 80% of randomly shuffled portfolios did worse — evidence that the real ordering (generated by the scoring algorithm) has genuine predictive structure.
Not financial advice. This tool is a personal project for learning about technical analysis and Python. Past performance of the scoring algorithm does not predict future results. Never invest money based solely on any automated tool, including this one.