An open, pluggable framework for composable quantitative workflows. Start with FRED. Expand to anything.
Inspired by Karpathy's autoresearch — the same three-layer contract (immutable evaluator, agent sandbox, human direction), applied to quantitative finance as an extensible framework.
This is a framework — not a product. FRED is the hello-world connector. Everything else is an extension of the same pattern.
- Python 3.12.10+
- uv — Python package manager
- FRED API Key — Get one free from FRED
# Clone the repository
git clone <repo-url>
cd quant_framework
# Install all dependencies
uv syncCreate a .env file in the project root (or export directly):
# .env
FRED_API_KEY=your_api_key_hereEdit configs/persona.yaml to control which functions and connectors your MCP server exposes:
name: "Quant Research Agent"
description: "MCP server exposing quantitative research functions"
host: "127.0.0.1"
port: 8000
functions:
- run_linear
- run_random_forest
- run_svr
- run_xgboost
- run_bayesian_ridge
- run_hmm
connectors:
- fredEdit configs/guardrails.yaml to define validation rules for function outputs:
defaults:
max_records: 10000
rules:
run_linear:
max_records: 5000
required_fields: [model, r_squared, coefficients]
roles:
analyst:
redacted_fields: [model]# Show available commands
uv run quant --help
# Start the MCP server with SSE transport
uv run quant serve --persona configs/persona.yaml
# Use stdio transport instead
uv run quant serve --persona configs/persona.yaml --transport stdioThis will:
- Register all modelling functions from the
FunctionRegistry - Initialise connectors (auto-connects using
$FRED_API_KEY) - Start the MCP server on
127.0.0.1:8000
Add to your claude_desktop_config.json:
{
"mcpServers": {
"quant-framework": {
"url": "http://localhost:8000/sse"
}
}
}uv run python examples/basic_usage.pyThis demonstrates:
- Querying GDP data from FRED
- Running linear regression via the
FunctionRegistry - Validating the result through the
GuardrailEngine
quant_framework/
├── pyproject.toml # Dependencies & CLI entry point
├── configs/
│ ├── persona.yaml # MCP server persona config
│ └── guardrails.yaml # Validation rules
├── examples/
│ └── basic_usage.py # End-to-end demo script
├── experiments/ # Autonomous research loop files
│ ├── evaluate.py # Evaluation harness (scalar metric)
│ ├── prepare_snapshot.py # Data snapshot caching script
│ └── strategy.py # Editable strategy sandbox
├── program.md # Human-directed research agenda
└── quant_framework/ # Package root
├── cli.py # CLI (quant serve)
├── core/
│ ├── function.py # @register_function, FunctionRegistry, FunctionResult
│ └── guardrail.py # GuardrailEngine, GuardrailViolation
├── connectors/
│ ├── connectors.py # BaseConnector, ConnectorRegistry
│ └── fred.py # FREDConnector (with 24h file cache)
├── functions/
│ └── modelling.py # Registered modelling functions
└── mcp/
└── generator.py # MCPServerGenerator
| Connector | Registry Name | Description |
|---|---|---|
FREDConnector |
fred |
Federal Reserve Economic Data with 24h file-based cache |
from quant_framework.connectors import FREDConnector
fred = FREDConnector()
fred.connect({"api_key": "your_key"})
df = fred.query("GDP", observation_start="2020-01-01")All functions are registered with @register_function and return a FunctionResult:
| Function | Registry Name | Model Type | Key Outputs |
|---|---|---|---|
run_linear_regression |
run_linear |
LinearRegression | coefficients, intercept, r² |
run_random_forest |
run_random_forest |
RandomForestRegressor | feature_importances, r² |
run_svr |
run_svr |
SVR | r² |
run_xgboost |
run_xgboost |
XGBRegressor | feature_importances, r² |
run_bayesian_ridge |
run_bayesian_ridge |
BayesianRidge | posterior_std, alpha_, lambda_ |
run_hmm |
run_hmm |
GaussianHMM | hidden_states, transition_matrix, AIC, BIC |
from quant_framework.functions.modelling import run_linear_regression
result = run_linear_regression(df, target="GDP", features=["UNRATE", "FEDFUNDS"])
print(result.output["r_squared"]) # 0.12
print(result.trace_id) # unique trace IDfrom quant_framework.core import GuardrailEngine
engine = GuardrailEngine("configs/guardrails.yaml")
engine.validate("run_linear", result.output) # passes
engine.validate("run_linear", result.output, role="analyst") # applies role-specific rules- Hot-reload: edits to the YAML take effect immediately (checks file mtime)
- Per-role overrides: stricter rules for specific roles
from quant_framework.core import FunctionRegistry
# List all registered functions
FunctionRegistry.list() # ['run_linear', 'run_random_forest', ...]
FunctionRegistry.list_by_category("modelling") # filter by category
# Call by name
result = FunctionRegistry.call("run_linear", df=df, target="GDP")The framework includes a fully autonomous research loop designed to test hypotheses and incrementally improve a quantitative strategy.
It builds on the three-layer contract outlined in program.md:
- Fixed Evaluation Harness (
experiments/evaluate.py): Scores the strategy on a fixed historical dataset. - Strategy Sandbox (
experiments/strategy.py): The single file where the agent tests features, model choices, and signal logic. - Human Direction (
program.md): Defines the agent's constraints and the high-level research agenda.
Provide the program.md file to any autonomous coding agent (like Claude or the built-in system) and instruct it to begin. The agent will read program.md, modify experiments/strategy.py, run evaluate.py, and use a keep/discard ratchet to only commit changes that improve the composite score.
from quant_framework.connectors.connectors import BaseConnector, ConnectorRegistry
@ConnectorRegistry.register("bloomberg")
class BloombergConnector(BaseConnector):
def connect(self, config): ...
def query(self, request, **kwargs): ...
def get_schema(self): ...
def health_check(self): ...from quant_framework.core import register_function, FunctionResult
@register_function(name="my_indicator", category="technical")
def my_indicator(df, window=14):
result = ... # your logic
return FunctionResult(output={"value": result}, metrics={"window": window})The function is automatically available in the FunctionRegistry and can be exposed as an MCP tool by adding its name to your persona YAML.
- Connector-first. Every data source is a
BaseConnector. Learn one interface, connect anything. - Functions as atoms. Decorated Python functions that auto-register and auto-expose via MCP.
- Progressive complexity. Start with FRED. Add what you need, when you need it.
- Three-layer contract. Immutable evaluator (guardrails), agent sandbox (function store), human direction (persona configs).
Arjun Singh
MIT