Factor investing starts with a question: which companies are the most profitable, the most efficient, the cheapest relative to their fundamentals? Answering it means computing ratios across thousands of filers, normalizing for sector, and ranking within a cohort, all while respecting point-in-time data so a backtest never sees the future.
3spread ships 90 precomputed factors covering profitability, growth, cash flow, efficiency, leverage, and composite scores (Altman Z, Piotroski F, Beneish M). Every factor carries a percentile rank within its sector and period cohort, and every value is stamped to the filing that produced it, so you can replay exactly what was known at any date.
This guide pulls a single company's factor profile, then runs a cross-company screen sorted by return on equity.
Everything below runs on the free Community tier: every dataset, 10,000 requests per day (600 per minute), no credit card.
Setup
pip install py3spread
export THREESPREAD_API_KEY="sk_live_..."Grab a key at 3spread.com/auth/signup. The client reads
THREESPREAD_API_KEY from the environment, so you never put it in the code.
List the available factors
from py3spread import Client
with Client() as client:
names = client.financials.factor_names()
# Group by category so the output is scannable
by_cat = {}
for f in names["data"]:
by_cat.setdefault(f["category"], []).append(f["name"])
for cat in sorted(by_cat):
factors = by_cat[cat]
print(f"{cat} ({len(factors)})")
for name in factors:
print(f" {name}")Categories include profitability, growth, cash_flow, efficiency, health, leverage, and composite. Each factor has a formula description in the response so you know exactly what the number means.
Pull a company's factor profile
from decimal import Decimal
TICKER = "AAPL"
with Client() as client:
page = client.financials.factors(
ticker=TICKER,
version="latest",
period_type="FY",
latest_only=True,
limit=100,
)
print(f"{TICKER} factor profile (latest FY):")
print(f"{'factor':<30} {'value':>15} {'pctile':>8}")
print("-" * 55)
for row in page["data"]:
val = row["value"]
pctile = row["value_pctile"]
if val is None:
print(f"{row['factor_name']:<30} {'null':>15} {str(pctile or '-'):>8}")
else:
print(f"{row['factor_name']:<30} {Decimal(val):>15.4f} {pctile:>8.1f}")latest_only=True collapses the series to the newest period per factor, so one
call gives you the current snapshot. The value field is a raw fraction (ROE of
1.57 means 157.4 percent, not 1.57 percent). The value_pctile field is the
precomputed rank within the company's sector and period cohort, 0 to 100.
If a factor is null, the flag field tells you why: div_zero for a division by
zero, neg_base for a negative base period, insufficient_inputs when the
underlying statement did not have enough data. Null-faithful means a
non-computable factor is null with a reason, never a zero that would pollute a
screen.
Screen the universe by a factor
The same endpoint does cross-company screens. Drop the ticker, pass a
factor_name, and sort by value:
screen = client.financials.factors(
factor_name="roe",
version="latest",
period_type="FY",
period_end="2024-12-31",
sort="value",
order="desc",
limit=20,
)
print(f"\nTop 20 by ROE, FY ending 2024-12-31:")
print(f"{'ticker':<8} {'company':<30} {'ROE':>12} {'pctile':>8}")
print("-" * 60)
for row in screen["data"]:
ticker = (row.get("filer_tickers") or ["-"])[0]
name = (row.get("filer_name") or "")[:28]
val = Decimal(row["value"]) if row["value"] else Decimal("0")
pctile = row["value_pctile"] or 0
print(f"{ticker:<8} {name:<30} {val:>12.2f} {pctile:>8.1f}")The period_end pin matters for a screen. Without it, rows from different fiscal
periods are mixed. Pinning to a specific date ensures every row in the response
comes from the same cohort, so the percentile ranks are directly comparable.
Point-in-time factors for backtesting
For a backtest, you need the factor values that were known on a specific date, not
the latest restated version. The version parameter handles this:
pit = client.financials.factors(
ticker=TICKER,
factor_name="roe",
version="as_of:2024-01-01",
period_type="FY",
limit=5,
)
print(f"\n{TICKER} ROE, point-in-time as of 2024-01-01:")
for row in pit["data"]:
val = Decimal(row["value"]) if row["value"] else None
print(f" period: {row['period_end']} ROE: {val} filed: {row['accepted_time']}")version="as_of:YYYY-MM-DD" selects the newest filing that was public on that
date. A restatement filed later does not appear, so the factor values your
backtest sees are exactly what a live strategy would have seen on that day.
Where to take it
Combine factors into composite screens. Pull ROE and debt-to-equity in two calls,
join on CIK, and filter for high-return, low-leverage names. Or use the
precomputed composite scores directly: altman_z_book for distress risk,
piotroski_f for financial strength, beneish_aqi for earnings manipulation
risk.
The factors/percentile endpoint goes deeper than the precomputed
value_pctile field. It lets you freeze the cohort at an arbitrary date and
get the exact percentile rank against the sector peers that were public then,
which is what a rigorous cross-sectional backtest needs.
The dataset
Full endpoint reference, response schemas, and every parameter are on the financials dataset page. The interactive API reference has the complete specification.
The py3spread client (v0.3.0) wraps all 11 financials endpoints with typed method signatures and automatic pagination. See the client documentation and the financials example script for statements, metrics, ratios, factors, and percentile ranks in one file.