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.
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
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
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
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
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)
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
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)
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
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
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
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)
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
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
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.