Documentation Index
Fetch the complete documentation index at: https://mathematicalcompany.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
The FundCluster orchestrates multiple FundManager instances as a single unit. Each fund runs independently with its own strategies, capital, and oversight loop. The cluster provides aggregate views and cross-fund rebalancing.
Quick Start
from horizon.fund import FundManager, FundConfig, FundCluster
# Create individual funds
crypto_fund = FundManager(FundConfig(
total_capital=50_000,
max_fund_drawdown_pct=20.0,
))
politics_fund = FundManager(FundConfig(
total_capital=30_000,
max_fund_drawdown_pct=10.0,
))
sports_fund = FundManager(FundConfig(
total_capital=20_000,
max_fund_drawdown_pct=15.0,
))
# Create cluster
cluster = FundCluster()
cluster.add_fund("crypto", crypto_fund)
cluster.add_fund("politics", politics_fund)
cluster.add_fund("sports", sports_fund)
# Start all funds
cluster.start_all()
# Aggregate status
status = cluster.aggregate_status()
# {
# "total_nav": 100000.0,
# "fund_count": 3,
# "funds": {
# "crypto": {"nav": 50000.0, "drawdown_pct": 0.0, ...},
# "politics": {"nav": 30000.0, "drawdown_pct": 0.0, ...},
# "sports": {"nav": 20000.0, "drawdown_pct": 0.0, ...},
# }
# }
# Stop all
cluster.stop_all()
Aggregate Views
Status
aggregate_status() combines NAV across all funds and provides a per-fund breakdown:
status = cluster.aggregate_status()
print(f"Total NAV: ${status['total_nav']:,.2f}")
print(f"Funds: {status['fund_count']}")
for name, fund_status in status["funds"].items():
print(f" {name}: NAV ${fund_status['nav']:,.2f}, DD {fund_status['drawdown_pct']:.1f}%")
Risk
aggregate_risk() computes weighted average drawdown and identifies the riskiest fund:
risk = cluster.aggregate_risk()
# {
# "weighted_avg_drawdown_pct": 3.2,
# "max_single_fund_drawdown_pct": 8.5,
# "max_drawdown_fund": "crypto",
# "fund_count": 3,
# }
Weights are proportional to each fund’s NAV. A fund with 50% of total NAV contributes 50% to the weighted average drawdown.
Cross-Fund Rebalancing
Move capital between funds to maintain target allocations:
# Define target weights (must sum to ~1.0)
targets = cluster.rebalance_across_funds({
"crypto": 0.5,
"politics": 0.3,
"sports": 0.2,
})
# {
# "crypto": {"current": 48000.0, "target": 50000.0, "delta": 2000.0},
# "politics": {"current": 32000.0, "target": 30000.0, "delta": -2000.0},
# "sports": {"current": 20000.0, "target": 20000.0, "delta": 0.0},
# }
The rebalance method computes target allocations and deltas but does not move capital automatically. This is intentional: capital movement across funds is a high-impact action that should be reviewed before execution.
Weight validation:
- Weights must sum to between 0.99 and 1.01
- All referenced fund names must exist in the cluster
- ValueError is raised on validation failure
Fund Access
# Access individual funds by name
crypto = cluster.fund("crypto")
crypto_status = crypto.status()
# List all fund names
names = cluster.fund_names
# ["crypto", "politics", "sports"]
Lifecycle
# Add a fund
cluster.add_fund("new_fund", new_fund_manager)
# Remove a fund (does not stop it)
cluster.remove_fund("sports")
# Start all funds
cluster.start_all()
# Stop all funds
cluster.stop_all()
Duplicate fund names raise ValueError. Removing a non-existent fund also raises ValueError.