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.

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 depends on two things: the absolute composite score and the gap between the #1 pick and the #2 pick. A high score alone isn't enough — if another stock was nearly as good, the pick wasn't decisive.

High
score ≥ 0.650
AND gap to #2 ≥ 0.040

Strong absolute score and a clear margin over the runner-up. Both conditions must hold — a stock that scores 0.72 but barely edged out a 0.71 competitor is Medium, not High.

Medium
score ≥ 0.500
OR score ≥ 0.650 but gap < 0.040

Either a solid score that fell just short of High, or a strong score but a close race against the runner-up. The "close race" case means the pick is correct but the conviction isn't as high.

Low
score < 0.500
(regardless of gap)

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.

The score gap is shown on the pick card as "Score gap to #2." Confidence reflects signal strength and decisiveness, not probability. A Low pick can win and a High pick can lose — but over time, High picks should outperform Low ones.

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.

Important limitation: the backtest uses only technical signals. Historical P/E ratios, EPS growth, and revenue growth are not available from free data sources on a point-in-time basis, so the backtest re-weights across the six technical factors only. This means backtest results won't perfectly match live performance, but they're still a useful sanity check for the technical side.

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.

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

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.