Statistical analysis¶
lazytools.statistical_analysis gives a LazyBridge agent read-only tools for
analysing historical hub series (by default as log returns):
statistical_return_volatility— sample standard deviation and (for return series) annualised volatility per instrument;statistical_return_correlation— pairwise Pearson-correlation matrix and the number of shared observations for every pair;statistical_return_outliers— dates where the absolute z-score of a value is at least a threshold (default2);statistical_regression_ols— univariate/multivariate OLS via statsmodels (robustHC0–HC3or Newey-WestHACstandard errors, confidence intervals, R², F, AIC/BIC, Durbin-Watson, residual diagnostics);statistical_regression_ridge/statistical_regression_lasso— regularised fits via scikit-learn;alphaempty selects it by cross-validation, Lasso also reports the surviving (non-zero) regressors. At most 10 regressors. These needlazystats[regression]installed.
Series specs and transforms¶
Every instrument argument accepts '<id>[|<transform>]' specs, comma-separated:
ticker:SPY, ticker:AAPL|level, macro:FEDFUNDS|diff, factor:FF5_daily/Mkt-RF.
Transforms are the hub's own (level, log_return, pct_change, diff);
when omitted the default is per-domain: tickers log_return, Fama-French
factors level (they are stored as decimal returns — weekly/monthly requests
compound them correctly instead of taking the last print) and macro series
diff (log/pct are undefined on series that cross zero). Mixed-domain panels
are outer-joined on dates; pairwise statistics and regression alignment use
the shared dates downstream. A regression dependent variable can therefore be
any hub series — a ticker return, a price level (ticker:AAPL|level) or a
macro series.
The tools have one data path: they read the full return matrix through
market_data_hub.extract.extract_returns. They do not call the Stooq connector,
accept user-provided price vectors, download data, or write to DuckDB. This is
important: the generic datahub_get_returns agent tool limits its JSON response
to protect model context; using that truncated response for a statistic would
silently make a long-window calculation wrong.
The complete matrix remains in the tool process. Tool results contain only aggregate metrics and, for the outlier tool, the flagged rows; metadata is allow-listed so a raw-return payload cannot leak into an LLM context through a future backend change.
Install and add to an agent¶
market-data-hub is the central private data dependency and must be installed
from its approved Git source:
G="git+https://github.com/selvaz/LazyTools.git"
pip install "lazytoolkit @ $G"
pip install "market-data-hub @ git+https://github.com/selvaz/market-data-hub.git"
Quickstart scripts¶
The minimal, zero-boilerplate shape — one tool provider, one natural-language
question, no bootstrap scaffolding. Both read LB_MODEL (default
claude-haiku-4-5) and need a matching provider API key (e.g.
ANTHROPIC_API_KEY) plus market-data-hub reachable (MARKET_DATA_DB
optional). agent(...) never raises — a failed call comes back as an error
envelope, so both scripts check result.ok before trusting result.text()
and print result.error otherwise (missing key, missing package, etc.). The
code below is embedded straight from examples/ — it is the actual,
currently-passing script, not a copy that can drift out of sync.
Volatility, correlation and outliers (examples/statistical_analysis_quickstart.py):
"""Minimal LazyBridge agent using the statistical_analysis tools.
The canonical, zero-boilerplate shape: build an Agent with one tool
provider, ask it a question in plain language, print the answer. The tool
descriptions and per-parameter docstrings (see
``lazytools/statistical_analysis/tools.py``) are what let the model figure
out instrument specs, transforms and frequencies on its own — nothing here
tells it how to call the tools.
Requires:
- an LLM provider API key matching MODEL below (e.g. ANTHROPIC_API_KEY);
- market-data-hub reachable (optional MARKET_DATA_DB env var; the hub's
default path is used otherwise).
Run:
python examples/statistical_analysis_quickstart.py
"""
import os
from lazybridge import Agent
from lazytools.statistical_analysis import StatisticalAnalysisTools
MODEL = os.getenv("LB_MODEL", "claude-haiku-4-5")
agent = Agent(MODEL, tools=[StatisticalAnalysisTools()])
result = agent(
"Compare SPY and TLT: what is their weekly volatility and correlation "
"over 2023-2024? Then flag any weekly return outliers for SPY beyond "
"2 standard deviations."
)
if result.ok:
print(result.text())
else:
# agent(...) never raises: a failed call comes back as an error
# envelope, so a real script must check .ok before trusting .text().
print(f"agent call failed: {result.error}")
OLS / Ridge / Lasso across domains (examples/regression_quickstart.py) —
a ticker return regressed on another ticker, a Fama-French factor and a
macro level in the same call:
"""Minimal LazyBridge agent using the regression tools (OLS / Ridge / Lasso).
Same shape as ``statistical_analysis_quickstart.py``: one tool provider, one
natural-language question. This scenario also shows that the dependent
variable and regressors can span domains — a ticker return, a Fama-French
factor and a macro series in the same regression — because the agent only
ever sees instrument specs, never raw data.
Requires:
- an LLM provider API key matching MODEL below (e.g. ANTHROPIC_API_KEY);
- lazystats[regression] installed;
- market-data-hub reachable (optional MARKET_DATA_DB env var).
Run:
python examples/regression_quickstart.py
"""
import os
from lazybridge import Agent
from lazytools.statistical_analysis import StatisticalAnalysisTools
MODEL = os.getenv("LB_MODEL", "claude-haiku-4-5")
agent = Agent(MODEL, tools=[StatisticalAnalysisTools()])
result = agent(
"Run an OLS regression of SPY weekly returns on TLT and the Fama-French "
"market factor (FF5_daily/Mkt-RF) since 2020, with Newey-West standard "
"errors. Then run a Lasso with the same regressors plus FEDFUNDS (as a "
"level, not a return) and tell me which regressors survive."
)
if result.ok:
print(result.text())
else:
# agent(...) never raises: a failed call comes back as an error
# envelope, so a real script must check .ok before trusting .text().
print(f"agent call failed: {result.error}")
Run either directly:
For a real end-to-end DeepSeek tool-call smoke test from Spyder that also
asserts the tools were actually called (rather than just printing the
answer), run examples/run_statistical_analysis_deepseek.py from the
repository checkout. It defaults to the low-cost deepseek-v4-flash model,
reads the API key from DEEPSEEK_API_KEY or the workspace deepseek.env,
and asserts that the agent actually invoked each of the three statistical
tools.
from lazybridge import Agent
from lazytools.connectors.datahub import DataHubTools
from lazytools.statistical_analysis import StatisticalAnalysisTools
agent = Agent(
"claude-opus-4-8",
tools=[DataHubTools(), StatisticalAnalysisTools()],
)
Use canonical lazydatacore identities such as ticker:SPY,ticker:TLT.
Bare symbols remain a convenience input and are canonicalised by the hub. The
statistics surface supports ticker:, factor: and macro: instruments
(crypto: and macro_panel: are rejected with a clear error). All outputs
are lazydatacore.AnalysisResult JSON with source/provenance set to
market-data-hub.
Parameters and interpretation¶
Every tool receives instruments, optional inclusive start/end dates and
frequency (D, W, M or Q; default D). Returns are computed by the hub
after resampling levels, so weekly/monthly returns compound correctly.
Volatility reports both the standard deviation at the requested return frequency
and annualised volatility using sqrt(252), sqrt(52), sqrt(12) or sqrt(4).
Correlation is pairwise: each coefficient only uses dates for which both
instruments have a return, and its accompanying pairwise_observations matrix
makes that sample size explicit.
Outlier z-scores are cross-sectional in time within each instrument's selected
period, not rolling: z = (return - period_mean) / sample_standard_deviation.
An observation is flagged when abs(z) >= threshold; threshold=2.0 is the
default. The result gives the date, canonical instrument, log return, signed
z-score and direction. max_results defaults to 100 and has a hard cap of 250;
the result preserves the true total_outliers and sets truncated: true when
the selected output limit is reached.