This guide explains how to keep the system fast for large portfolios (50+ assets) and slow environments.
Measured with python scripts/benchmark.py on a 4-asset portfolio (v10.6.5):
| Benchmark | Symbols | Elapsed | Peak memory |
|---|---|---|---|
| Batch query | 4 | 0.091 s | 1.45 MB |
| Batch scoring | 4 | 0.116 s | 1.43 MB |
| Lazy loading (5J50.DE, 345 rows) | 1 | 0.042 s | 0.39 MB |
The synthetic 100-asset tests in tests/test_performance.py assert batch
scoring completes in under 60 seconds and peak memory stays under 500 MB.
For portfolios with 50+ assets, use parallel batch scoring:
from quant.analytics.scoring import batch_score_assets_parallel
results = batch_score_assets_parallel(
symbols=symbols,
price_data=prices,
fundamentals=fundamentals,
news=news,
tiers=tiers,
max_workers=4,
)Recommendations:
- Under 20 assets: the thread path is used automatically (lower overhead).
- 20 to 50 assets:
max_workers=4. - 50+ assets:
max_workers=8or the CPU core count.
batch_score_assets_optimized(symbols, tiers) loads all market history in a
single query, then scores in parallel.
Instead of N per-symbol queries, use one batch query:
from quant.data.database import batch_query_portfolio_data
data = batch_query_portfolio_data(symbols)
prices = data["prices"]For very long histories, stream rows in chunks:
from quant.data.database import load_prices_lazy
for chunk in load_prices_lazy("AAPL", chunk_size=5000):
process(chunk)Cache expensive, JSON-serializable calculations on disk:
from quant.analytics.cache import disk_cache
@disk_cache(max_age_days=1)
def expensive_calculation(x: int) -> int:
return x * xCache management is internal; quant doctor reports the cache location and
freshness. The hidden quant cache-stats / quant clear-cache commands remain
for maintenance.
quant.utils.retry.retry_with_backoffretries transient external failures with exponential backoff.quant.analytics.scoring.score_asset_with_fallbacksdegrades gracefully when data is missing, markingdata_qualityas PARTIAL or FALLBACK.quant.portfolio.tier_manager.load_tiers_safenever crashes on a missing, empty, or corrupted tiers file.
Use the read-only diagnosis to spot performance issues:
quant doctorLook for WARNING on "Data freshness" (slow updates) or "Signal cache" (slow scoring).
Run the performance suite:
pytest tests/test_performance.py -vIt reports batch scoring time for 50 and 100 assets, batch query time, and memory usage.
The old "+2.5 percent since 13 Sep" was a naive value change and lied whenever
money entered. From v10.7.0 the Overview return uses Modified Dietz over the
period between two dates, using the flows table:
R = (V_end - V_start - F) / (V_start + sum(w_i * F_i))
w_i = (days_in_period - days_from_start_to_flow) / days_in_period
Deposits do not count as profit. Flow signs: buy = +amount (money entered
invested), sell = -amount (money left invested), dividend = -amount (left
invested, landed in cash). The Overview sentence reads: "Since 13 Sep: +20.84
EUR. Of that: you added 0.00 EUR, market moved +20.84 EUR. Return: +2.4
percent."
Per-asset performance is simply the price change over the period, labeled
"price change" (honest for a held asset). The value series the chart reads is
portfolio_value_history(date, invested_eur), written by the daily job from
shares times close; it never includes cash.