Getting started¶
This page takes a deterministic synthetic return panel through validation, estimation, inspection, and export. No network or external dataset is needed.
Install¶
python -m pip install mfdro
python -m pip install "mfdro==0.2.0"
git clone https://github.com/jeyllani/mfdro.git
cd mfdro
python -m venv .venv
source .venv/bin/activate
python -m pip install -e .
MFDRO requires Python 3.10 or newer. The editable installation is for contributors and repository examples; ordinary users should install the PyPI release.
1. Prepare daily simple returns¶
import numpy as np
import pandas as pd
rng = np.random.default_rng(20250301)
dates = pd.bdate_range("2018-01-01", "2022-12-30")
factor = rng.normal(0.0002, 0.008, size=(len(dates), 1))
noise = rng.normal(0.0, 0.006, size=(len(dates), 6))
returns = pd.DataFrame(
factor + noise,
index=dates,
columns=[f"asset_{index:02d}" for index in range(6)],
)
Rows are observation dates, columns are assets, and values are simple returns, not prices or log returns. Read the data contract before substituting production data.
2. Choose an estimator¶
The projected preset is fast enough for exploration and explicit about being an approximation:
from mfdro import MultiFrequencySignal, SignalConfig
config = SignalConfig.projected(
n_projections=100,
n_quantiles=100,
random_state=20250301,
)
engine = MultiFrequencySignal(config)
SignalConfig.reference() returns the free-support reference configuration.
Use the full constructor when changing scaling, weights, distance, or frequency
definitions. The configuration guide explains the
scientific trade-offs.
3. Run a preflight check¶
diagnostics = engine.validate_path_inputs(
returns,
lookback_months=36,
seed_namespace="synthetic_example",
)
print(diagnostics.summary())
print(diagnostics.formations.head())
This constructs and validates every rolling window without solving transport
problems. diagnostics.n_ready tells you how many requested formations can be
estimated; ordinary warm-up appears as an insufficient formation instead of an
exception.
4. Estimate the path¶
path = engine.estimate_path(
returns,
lookback_months=36,
on_insufficient="skip",
seed_namespace="synthetic_example",
)
The compact accessors cover common analysis:
print(path.rho.tail())
print(path.sqrt_rho.tail())
print(path.summary())
The complete evidence remains in three stable tables:
| Attribute | Contents |
|---|---|
path.estimates |
rho, sqrt_rho, frequency distances, weights, seeds, sample sizes, and config identity |
path.audit |
Window boundaries, asset identity, matrix checks, and sample sizes |
path.skipped |
Requested formations not estimated, with reason and detail |
print(
path.estimates[
["date", "rho", "sqrt_rho", "distance2_daily", "distance2_weekly", "distance2_monthly"]
].tail()
)
print(path.audit[["date", "n_daily", "n_weekly", "n_monthly"]].tail())
print(path.skipped[["date", "reason"]].head())
5. Preserve the experiment¶
config.write_json("artifacts/config.json")
path.save("artifacts/signal_path")
# Later, possibly in another process:
from mfdro import SignalPath
restored = SignalPath.load("artifacts/signal_path")
assert restored.config.digest == config.digest
SignalPath.save refuses a non-empty destination by default. Pass
overwrite=True only when replacement is intentional. The export contains
JSON tables, the configuration, a versioned manifest, and checksums.
Point estimate from precomputed measures¶
Use estimate when frequency measures are built by another controlled
pipeline:
estimate = engine.estimate(
{
"daily": daily_matrix,
"weekly": weekly_matrix,
"monthly": monthly_matrix,
},
seed=1234,
)
print(estimate.rho)
print(estimate.frequency_squared_distances)
print(estimate.to_series())
Labelled DataFrames are aligned to the first measure's columns. Unlabelled arrays must already share exactly the same asset order.
Next steps¶
- Follow the end-to-end workflow for real research data.
- Continue through the scientific notebook suite.
- Learn how to read every output field.