{"record":{"id":"b4f184ca824247c7","repo":"HKUDS/Vibe-Trading","slug":"characteristics-frame-is-empty","errorCode":null,"errorMessage":"characteristics frame is empty","messagePattern":"characteristics frame is empty","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agent/src/quantlib/factormodel.py","lineNumber":339,"sourceCode":"            reported through the returned fill counts.\n        market_caps: Market capitalisation by asset, for the cap-weighted mean.\n        definitions: Factor construction map; defaults to\n            :data:`STYLE_FACTOR_DEFINITIONS`.\n        winsorise: Fraction trimmed from each tail before standardising.\n\n    Returns:\n        Tuple of ``(exposures, filled)``. ``exposures`` has one row per asset\n        and one column per constructible factor. ``filled`` maps each factor to\n        the number of assets whose exposure was imputed as zero because the\n        underlying characteristic was missing -- a factor with most of its cells\n        filled is not a measurement and the caller must be able to see that.\n\n    Raises:\n        ValueError: If ``characteristics`` is empty, or if no factor at all can\n            be built from the columns supplied.\n    \"\"\"\n    if characteristics.empty:\n        raise ValueError(\"characteristics frame is empty\")\n\n    columns: dict[str, pd.Series] = {}\n    filled: dict[str, int] = {}\n\n    for factor, recipe in definitions.items():\n        available = {c: s for c, s in recipe.items() if c in characteristics.columns}\n        if not available:\n            continue\n\n        parts = []\n        for characteristic, sign in available.items():\n            raw = characteristics[characteristic]\n            try:\n                standardised = standardise_exposures(\n                    raw, market_caps=market_caps, winsorise=winsorise\n                )\n            except ValueError:\n                # A single unusable characteristic must not take the whole factor","sourceCodeStart":321,"sourceCodeEnd":357,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/quantlib/factormodel.py#L321-L357","documentation":"build_style_exposures iterates factor definitions over a characteristics DataFrame; an entirely empty frame (no rows or no columns) means no factor can be constructed, so it fails fast rather than returning an empty exposure matrix that would silently break downstream regressions.","triggerScenarios":"Passing characteristics=pd.DataFrame() or a frame whose filtering left zero rows/columns — e.g. an empty CSV read, a date slice outside the data, or a screen that removed everything.","commonSituations":"Backtest loops reaching an out-of-range date, empty point-in-time snapshots, upstream joins producing empty results, or unit tests with placeholder fixtures.","solutions":["Check characteristics.shape before calling; log and skip the date when empty.","Fix the upstream data load/selection so the frame actually contains the characteristic columns and rows for the requested date."],"exampleFix":"# before\nexposures, filled = build_style_exposures(characteristics, definitions)\n# after\nif characteristics.empty:\n    exposures, filled = pd.DataFrame(), {}\nelse:\n    exposures, filled = build_style_exposures(characteristics, definitions)","handlingStrategy":"validation","validationCode":"assert not characteristics.empty","typeGuard":"def is_usable_frame(df) -> bool:\n    return df is not None and not df.empty and df.shape[1] > 0","tryCatchPattern":null,"preventionTips":["Check shape after date slicing in backtest loops.","Fail loudly on empty CSV/DB reads upstream."],"tags":["factormodel","empty-input","dataframe"],"backgroundTag":"empty-collection-argument","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}