HKUDS/Vibe-Trading · error · ValueError
each portfolio source must be an object
Error message
each portfolio source must be an object
What it means
Each entry in the portfolio settings 'sources' array must be a JSON object (dict); scalars, strings, arrays, or nulls are rejected during per-entry validation at the given index.
Source
Thrown at agent/src/portfolio/config.py:160
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),
)
else:
connection = store.get(connection_id)
profile = profile_by_id(connection.profile_id)
if profile not in eligible_profiles():
raise ValueError(View on GitHub (pinned to 80ffdda44c)
Solutions
- Wrap every entry as an object: {"connection_id": "...", ...}
- Validate the parsed JSON shape before saving/loading the file
- Remove stray null/undefined entries from the array
Example fix
# before
"sources": ["binance-main"]
# after
"sources": [{"connection_id": "binance-main"}] Defensive patterns
Strategy: type-guard
Validate before calling
payload['sources'] = [s for s in payload['sources'] if isinstance(s, dict)]
if len(payload['sources']) != original_len:
log.warning('dropped malformed source entries') Type guard
def all_sources_are_objects(payload) -> bool:
return all(isinstance(s, dict) for s in payload.get('sources', [])) Try / catch
try:
settings = parse_settings(payload, store)
except ValueError as exc:
if 'must be an object' in str(exc):
payload['sources'] = [normalize_entry(s) for s in payload['sources'] if s]
settings = parse_settings(payload, store) Prevention
- Don't write shorthand id lists; always use entry objects
- Validate generator output with a JSON schema
- Filter null entries produced by list-building code
When it happens
Trigger: "sources": ["conn1", {...}] or [{null}] — e.g. a list of ids instead of objects, or a trailing null from list-building code.
Common situations: Writing shorthand configs (list of ids), LLM/manual generation of the file, or JSON round-trips that flatten entries.
Related errors
- sources must be a list
- agent config 'channels' must be an object
- agent config 'channels.feishu' must be an object
- hypotheses storage must contain a JSON list
- invalid portfolio settings: {exc}
AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28).
Data as JSON: /api/errors/b5b940fc7f1ca498.
Report an issue: GitHub.