HKUDS/Vibe-Trading · warning · ValueError
at most 50 portfolio sources are allowed
Error message
at most 50 portfolio sources are allowed
What it means
Portfolio settings enforce a hard cap of 50 sources per configuration; len(raw_sources) > 50 raises immediately. The limit bounds fan-out of read-only portfolio aggregation per settings file.
Source
Thrown at agent/src/portfolio/config.py:154
when omitted.
Returns:
Validated, order-normalized settings.
Raises:
ValueError: If the display currency, the source list, a connection id,
a label, or a referenced profile's eligibility is invalid.
"""
store = connection_store or ConnectionStore()
currency = str(payload.get("display_currency") or "USD").strip().upper()
if currency not in {"USD", "CNY"}:
raise ValueError("display_currency must be USD or CNY")
raw_sources = payload.get("sources")
if not isinstance(raw_sources, list):
raise ValueError("sources must be a list")
if len(raw_sources) > 50:
raise ValueError("at most 50 portfolio sources are allowed")
seen_ids: set[str] = set()
sources: list[PortfolioSource] = []
for index, raw in enumerate(raw_sources):
if not isinstance(raw, dict):
raise ValueError("each portfolio source must be an object")
connection_id = (
str(raw.get("connection_id") or raw.get("id") or "").strip().lower()
)
if not _SOURCE_ID_RE.fullmatch(connection_id):
raise ValueError(f"invalid portfolio connection id: {connection_id or '?'}")
legacy_profile_id = str(raw.get("profile_id") or "").strip().lower()
if legacy_profile_id:
legacy_profile = profile_by_id(legacy_profile_id)
connection = store.ensure(
connection_id,
legacy_profile.id,
str(raw.get("label") or legacy_profile.label),View on GitHub (pinned to 80ffdda44c)
Solutions
- Prune sources to the 50 you actually need (deduplicate by connection_id first)
- Split aggregation across multiple settings files/profiles if genuinely required
- Audit for stale entries from previous connections before adding new ones
Defensive patterns
Strategy: validation
Validate before calling
seen = set()
deduped = [s for s in payload['sources']
if (id_ := (s.get('connection_id') or s.get('id'))) not in seen and not seen.add(id_)]
payload['sources'] = deduped[:50]
if len(deduped) > 50:
log.warning('truncated sources to 50 (dropped %d)', len(deduped) - 50) Type guard
def within_source_limit(payload) -> bool:
return isinstance(payload.get('sources'), list) and len(payload['sources']) <= 50 Try / catch
try:
settings = parse_settings(payload, store)
except ValueError as exc:
if 'at most 50' in str(exc):
payload['sources'] = dedupe_and_cap(payload['sources'], 50)
settings = parse_settings(payload, store) Prevention
- Deduplicate merged configs by connection_id before saving
- Split large account sets across multiple settings files
- Audit for stale connections before adding new ones
When it happens
Trigger: A settings file with 51+ entries in sources, typically produced by merging multiple configs or programmatically enumerating every exchange connection.
Common situations: Concatenating settings files, scripts that add a source per account/subaccount, or copying a bulk test fixture into production config.
Related errors
- max_bytes must be positive, got {max_bytes}
- display_currency must be USD or CNY
- sources must be a list
- each portfolio source must be an object
- invalid portfolio connection id: {connection_id or '?'}
AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28).
Data as JSON: /api/errors/0e7746b1ef60a3a8.
Report an issue: GitHub.