Skip to content
Quant500 - Quantitative Analysis
Search ticker or company...
ES | EN

Methodology

In short

Every market day we score the ~500 S&P 500 companies on four measures that academic research has studied for decades: momentum (what is already rising), value (what is cheap against what it earns), quality (what actually makes money) and growth. Each company gets a score comparable with the rest, and the portfolio keeps the best ones. Once a month the list is rebuilt and whatever dropped out is sold.

  • Accounts are used only from the day they were filed with the SEC: via Sharadar up to Aug 21, 2026, directly from the SEC since. Prices come from Sharadar for the history and from professional financial APIs day to day. Nothing is computed with information that was not available that day.
  • The history is not cherry-picked. At each past date only companies that were in the index THAT day can be selected, including those that later went bankrupt or disappeared. Measuring with today's survivors handed us 13 points a year (measured on Sep 1, 2026 over 2021-2026).
  • We publish what it costs. Commissions, spread and turnover, measured over the backtest's rebalances, not guessed.
  • And we publish what we do not know. That the edge is measured but not proven, how many years you spend behind the index, and which data is missing. Telling you that too is part of the service: you know exactly where you stand.

Below is the full detail: every formula, every source and every limit, with the name of the file each number comes from. All of it free, no account needed. If something does not add up, you can check it yourself. And if a term is unfamiliar (P/E, beta, z-score, walk-forward…), the glossary explains it with an example.

Welcome to the engine room. Here is how everything on this site is computed: which file each number comes from, the exact formula that turns it into a score, how the portfolio is built, and what the curve actually measures. Every claim on this page carries the source file in brackets, so you can check it rather than believe it. And behind every page there are twenty-nine automated checks — prices cross-checked against a second source, curves reproduced window by window across two independent engines, even the layout reviewed with a real browser. Twenty-one run on every cycle of the day; the other eight (layout, indexability, structured data, accessibility, stable addresses, profile navigation, the company-page figures and the price cross-check) run at deploy time, because they need a browser or a second source. And it is worth being exact about what happens when one fails: the portfolio check reverts on its own — if carteras.json comes out inflated, the last validated copy is restored before anyone sees it — while the rest warn but do not revert: they write the failure into the cycle log by name, and what was generated is published anyway.

The specific figures on this page are measured on the data of October 21, 2026 —the last closed portfolio is the 10/21/2026 session and the last ranking is the 10/21/2026 session— and each one carries its date. A figure without a date means nothing. And you do not have to take any of them on trust: the daily ranking and each portfolio's curve download as CSV, free, with no account and no signup, from the same file that paints the table (§10). That is something few services offer even for money; here it is the norm.

This is not investment advice. Quant500 publishes automated quantitative analysis, identical for everyone, and by definition content that is the same for everybody cannot be a personalized recommendation for anybody. We are not a broker, a fund, a signal service or a licensed adviser. What follows explains how the numbers are made, not what you should do with them.

1. What gets published, and what does not

Let us start with the core of it: every return published here has a reproducible walk-forward backtest behind it, and this page tells you exactly what each number is and where it comes from.

Every portfolio here is published with a backtest

The twenty factor portfolios: momentum, value, quality, growth and multi-factor, in sizes of 10, 20, 50 and 100 positions. They are selected by factor ranking, so their history can be rebuilt with a real walk-forward: it was rebuilt in full up to Aug 21, 2026 on Sharadar data and frozen, and each new rebalance is added by the daily cycle (regenerar_carteras_honesto.py). They carry backtestDisponible: true in carteras.json.

And one distinction, the hardest to maintain: today's holdings are not the backtest. A model portfolio's table lists today's best companies according to the algorithm — the model's output today; the curve above it is the result of having repeated that rule every month since December 1998. Both come from the same code and still answer different questions.

The honest calculation is the default, and leaving it costs two flags. regenerar_carteras_honesto.py starts in point-in-time mode and writes nothing otherwise; publishing the inflated figures requires typing --universo-de-hoy and --si-quiero-publicar-lo-inflado. This is not a stylistic quirk: the gap between the two modes is 16.16% versus 39.21% CAGR on the 20-stock multi-factor portfolio (measured on Aug 14, 2026; today’s honest CAGR, in the §8 table, is a different number, and so is the size of the shortcut: re-measured on Sep 1, 2026 over August 2021 to July 2026 it came out at 13.33 points, 25.93% against 39.26% — it depends on the window, what does not change is that it is large), and an automated process calling the script with no options would publish the inflated one without anyone noticing. As long as honesty is optional, it is not honesty: here it is the default.

2. The universe: who is eligible, and on which date

The universe is the S&P 500. The hard question is not that one, it is when: who was in the index on the day the model made its decision. Rebuilding that is laborious and easy to skip; here it is the foundation of the whole calculation, and this is how it is done. Up to Aug 21, 2026, membership comes from Sharadar's historical S&P 500 table, back to 1998 and including the companies that later disappeared. What follows in this section —the reconstructed composition, renames, recycled tickers, foreign tickers and the Cboe patch— applies to our usual data, which only feeds the months lived since that date: the Sharadar history uses a single symbol space and is computed without patches.

Historical index membership

Membership is queried by date against a reconstructed historical composition (universo_pit.py over datos/construido/universo_historico.json): 201 monthly snapshots between Dec 31, 2009 and Aug 13, 2026, 888 companies with their join and leave spans, and 68 ticker changes. The source is the Wikipedia constituents table revision by revision, cross-checked ticker by ticker against an independent public dataset that confirms 99.3% of the filter's exclusions.

The rule: for a date F, use the last snapshot whose revision date is on or before F — never the month label, which comes later. The list may therefore be a few weeks stale, which is exactly the error a real investor would have made looking at the last published list, but no knowledge later than F can leak in.

What this is worth: applying today's list backwards lets you buy, in 2021, companies the index did not admit until 2025 — chosen precisely because they did well afterwards. AppLovin occupied 21 stock-months of the 20-stock multi-factor portfolio and did not enter the index until September 2025, after rising 1,365%. That shortcut is worth 23.05 percentage points of annual return on that same portfolio: it goes from 16.16% to 39.21% (regenerar_carteras_honesto.py). Measured on August 14, 2026. Both figures are from that day: today's honest CAGR in the §8 table is different.

Ticker changes: the series decides, not the name

Snapshots store the ticker in force on each date (FB in 2021, ANTM in 2021). Out of a company's whole rename chain, the symbol chosen is the one that actually holds the longest price series; ties go to the most recent (universo_pit.py, method moderno()). Not an aesthetic preference: the index calls Bank of New York BNY and EchoStar ECHO, but the complete series live under BK and SATS (4,178 sessions each, versus 367 and 340); returning the new names made two full members vanish from the backtest with nothing raising an alarm. And the other way round, Elevance has its 4,178 sessions under ELV and only 2,202 under ANTM.

Recycled tickers

The rename chain is walked backwards accepting only hops that had already happened at the date being queried. The symbol Q was Quintiles until it renamed to IQV in 2017, and in November 2025 Qnity Electronics, spun off from DuPont, took it over: two different companies sharing a symbol in different eras, separated only by the date. There is also a forbidden-pairs list (deslistadas_pares_prohibidos.json) for cases that are the same registrant but a different price series: TYC→JCI, ACE→CB, UA→UAA, CMCSK→CMCSA.

Two more filters, small and with a measurable effect. 20 tickers that were never in the S&P 500 are dropped from the price history (AAP, B, BL, CC, CR, DEC, E, EC, G, HR, MC, ORC, R, RC, SND, SY, TE, TY, UA, XE): 3.8% of foreign names, and not harmless ones, since ORC pays out ~20% a year and EC 8% per quarter — exactly the profile the Value factor grabs. And when both share classes of the same company are present, the secondary one is removed —GOOG with GOOGL, FOX with FOXA, NWS with NWSA— before the portfolio is picked (factores.py, CLASES_DUPLICADAS): without it, an "equally weighted" ten-stock portfolio would hold 20% of the money in a single business in 9 of the months measured.

There is one declared exception, in plain sight and by name: Cboe Global Markets disappears from the source table in February 2019 and in reality stays in the index to this day. That is a source failure, not an index event, and it is patched explicitly and can be switched off (universo_pit.py, PARCHES). Its effect is small: CBOE occupied 14 stock-months of the 50-stock multi-factor portfolio and none of the 20-stock one.

3. The data and its sources

Fundamentals: the SEC, and the filing date

Fundamentals come from what companies file with the U.S. Securities and Exchange Commission (10-K, 10-Q and, for insiders, Form 4). Every SEC data point carries two dates: end, when the period closed, and filed, when it was published. A fiscal year ending in December is not published until February or March. Here everything is indexed by filed (construir_fundamentales_pit.py): using a figure before it existed is information from the future, the classic error that inflates backtests across this industry. Point-in-time SEC data, straight from the primary source: something even most paid platforms do not offer, and it underpins every figure since Aug 21, 2026; before that, accounts come from Sharadar, which also dates them by their SEC filing.

How a day's snapshot is assembled

Field by field, not record by record (pit.py, CarteraPIT.foto()). The balance sheet is published quarterly; annual revenue and earnings only appear in the annual report. Taking a single record, the most recent one would almost always be a quarterly balance sheet carrying no revenue. So for each field separately, the most recent value already published on that date is used.

Restatements: if a figure is corrected later, on a given date the last version published up to that date is used — what an investor would have had in front of them that day. Never the later correction.

A 450-day expiry. Any field that has gone more than five quarters without an update is not returned: the company stopped publishing it, and the last known value no longer describes anything. Without that limit, eras get mixed inside the same snapshot — Amazon would show a gross profit of 5,531 million, from the 2009 close, alongside revenue of 775,680 million from 2026: a 0.71% gross margin instead of the real 48%, and a fall to 281st of 476 in Quality. The limit discards 2,005 of 54,033 served fields (3.7%), and one out of four gross margins.

A ratio cannot mix two filings. When two fields are divided, the result only means something if both describe the same period and the same version of the accounts; picked independently they do not guarantee that. The five Quality ratios and the accounting identity of the balance sheet are requested as pairs, and if no filing carries both fields, the component is left empty with its reason — never filled in by eye (pit.py, PAREJAS; factores.py, reason periodos_distintos). Measured on Aug 21, 2026: the pairs match in 2,479 of 2,505 cases, and where they do not, the difference is not decimals — mixing filings, OXY would show a gross margin of 1.0836, which after clipping gives the best possible Quality mark for a company whose real margin is 0.89.

Nothing can be published before its period closes. A record with filed earlier than end carrying financial data claims its earnings were known before the quarter ended. It is discarded. Across the file's 169,192 records there are exactly two, almost certainly a tagging error by the filer; they move no published figure and are dropped anyway. The share count is untouched: a 10-K declares on its cover the shares outstanding at a slightly later date, and that is how the SEC works.

TTM: the last twelve months, not the last fiscal year

Flow items —revenue, net income, EPS, operating cash flow, gross and operating margin— are trailing-twelve-month figures: for the history, Sharadar's as-filed TTM; in the daily calculation, rebuilt quarter by quarter and summing the last four. Stock items —equity, assets, liabilities, debt, shares, cash— are the last quarter's snapshot.

The classic problem with SEC quarterlies is that they come mixed: a 10-Q publishes both the standalone quarter and the year-to-date total, and the 10-K publishes the full year but not the fourth quarter separately. It is solved by subtraction: Q4 = full year − nine-month cumulative. For Apple FY2024, 391,035 − 296,105 = 94,930 million, which is exactly the fiscal fourth quarter Apple reported. The subtraction is always between two periods starting on the same day and under the same accounting tag; if a piece is missing and the chain cannot be built, it falls back to the plain annual figure.

The XBRL tags

The same accounting concept is tagged differently by company and by era. All of them are tried in preference order and the first one with data for that specific quarter wins (a whole tag is never dropped at once). These are the canonical ones (construir_fundamentales_pit.py, CONCEPTOS and BALANCE):

Fieldus-gaap tags, in preference order
Revenue (rev)RevenueFromContractWithCustomerExcludingAssessedTax · …IncludingAssessedTax · Revenues · SalesRevenueNet · SalesRevenueGoodsNet · RevenuesNetOfInterestExpense
Net income (ni)NetIncomeLoss · ProfitLoss · NetIncomeLossAvailableToCommonStockholdersBasic
Gross profit (gross)GrossProfit; and where the company does not publish it, derived by subtracting cost of revenue (CostOfRevenue, CostOfGoodsAndServicesSold, CostOfSales…)
Operating income (oper)OperatingIncomeLoss
Diluted EPS (eps)EarningsPerShareDiluted · EarningsPerShareBasicAndDiluted · EarningsPerShareBasic · IncomeLossFromContinuingOperationsPerDilutedShare · …PerBasicShare
Operating cash flow (ocf)NetCashProvidedByUsedInOperatingActivities · …ContinuingOperations
Equity (eq)StockholdersEquity · StockholdersEquityIncludingPortionAttributableToNoncontrollingInterest
Assets / LiabilitiesAssets · Liabilities
Debt (debt)LongTermDebtNoncurrent · LongTermDebt · DebtLongtermAndShorttermCombinedAmount · LongTermDebtAndCapitalLeaseObligationsIncludingCurrentMaturities · LongTermDebtAndCapitalLeaseObligations · NotesPayable
SharesWeightedAverageNumberOfDilutedSharesOutstanding · …SharesOutstandingBasic · CommonStockSharesOutstanding · dei:EntityCommonStockSharesOutstanding

Those choices are not cosmetic. Cost of revenue was added because Netflix abandoned GrossProfit in 2021 and McDonald's, Chevron, Dow, Nucor and Corteva never use it: with no gross margin they were left with two of Quality's five components, below the minimum of three, and dropped out of the factor entirely. Debt tags were added because the first three left banks, insurers, oil companies and REITs with no leverage figure. And SecuredDebt, UnsecuredDebt and SubordinatedDebt are not added even though many companies publish them: they are slices of debt, and using a slice as the total says the company owes less than it does, which is worse than not scoring it.

Exceptions are per company, never an automatic rule. There are two symmetric doors. REV_SECTORIAL and REV_COMPUESTO admit a tag for a specific company because there it genuinely is the whole top line (a REIT's rent, a bank's net interest income — things ASC 606 does not cover). ETIQUETAS_FRAGMENTO withdraws a tag for a specific company because there it is not: Bunge (BG) publishes RevenueFromContractWithCustomer* covering only 26% of its revenue —3,368 of 12,961 million in Q1 2021, 16,811 of 59,152 for the year— and one of its restatements beat the full figure on date, leaving a −48% gross margin at a company that earns +6.5%.

Why by hand: on Aug 21, 2026 four automatic recipes were tested against the annual figure each company itself publishes, and the only one that fixed BG touched 358 companies and moved 369 records further from the 10-K against 381 it moved closer, leaving PM, ORCL, MA, RSG and TKO with no revenue at all. The information needed to decide is in neither the magnitude nor the tag name: Revenues is the total at BG and a fragment at GE. Any rule fixes one and breaks the other.

Prices: what is discarded, and why

Price history up to Aug 21, 2026 comes from Sharadar (split-adjusted, with spin-offs rescaled and every company that left the index). Day to day, prices come from professional financial APIs: live quotes through a keyed, rate-limited API, and a second provider that acts as a cross-check on the first. The close used is split-adjusted but not dividend-adjusted (daily_update.js): dividends are added separately on their ex-date, and using an already dividend-adjusted price would count them twice. The symbol list lives in exactly one place, simbolos_yahoo.json: two copies of the same list always end up drifting apart.

Ghost bars

Volume exactly zero and all four prices (open, high, low, close) identical. That is not a quiet session: it is the provider dragging forward the last known value of something no longer trading. They are discarded (daily_update.js esFantasma(), and limpiar_fantasmas.py for what was already stored). Measured over the file's 801,506 rows on Aug 18, 2026: 615 rows meet both conditions and sit in only six tickers, all of them companies not trading in the U.S. during that stretch or that stopped doing so; zero false positives among 500 live companies. And rows with zero volume and non-flat prices number zero, so requiring both conditions excludes nothing. The case that shows the risk: Electronic Arts really closed on Aug 4, 2026 on 48.7 million shares, and the provider kept serving 209.70 at zero volume for four sessions — four closing prices that never existed, and that this filter keeps out.

Shadow quotes before the listing

For some companies the provider serves years of quotes predating their U.S. listing: SW carries Smurfit Kappa's Dublin quote from 2010, AMCR carries Australia's from 2012, FERG and DEC carry London's. With that stretch included, SW would score a momentum of 1.275 on Jul 8, 2024 —built on the Dublin shadow— when the honest answer is "history too short, no momentum" until July 2025. The ghost-bar filter only catches two thirds of those rows; the rest carry a fake volume and slip through. The real first-day dates are declared company by company in simbolos_yahoo.json (primer_dia), not guessed from volume: SW Jul 8, 2024, AMCR Jun 11, 2019, FERG Mar 8, 2021, DEC Dec 18, 2023.

Merger exchanges that the provider encodes as splits. AvalonBay stopped trading on Aug 14, 2026 and each share was exchanged for 2.793 Vivmark shares. The provider declares this as a 2.793:1 split dated 17/8 and serves AVB's entire series divided by 2.793 (65.90 where it closed at 184.06). It is not a split, it is an exchange, and treating it as a split would rescale 1,543 of 1,556 rows and leave a −64% cliff in June 2020 that never happened. AVB's series stays in its own scale and ends on the 14th; mapping it to VMRK would splice two companies together. The distinction is declared, with its evidence, in simbolos_yahoo.json: only pure, verified renames go in renombres —BNY gives exactly the same closes as BK across the 1,044 common dates, and ECHO the same as SATS across 1,045— while mergers go in delistados.

Real splits are applied, and applied backwards, across the full history: the download is compared with the stored data over the overlapping days and, if the median ratio drifts from 1 or the provider declares a split in the window, that ticker's entire series is re-downloaded. Rescaling only a window of days would leave the series split across two scales and manufacture crashes that never happened — KLAC from 1,940.04 to 192.92 (−90.1%), CVNA −80.3%, CRWD −75.4%, DD +209.1% from a reverse split — which is why the scale is always checked against the full series.

The market benchmark

The yardstick for the equal-weight version is the equal-weight S&P 500 computed from the same data as the portfolios: every company that was in the index each month, weighted the same, dividends reinvested, including the ones that later disappeared. It is the fair benchmark for a portfolio that also splits its money evenly: measuring it against a cap-weighted index would mix two different things —which companies were picked, and how the money was split— and hide which of the two is doing the work. The cap-weighted version is measured against SPY, the S&P 500 ETF, which has traded since 1993 and covers the whole window.

Until September 2026 the equal-weight yardstick was RSP, the fund that tracks that index, rather than an average of our own, for a reason that was true: an average of one's own universe only averages companies with available prices, so it inherits the very survivorship bias it is meant to judge (depending on which universe was averaged, it came out at +64.7% or +83.9% cumulative over a window in which RSP did +46.3%). With the long backtest that reason goes away —we now have prices for every index member at every date, including the ones that disappeared— and a new one appears: RSP did not exist before May 2003, and the backtest starts in 1998. So the yardstick is the calculation, and RSP becomes its check: over the 23 years in which both exist, the calculation is ahead by +0.55 points a year, which is the fund's fee plus its trading costs, and month by month they move almost identically (correlation 0.999, measured on Sep 11, 2026). If they ever drift apart by more than a point and a half a year, the site stops publishing the track record rather than show a broken yardstick.

An asymmetry worth declaring: neither the calculated yardstick nor our portfolios deduct costs, while RSP and SPY, being funds, carry their fee inside the price (0.20% RSP and 0.09% SPY). In the equal-weight version the comparison is like for like; in the cap-weighted version, SPY's fee works slightly in our favor.

And if the benchmark does not reach as far as the portfolio, the calendar is cut short and we say so — the gap is not filled. Dragging the benchmark's last close forward when it lags the stock prices would hand the market a rise it never made — four sessions of lag are worth an invented +0.87%. One fewer point on the curve is a visible gap; one point with two dates inside it is a false figure (regenerar_carteras_honesto.py).

4. The four factors, one by one

Here is the complete recipe, thresholds and limits included. There is one single definition of each factor and both halves of the system use it: the ranking you see and the backtest that measures it (factores.py). This is not housekeeping: with two definitions of the same factor —one for the backtest, one for the site's engine— you would be showing the returns of one next to the ranking of the other, which is not the same thing. Here it is one definition, in one file, shared by both halves.

Momentum

Twelve-month return skipping the most recent month, divided by annualized volatility. It comes from prices, not from accounts.

momentum = ( P[t−21] / P[t−252] − 1 ) / ( σ · √252 )
σ = population standard deviation of daily returns over the last 252 sessions

It requires 253 closes, more than 30 valid daily returns and strictly positive volatility; otherwise there is no momentum and the reason is stated (historia_corta, hueco_en_extremos, pocas_sesiones, volatilidad_cero). The 21-session skip is the standard in the literature: the most recent month is excluded because at one month the effect reverses.

Value — three yields on price, minimum 2 of 3

ey = EPS (TTM) / price earnings yield
by = (equity / shares) / price book yield
sy = (revenue TTM / shares) / price sales yield

Quality — five ratios, minimum 3 of 5

roe = net income / equity
roa = net income / assets
gm = gross profit / revenue gross margin
om = operating income / revenue operating margin
lev = debt / equity ENTERS INVERTED: more debt, worse

All five are requested as same-filing pairs (§3). And explicitly declared zero long-term debt is not a gap: it is the best possible leverage. Five index companies declare it today —CMG, FSLR, ODFL, SNDK and WDC— and that declared zero scores as what it is: the best possible leverage mark, never confused with a missing figure.

Growth — three year-on-year growth rates, minimum 2 of 3

g_rev = revenue(today) / revenue(one year ago) − 1
g_eps = EPS(today) / EPS(one year ago) − 1
g_ocf = operating cash flow(today) / same one year ago − 1

"One year ago" is another point-in-time snapshot taken under the same rule: only what was public that day (pit.py, foto_anterior()). The base must be positive: growing a percentage over a loss means nothing — from −16.50 to +2.81 is not "117% more earnings", it is something this ratio cannot measure. That is different from having no accounts for last year, and two different reasons are emitted (base_no_positiva and sin_ano_anterior). Boeing publishes all three of its prior-year figures; the point is that two of them are losses.

Before normalizing: what is thrown away and what is clipped

There are two ranges per component, and the difference matters a lot. POSIBLE is what a real company can actually have: outside it the data point is wrong —not an extraordinary company— and the component is discarded. LIMITES is the statistical clip inside what is possible: real but extreme values that, left unclipped, blow up the standard deviation and compress everyone else's Z-score.

Clipping broken data is worse than dropping it. Equity Residential showed a gross margin of 5,070 because its revenue tag for that period was 216,000 dollars instead of 2,900 million; clipped to 1.0 it did not end up "discarded", it ended up as the best in the index. That is how SBAC finished #4 of 476 in Quality, and how BRK-B was #1 in Value with the maximum mark, on a class-A EPS applied to the class-B share (a 4,253% earnings yield).

ComponentPOSSIBLE (outside: discarded)LIMITS (clipped)
ey — earnings yield−2.0 … 2.0−0.5 … 0.5
by — book yield0.0 … 20.00.0 … 2.0
sy — sales yield0.0 … 50.00.0 … 3.0
roe−10.0 … 10.0−1.0 … 1.5
roa−2.0 … 2.0−0.5 … 0.5
gm — gross margin−1.0 … 1.50.0 … 1.0
om — operating margin−5.0 … 1.5−0.5 … 1.0
lev — debt / equity0.0 … 50.00.0 … 3.0
g_rev — revenue growth−1.0 … 50.0−1.0 … 2.0
g_eps — EPS growth−10.0 … 50.0−1.0 … 2.0
g_ocf — cash-flow growth−10.0 … 50.0−1.0 … 2.0

Source: factores.py, constants POSIBLE and LIMITES.

The floors are asymmetric for a concrete reason. Revenue cannot fall more than 100%, so g_rev is floored at −1. EPS and cash flow can: they only need to cross into negative territory. A −1.16 there is not broken data, it is a company that went from making money to losing it — exactly what the factor should penalize; with the floor at −1 it was discarded and the company was left with no growth score.

Two sanity checks on the per-share metrics

Two, not one, because they do not depend on the same thing. Book value and sales per share only need the share count to be credible (at least one million; the index company with fewest shares is NVR, at about 2.9 million). Earnings yield additionally needs the EPS to reconcile: EPS × shares / net income must fall between 0.33 and 3.0.

The share minimum exists because several companies publish the count in millions under one tag and in units under another: McDonald's gave both 711.1 and 710,505,859, and at 711 shares McDonald's "capitalized" 195,000 dollars. The EPS band is deliberately wide, so as not to confuse a scale error with the legitimate effect of preferred stock: Albemarle pays around 190 million a year in convertible preferred dividends, its identity gives 0.16, and the data is correct. Each of the three metrics carries its own flag: an odd EPS does not take the other two down with it.

When a cell is blank, the reason is emitted by whoever blanked it

Every component that fails to score writes down its code and the figure behind it on the same line that discards it (factores.py, function _anotar; published in motivos_factores.json). An emitted reason cannot contradict the calculation because it comes out of the calculation.

The easy alternative —letting the browser guess the reason from which fields are missing— was ruled out by measuring it: a reason deduced from symptoms would tell Boeing "no accounts from a year ago" while all three prior-year figures are published, and NVR "banks and insurers, or they owe more than they own" — a homebuilder with 3,391 million of positive equity. Guessing is right one time in three; that is why the reason is issued by the calculation, not by the symptom.

The codes are: falta_en_la_sec, acciones_no_creibles, bpa_no_cuadra, patrimonio_no_positivo, ingresos_no_positivos, base_no_positiva, sin_ano_anterior, fuera_de_rango, sin_cuentas, periodos_distintos; and at factor level, pocos_componentes, sin_normalizar and clase_duplicada. In the Aug 21, 2026 session there were 41 blank cells across 499 companies: 39 for not reaching the minimum number of components and 2 for too short a price history.

5. How they combine: the Z-score

Each component is normalized separately and only then are the Z-scores averaged. In that order, and the order is the whole point.

Z(component) = clip±3 [ sign · (x − mean) / stdev ]
Z(factor) = clip±3 [ mean of the Z of its available components ]
Z(multi) = ( Z_momentum + Z_value + Z_quality + Z_growth ) / 4

Mean and standard deviation are computed over that date's scored universe, not over a historical table: the deviation is the population one, at least 20 companies must carry the component, and the deviation must be strictly positive. The sign is −1 for leverage (more debt, worse) and +1 for everything else. Winsorization at ±3 is applied twice, at component level and at factor level.

Why raw averaging does not work. Each component lives on a different scale, so whichever varies most swallows the rest. Measured on Jul 20, 2026 over the S&P 500, the true weight inside Quality when averaging without normalizing was: gross margin 54.2%, operating margin 45.0%, ROE 0.2%, ROA 0.0%, debt/equity 0.6%. In other words "Quality" was margins and nothing else: ROE, the first metric the site names, weighed two thousandths. Normalizing first makes all five count equally.

The multi-factor score only scores companies that have all four factors. Scoring one on two and comparing it with one scored on four is not a comparison. That is why the multi-factor ranking is shorter than the single-factor ones: in the 10/21/2026 session there were 494 companies with momentum, 491 with value, 491 with growth, 471 with quality and 464 with all four, out of a 503-name constituent list.

6. The walk-forward

Our backtest is built to answer one question only: what would genuinely have happened. The easiest way to make a strategy look brilliant is to pick the winners using today's data and then measure them against past prices —it produces spectacular figures and zero predictive value— so here it is done exactly the other way round, and reproducibly: the history up to Aug 21, 2026 was computed once and frozen, and each new month is added by the daily cycle (regenerar_carteras_honesto.py, and its laboratory twin backtest_honesto.py):

1. Stand on a date in the past
One rebalance every 21 sessions —roughly monthly— on the real trading calendar.
2. Score using only what was public that day
Prices up to and including that session, nothing later; fundamentals already filed with the SEC according to their filed field; and only companies that were in the index that day.
3. Buy the top N, equally weighted
10, 20, 50 or 100 positions, each with the same weight. The holdings table you see uses those same weights, because otherwise the curve would describe a different portfolio from the one being shown to you.
4. Move forward one period and repeat
With identical rules in every period. No parameter is re-tuned year by year to make the curve prettier.

Returns are total returns: each leg's return is (end price − start price + dividends) / start price, counting dividends whose ex-date falls inside the leg. The same treatment is applied to the strategy and to the market.

And a position that stops trading is closed, not evaporated. If the quote is cut mid-leg by a takeover, a merger or a delisting, returning "no data" would erase that company from the average as if it had never been bought, and survivorship bias would walk back in through the side door. It is liquidated at the last known price and that result counts.

The metrics, and how they are measured so they cannot flatter the portfolio

MetricHow it is computed
CAGROver real calendar years between the first purchase and the last point of the curve, not over "an average" 252/21 periods. With 59 rebalances the theoretical count would give 4.917 years when 4.942 actually elapsed, and that error would always favor the portfolio.
Sharpe / SortinoExcess over each day's 3-month Treasury bill (^IRX; between 0.02% and 5% depending on the year), divided by volatility (Sharpe) or by downside volatility only (Sortino), annualized at 12 periods per year.
Maximum drawdownMeasured daily, not at rebalance points, with the peak starting at the initial capital. Measuring it monthly hides several points, always in the portfolio's favor: on the 10-stock multi-factor the monthly figure comes out at −62.3% when the real daily drawdown reaches −65.9%, and on 10-stock momentum −74.2% against −77.6% (carteras.json, Sep 11, 2026). That is why Calmar is also computed on the daily drawdown.
Beta and alphaAgainst the equal-weight S&P 500 over the same window. Each chart range (1, 2, 3, 4 years or all) recomputes everything over that range: a full-history beta applied to one year would be mixing windows.
YTDAgainst the portfolio's value on the last trading day of the previous year, not against January's first rebalance, which would swallow the first two weeks.

Where each curve starts, and why not at zero

Since December 1998 the point-in-time universe already fills all twenty portfolios, so today they all start on the same day. The rule is still there for when that is not the case: a period with no portfolio is a declared gap, never an invented zero: one flat month that never happened would be enough to make two screens report two different CAGRs for the same cumulative return. The series starts at the first period where the portfolio genuinely fills, and a gap after the start is not filled in: the program stops and says so.

All five strategies of a given size start on the date the universe fills all five, which is the multi-factor date (it needs all four components at once and is the most demanding). With the 10/21/2026 file: the 10- and 20-stock portfolios start on 12/31/1998 and the 50- and 100-stock ones on 12/31/1998. Momentum, value and quality could have started earlier; starting later is not inventing a data point, but hiding it would be.

7. Costs

Very few sites tell you what following their strategy costs; here it is measured and published. And so you can read it with the cards face up, the important part first: no curve on this site includes costs. No commissions, no bid-ask spread, no slippage, no taxes. Your real result will be lower. It is written in the notices attached to every portfolio in carteras.json, and it is repeated here because it is the limitation that weighs most on a strategy that turns over every month.

What is measured, separately, is turnover and what it would cost (medir_senal.py). The assumption is written down: 10 basis points per side (0.10% of the traded amount, on each side), a round and deliberately conservative figure for S&P 500 names — it covers a retail broker's commission, half the spread of a large liquid company, and some slippage. It can be changed with --coste. Annual cost in points is annual_turnover × 2 × 0.0010, because each replacement is a sale and a purchase.

PortfolioPositions replaced per rebalanceAnnual turnoverAnnual cost at 10 bp/sideWith hysteresis band
Multi-factor 2025.3%303%0.61 points132% → 0.26 points
Multi-factor 1028.6%344%0.69 points167% → 0.33 points
Momentum 2035.9%431%0.86 points233% → 0.47 points
Value 2014.7%177%0.35 points54% → 0.11 points
Quality 205.7%68%0.14 points22% → 0.05 points
Growth 2015.4%185%0.37 points117% → 0.23 points

Source: medir_senal.py, measurement of Aug 14, 2026 over 59 rebalances between Aug 10, 2021 and Jul 20, 2026. Note: that run was made without the point-in-time universe (today's constituents) and over the short 4.9-year sample, so it is not the same window as the published curves. Turnover is one-directional: the annual figure is the average per period times 12.

The hysteresis band is the cheap way to cut turnover without changing the signal: a position is held while it stays inside the top 2N and is only sold when it drops below; the gaps are filled with the best names not already held. Same entry rule, looser exit rule. It is not applied to the portfolios this site publishes: it is a measurement, not the strategy.

The half a point a year above is the cost of turnover under an assumption worth saying out loud: 10 basis points per side, over 59 rebalances from 2021-2026 and without a point-in-time universe. The same portfolio, measured with medir_costes.py over the 330 rebalances of the long history, costs 31 bp a year assuming a quoted spread of 3 bp, and 219-274 bp with the spread our two estimators infer from daily highs and lows, which is an upper bound. The three figures are the same calculation under different assumptions, not three results that contradict each other. That is why every cost figure on this site says three things: whether the spread is assumed or estimated, at what commission and what account size, and over which window. Without those three, two numbers from the same calculation read as a contradiction. It does not include currency conversion —which for a euro-based investor can be the largest expense and is the easiest to avoid— nor taxes: a portfolio rebalanced monthly realizes capital gains monthly, while an index fund defers them.

8. What the tests say

Nearly three decades of reproducible walk-forward, file by file: few houses can show you a sample like this, and this one is ours. Every figure in this section comes from carteras.json exactly as published today, over the window 12/31/1998 → 10/21/2026: 27.81 years and 333 measurable months, the same for every portfolio size. (One more rebalance than months: the last one is already bought and its month has not closed.)

StrategyCAGRSharpeMax DDVolatilityAlpha vs equal-weightBeta
Multi-factor 1011.83%0.46−65.91%28.65%+3.57%0.76
Multi-factor 2011.32%0.47−63.55%25.32%+2.88%0.78
Multi-factor 5010.54%0.47−61.69%22.08%+1.94%0.80
Multi-factor 10010.66%0.50−59.72%20.58%+1.84%0.83
Momentum 108.32%0.35−77.58%28.35%+0.80%0.67
Momentum 209.85%0.42−65.31%25.14%+2.53%0.65
Value 2011.10%0.44−80.01%28.83%−3.17%1.50
Quality 2010.33%0.47−53.81%21.77%+1.60%0.82
Growth 207.86%0.34−70.12%26.70%−2.58%1.03
Equal-weight S&P 500 (calculated)10.22%0.48−60.41%20.23%0.00%1.00

Source: carteras.json (session 2026-10-21) (the equal-weight yardstick is measured by the same engine, on its daily curve and with the same formulas as the portfolios). Cumulative over the full window: multi-factor 20 +1,871% and the equal-weight S&P 500 +1,395%.

And a sample of how nothing gets dressed up here: read the Value and Growth rows carefully: they are the ones with negative alpha, and Value also has the highest beta and the deepest drawdown in the table (carteras.json, Sep 11, 2026). A strategy can finish up and still have beaten nothing, and it is published all the same.

The edge: measured, not proven

Putting your own edge through a statistical test and publishing the result even when it does not flatter you is exceedingly rare in this industry. We do it on every server start, on the published curve:

What the 333 months of the 20-stock multi-factor portfolio say

  • It beats the equal-weight S&P 500 by +0.11% a month on average and wins in 188 of 333 months (56%). Compounded, that is the ~1.1 annual points of CAGR difference.
  • The direction is consistent, but the margin is not large enough relative to its own variation: t = 0.46, and if the true edge were zero there would still be roughly a 32.2% chance of seeing 27.8 years this good or better by luck alone.
  • Stated as a range, which is more honest than a single number: with a stationary block bootstrap (Politis-Romano, six-month average blocks, 10,000 resamples with a fixed seed, recomputing the CAGR difference in each one), the annual edge lands between −4.0 and +6.4 points at 90% confidence and between −5.1 and +7.4 at 95%. Zero is inside both, and in 35.2% of resamples the edge comes out negative.

It is the most conservative of the three methods tested on Aug 22, 2026: the Student t and the excess bootstrap both excluded zero at 90% by a decimal or two; this one does not. The one that does not exclude zero is the one published. The calculation lives in server.js (getTrackRecord / intervaloVentaja) and is redone at every start from the published curve, so it cannot be hand-written or go stale.

The signal referee: does the score actually rank?

A portfolio backtest answers "how much would a 20-stock portfolio have made". That is one path: it depends on the cut at 20, on the luck of a handful of names and on the luck of the dates. To choose between two similar definitions of the same factor you have to measure the signal, not the portfolio (medir_senal.py). Four statistics, each with its exact definition written inside the file itself:

The information coefficient, on the long history

  • Measured over the 331 months of the long history (December 1998 to August 2026, Sharadar data with every company that was in the index, by-date universe), the one-month multi-factor IC is +0.0121 with a between-month deviation of 0.156: t = 1.41 (medir_senal.py, measurement of Sep 11, 2026). By the usual thresholds it is still not significant: the signal points in the right direction, separates from the noise somewhat more than on the short history, and not enough.
  • Returns are measured as total return —price plus Sharadar's dividends since 1998—, and the portfolio that comes out of the same run matches the published one: the 20-stock multi-factor makes 11.37% a year against 10.37% for the equal-weight index, the two figures on the home page.
  • On the previous history (185 months, 2011 to 2026, our usual data) it came out at t = 0.72, and measured from price alone over the stretch without dividends it would have appeared to score 0.99. The properly measured figure was published, not the flattering one.

Publishing t = 1.41 knowing it does not reach 2 sums up the house rule: the figure is the one that comes out, not the one that suits.

Information coefficient (IC)
Spearman rank correlation between the score on the decision day and the subsequent return, computed as the Pearson correlation of average ranks — not with the short formula, which only holds without ties, and winsorization at ±3 creates real ties at the cap. The IC-IR is the mean divided by the sample standard deviation of the series.
Decile ladder
The scored universe is split into ten groups and each group's average return is measured in excess of the average of everything scored that day. That is where you see whether the factor works across the whole range or only at the extreme: one that only distinguishes the tenth decile is a handful of names, not a factor. Monotonicity is the correlation between decile number and return, sign-flipped, so that +1 means a perfect ladder.
Overlap
At horizons longer than one period the observations overlap, which does not bias the mean but inflates apparent significance. So the IC-IR is also reported over the sub-sample of independent dates.
Look-ahead leakage
The context each variant receives exposes only what was public that day; there is no way to ask it for a later data point. And it is tested with poison: false figures dated after the queried date are injected and nothing may change — plus, as a power check, the same poison must change the score once it is genuinely in the past, because otherwise the test would prove nothing (verificar_backtest.py).
FactorMean ICIC-IRMonths with positive ICDecile monotonicityDecile 1 − decile 10
Momentum+0.0123+0.0657.1%+0.05+0.17%
Value+0.0103+0.0653.5%+0.90+0.33%
Quality+0.0020+0.0251.7%−0.58−0.06%
Growth−0.0011−0.0150.5%−0.77−0.09%
Multi-factor+0.0121+0.0855.6%+0.08+0.12%

Source: medicion_senal_larga.json (Sep 11, 2026), produced by medir_senal.py on Sharadar data. One-period horizon (21 sessions), 331 dates between Dec 31, 1998 and Aug 21, 2026, point-in-time universe: on each date, the index members of that day (between 490 and 502 scorable), out of the 1,124 companies that passed through it. Total return, dividends included. It is the same run as the figure in the box.

That table is worth reading in full, not just its last row. These are small ICs —a hundredth on average— with a between-month deviation of 0.12 to 0.21: the signals rank weakly and noisily. Value is the only factor whose decile ladder runs cleanly from top to bottom (monotonicity +0.90; at twelve months its best decile beats its worst by 3.4 points). Momentum has a positive IC but a flat ladder, quality has it upside down with an IC close to zero, and growth comes out inverted: at twelve months its worst-scored stocks return 2.8 points more than its best-scored. The multi-factor averages all of that: a positive IC and an almost flat ladder. On the short history quality came out inverted and momentum was the cleanest; with almost twice the history the picture changes, which is what you would expect from signals this weak. Everything is published because everything is measured; we do not pick the flattering part.

9. What we do not know

A section like this is one of the rarest things you will find on a financial site, and it is the one we are proudest of: telling you precisely where our knowledge ends is the best proof that everything else is genuinely measured. Nothing below subtracts from the above; it draws its boundary.

The other half of survivorship bias, almost closed

The membership side is solved: on each date only companies that were in the index that day are eligible. The price side is too, almost entirely: since the history comes from Sharadar we hold the series of the companies that left the index, and on the decision dates only about 0.1% of member-slots have no price. With our earlier data, almost a third of the 2011 members had none. What remains is measured and declared in every portfolio's notices.

It is still one single history of the market

27.8 years of monthly buy decisions. It covers the dot-com bust, the 2008 crisis, the 2020 crash and the 2022 bear market. Many cycles, but of a single market. And the 333 months are not 333 independent observations: a portfolio that replaces a quarter of its positions per month carries the same companies for quarters at a time.

The edge is not proven

Both ways of looking at it agree. On the portfolio side, zero falls inside the 90% and 95% confidence intervals; on the signal side, the multi-factor IC, measured on the long history, gives t = 1.41 (§8). We are not saying it does not work: we are saying that not even 27.8 years and five hundred companies are enough to separate it from noise in the equal-weight version, and that anyone investing should allow for that possibility. Part of that uncertainty is purely statistical and no amount of better code fixes it: it would take more history than exists.

The factors are not weighted against each other

All four enter with equal weight —an arithmetic mean— and that is a decision, not a result. And worth knowing: an arithmetic mean of four Z-Scores does not split 25/25/25/25. It splits by how much each one spreads. Measured on Aug 31, 2026 across 188 rebalances, on the 2011-2026 history before it was extended back to 1998 (so the portfolio returns quoted below are from that window, not today's §8 table): momentum 34.0%, growth 23.4%, value 22.2%, quality 20.3%. Making them genuinely equal —normalizing each Z again before averaging— was tested and comes out worse: ranking power drops (paired t −0.81) and the 20-stock portfolio goes from 16.4% to 15.4% net annual. So the split stays as it is, and now because of a measurement rather than caution alone. Weighting them by past performance would still be fitting the model to the very sample used to judge it.

The data belongs to third parties

Sharadar for the history; the SEC, the market APIs and the Wikipedia constituents table day to day. Their accuracy is not under our control: they can carry source errors, later revisions or publication lags. The calculation is automated, and a failure in one source can propagate into a score without anyone reviewing it by hand that day. That is why twenty-nine verifiers (§10) watch the seams with every source: here, distrust of the data is automated.

A verifier only catches what someone thought to check

Twenty-nine green guardians do not prove the site is right: they prove that the twenty-nine classes of failure most damaging to a data site are not present today. A class of failure nobody thought to check can still happen in full (verificar_todo.py, in its own words).

There is also a lag that is not a bug and is worth understanding: fundamentals are quarterly and a quarter takes about six weeks to reach the SEC. Until then the model carries on with the previous quarter. That is the same delay any real investor faces, not a bias.

10. How to check it yourself

We are not asking for an act of faith: we hand you the data. Publishing a methodology is worth little if the numbers cannot be downloaded, so everything this site paints downloads free and without an account, from the same file that paints the table: it cannot say anything different from the screen.

WhatWhere
The full daily ranking, as CSV
All ~500 companies with their Z-score for each factor and their price; not ten rows with the rest behind a signup.
/api/descarga/ranking.csv?factor=multifactor
factor: multifactor, momentum, value, quality, growth
A portfolio's walk-forward curve, as CSV
Each date is a rebalance, with the portfolio and the equal-weight S&P 500 rebased to 100 and each month’s return. Careful with what this file does NOT let you rebuild: it is monthly, and the Sharpe, Sortino, volatility, max drawdown and Calmar in the risk box are computed on the daily curve, which is not published. Monthly points give different figures, and always more flattering — for the 20-stock multi-factor portfolio, Sharpe 0.52 instead of 0.47, volatility 21.21% instead of 25.35%, and a max drawdown of −61.14% instead of −63.55% — so what we publish is the harsh number, not the pretty one. The CAGR (11.37%), beta, alpha, Treynor and share of winning months do reproduce exactly from here. Checked on Sep 12, 2026 by recomputing them with independent code over the 331 monthly points from Dec 31, 1998 to Aug 21, 2026.
/api/descarga/curva.csv?id=multi_20
id: multi/spmo/value/qual/growth _10/_20/_50/_100
The portfolios and their metrics/carteras.json — includes performance, history, assets, fechaCompra, metodologia and avisos for each
The daily ranking/algoritmos.json
Why a cell is blank/motivos_factores.json — the code and the figure emitted by whoever discarded the component
Ranking history and the decile ladder/api/ranking-historico, /api/ranking-historico/deciles?factor=…, /api/ranking-historico/serie
Spreadsheet downloads/api/descarga/estrategias.xlsx, /api/descarga/deciles.xlsx

Every CSV carries its date in the header and in the filename, so that a month from now you know when it is from.

The twenty-nine guardians

A check that runs only once is not a guarantee: it is a snapshot of one good day. Twenty-nine verifiers run over the data before it is published, from a single command (verificar_todo.py), and they distinguish three states rather than two: fine, failed and not examined — because "I could not check it" is not "it is fine", and a verifier that conflates the two passes things falsely.

GuardianWhat it checks
verificar_carteras.pythat the published backtest is neither circular nor inflated
verificar_ventanas_coherentes.pythat a portfolio’s fact sheet and the chart beside it report the same return
verificar_frescura.pythat no file has stopped advancing
verificar_misma_pantalla.pythat two figures with the same name do not contradict each other on the same screen
verificar_aviso_ciclo.mjsthat the email alert fires when the cycle fails, and stays quiet when it should
verificar_datos.pyprice seams and consistency between files
verificar_backtest.pythe backtest figures, including the poison test against look-ahead leakage
verificar_maquetacion.mjsgaps, overflows and jumps across 36 pages by 4 widths, in a real browser
verificar_precios_cruzado.pythat published prices reconcile with a second source
verificar_cadenas.pythat the server cycle and the laptop cycle regenerate the same things
horario_mercado.test.jsthat the cycle never picks the wrong session: weekends, holidays, clock changes
verificar_ranking_vs_simulador.jsthat every portfolio is the ranking's top N and that no company has two prices
verificar_simulador_snapshot.pythat the simulator reproduces the published curves — the "same machinery" promise
verificar_seo.mjswhat a search engine sees: canonical, hreflang, sitemaps and titles, against production
verificar_cartera_comprada.pythat the portfolio shown as held is the one from the last rebalance, not today’s ranking
verificar_insiders.pythat the Form 4 chain arrives whole: buys and sells, not sells only
verificar_presentaciones_sec.pythe only one that asks the SEC instead of our own files: that every 10-Q and 10-K filed has been ingested
verificar_rutas.mjsthat no page blows up while being served: 64 routes, one per family, before anything ships
verificar_series_ajenas.pythat each company’s price series is its own and not another company’s
verificar_capitalizacion.pythat no company carries the market cap of one share class with the price of another
medir_cobertura_pit.pythat the file the backtest reads covers the index of every date, not just today's
verificar_navegacion.mjsthat clicking a ranking row, picking a company in the header search or switching company inside the profile all land on the company you clicked, not the default one
verificar_url_estable.mjsthat the address a visitor arrives at is still in the bar after the page loads: pages reusing another section’s view rewrote themselves, and a reload lost the page
verificar_sesiones.mjsthat a revoked session stops working on all six endpoints that check it, that the email pass is not an access token, and that alerts can be unsubscribed without signing in
verificar_barrido_visitas.mjsthat the visit log really is deleted after 12 months as the legal page promises, and that rotating it loses none of the visits arriving mid-rotation
verificar_accesibilidad.mjsthat the site can be used with a keyboard and a screen reader: focus, names, table headers, real contrast and declared language
verificar_jsonld.mjsthat the structured data a search engine reads says the same as the page a visitor sees, and that every download it promises answers
verificar_reciente_ssr.mjsthat what the server paints and what app.js rewrites on top of it say exactly the same thing: a page that changes its own text while it loads shows the visitor a flicker, and leaves a search engine reading the shorter version
verificar_ficha_ssr.mjsthat the figures the company page ships already written in its HTML are exactly the ones app.js leaves in place a second later: those 27 numbers are written twice, once by the server and once by the browser, and when the two drift the visitor watches the largest figure on the page reformat itself

One on that list, medir_cobertura_pit.py, watches a risk none of the others do: the backtest silently falling back to a fundamentals file holding only today's companies —507 instead of 880—. That would leave 205 of the 498 index members of February 2011 with no fundamentals, pick the 2011 top 20 from survivors, and inflate the 20-stock multi-factor portfolio by +2.06 CAGR points — measured in August 2026 on the previous history, which started in 2011. And the backtest verifier would not catch it: its poison test looks for look-ahead leakage, and this is not leakage, it is selection bias. That is why coverage of every date has a guardian of its own.

If you find a figure that does not add up, write to [email protected] naming the page and the value. Errors get corrected, not argued about.

Every daily calculation is redone three times each business day (00:00, 15:31 and 22:20 Madrid time) and the data files are rewritten in full on each cycle; the history up to Aug 21, 2026 is frozen and not recomputed. The live figures —the ones that change from one cycle to the next— are written by the server as it serves the page, straight from the published file. The one-off measurements are typed by hand, which is why each carries the date it was taken: nobody regenerates them, and a measurement without a date would be worth nothing.

Who is behind this

There is no newsroom, no team and no research desk. This is built by Pol Gracia Serrahima, an AI and automation engineer (n8n) based in Cerdanyola del Vallès, Spain, who has been investing in the S&P 500 for over twenty years. He built the whole site on his own: the engine that scores the index constituents on every market day, the checkers that watch it, and every page you are reading, this one included. He is the site owner, by name, company and tax ID, in the legal notice.

One person working alone does not ask to be believed: he shows the method. That is why this page is this long, and why the uncomfortable parts are published next to the good ones — where every figure comes from (§3), what it costs to run (§7), what the tests say (§8) and what we do not know, starting with the fact that the edge is measured but not proven. Nobody picks or hand-edits any holding in any ranking or portfolio: the algorithm does it, with the rule written above. Backtest results are simulated, not real trades, and past performance does not guarantee future returns.

He also declares his own side. The shares he holds that appear on this site are published, with the date, in the declaration of interests, and updated the same day they change; they play no part in the algorithm, which produces the same output whether he holds them or not. Quant500 takes no money from the companies it covers, from brokers, from advertising or from affiliate links.

And what comes out of all this is given away. The rankings, the model portfolios, the company profiles, the simulator, the guides and the CSV downloads in the section above are open, free and require no account: the full daily ranking with every factor score, not ten rows with the rest behind a signup. Publishing daily since August 2026.

The model portfolio, weekly in your inbox

Every Saturday, its week in one minute: how it went, the best and the worst, and which insiders bought with their own money. Plus, the day the model is rebuilt, which stocks come in and which go out. A simulation, not advice. At most one email a week, no ads, no account; one-click unsubscribe. Made by one person: Pol Gracia.

A model portfolio with simulated results, not a personalized recommendation. Your email is used for this only: privacy.

S&P 500 market gravity