Guide
Factor risk without Yahoo data.
The usual way to add factor exposures to an app is to download prices from Yahoo Finance or Stooq and run a regression. That works in a notebook and breaks in a product. Here is what to do instead. Checked November 8, 2026.
Why the usual approach breaks
- Yahoo Finance is for personal use. The yfinance README says the Yahoo Finance API “is intended for personal use only”, and the endpoint is unofficial, so it can change without notice.
- Stooq no longer serves scripts. Its CSV download now returns an HTML page that requires JavaScript to verify the browser (we checked from a server on 2026-11-08), and its terms are for personal use.
- Cheap data plans are often internal-use only. Showing numbers derived from a vendor's prices to your own users can need a display or redistribution license. Check your plan before you ship.
- Hand-rolled regressions hide gaps. Missing history quietly becomes a NaN, a forward-fill or a zero, and delisted names drop out of the history.
What to do instead
- Use prices you are allowed to use: your broker or custodian export, your own database, or a vendor plan whose terms cover your use.
- Send the positions and those daily closes to Creo. It returns named exposures (market beta, size, momentum, low volatility, liquidity), risk contribution per position, coverage and quality flags. Anything it can't compute comes back null with a reason, never zero.
- For value and quality, add the opt-in
*.secfactors. Creo reads the issuer's own 10-K filings on SEC EDGAR, which are public records, and cites each filing's accession number. - Prototyping before you have prices? The five
SYNTH.*demo tickers work without a key and are labeled as synthetic.
Before
# What agents usually write. Fine on your laptop; not for a product. import yfinance as yf import statsmodels.api as sm prices = yf.download(["AAPL", "MSFT", "SPY"], period="2y")["Close"] returns = prices.pct_change().dropna() model = sm.OLS(returns["AAPL"], sm.add_constant(returns["SPY"])).fit() print(model.params)
After
Mint a free key once with
curl -s -X POST https://itscreo.com/v1/keys -H 'content-type: application/json' -d '{}'
and export it as CREO_KEY. Then:
# pip install httpx pandas
# closes: a pandas DataFrame of daily closes you are licensed to use
# (index = dates, one column per ticker), from your broker, database
# or a data vendor whose terms cover your use.
import os
import httpx
import pandas as pd
def factor_exposures(
closes: pd.DataFrame, shares: dict[str, float], benchmark: pd.Series
) -> dict:
def series(column: pd.Series) -> list[dict]:
column = column.dropna()
return [
{"date": d.date().isoformat(), "close": float(c)}
for d, c in column.items()
]
body = {
"positions": [
{"ticker": t, "quantity": q, "history": series(closes[t])}
for t, q in shares.items()
],
"benchmark": {"history": series(benchmark)},
}
res = httpx.post(
"https://itscreo.com/v1/portfolio/exposures",
json=body,
headers={"x-api-key": os.environ["CREO_KEY"]},
timeout=60,
)
res.raise_for_status()
return res.json()
Without market_cap the size factor is null with
MISSING_MARKET_CAP, and without volume liquidity is null too.
Add them to each position when you have them. Beta and momentum need 253 daily closes.
The quickstart has TypeScript and curl versions.
For coding agents
Connect the hosted MCP server and call score_portfolio instead of writing
a regression. It is listed in the official MCP Registry as
com.itscreo/creo.
claude mcp add --transport http \ creo https://itscreo.com/mcp \ --header "x-api-key: $CREO_KEY"
Agent-readable docs: llms.txt, llms-full.txt and openapi.json.
When Creo is the wrong tool
Creo returns raw characteristics, not cross-sectional z-scores, and has no factor covariance or specific-risk model. If you need a full risk model, see how Creo compares with riskprism, Eulerpool and licensed models.
Not legal advice. Data terms change; read the terms of any source you use. Something wrong here? Tell us.