virattt/ai-hedge-fund · critical · ValueError
held position {ticker} has no price within {_MARK_LOOKBACK_D
Error message
held position {ticker} has no price within {_MARK_LOOKBACK_DAYS} days of {as_of} — cannot value the book What it means
Raised by _mark_prices (hedge_fund/pipeline/run_cycle.py:163) when a ticker the broker currently HOLDS has no price bar within the last _MARK_LOOKBACK_DAYS (=7) calendar days of as_of. The policy is asymmetric by design: a universe ticker with no recent bar is merely skipped (TickerSkip, 'missing data reads as no signal'), but a held ticker cannot be valued honestly, so the run raises rather than mark the book at zero or stale prices.
Source
Thrown at hedge_fund/pipeline/run_cycle.py:163
held: dict,
data_client: DataClient,
) -> tuple[dict[str, float], list[TickerSkip]]:
"""Last close on or before *as_of* for each ticker, within the lookback.
No bar and not held -> TickerSkip (the caller then never runs analysts
on it). No bar but HELD -> raise: the book cannot be honestly valued.
"""
start = (_date.fromisoformat(as_of) - timedelta(days=_MARK_LOOKBACK_DAYS)).isoformat()
marks: dict[str, float] = {}
skipped: list[TickerSkip] = []
for ticker in tickers:
prices = data_client.get_prices(ticker, start, as_of)
bars = [p for p in prices if p.time[:10] <= as_of]
if bars:
marks[ticker] = max(bars, key=lambda p: p.time).close
elif ticker in held:
raise ValueError(
f"held position {ticker} has no price within "
f"{_MARK_LOOKBACK_DAYS} days of {as_of} — cannot value the book"
)
else:
skipped.append(TickerSkip(
ticker=ticker,
reason=f"no close within {_MARK_LOOKBACK_DAYS} days of {as_of}",
))
return marks, skipped
View on GitHub (pinned to eff8a7320f)
Solutions
- Verify data coverage: check get_prices(ticker, as_of-7d, as_of) actually returns bars; if the cache is truncated for that ticker, refresh/re-fetch it.
- If the position is legitimately stale (halt/delisting), have the cycle logic liquidate or write off the position before the mark step instead of holding it into a valuation.
- Use more liquid tickers in the universe, or accept that halted names abort the run and wrap run_cycle per cycle with recovery logic.
- As a last resort, widen _MARK_LOOKBACK_DAYS — but understand this marks the book at up-to-N-day-old prices, changing valuation honesty.
Example fix
# before
record = run_cycle(fund, as_of, broker, client, universe) # held MEgas halt -> ValueError mid-run
# after
from datetime import date, timedelta
look = (date.fromisoformat(as_of) - timedelta(days=7)).isoformat()
stale = [t for t in broker.positions() if not client.get_prices(t, look, as_of)]
if stale:
# liquidate/flag stale names before the cycle
for t in stale:
broker.close(t) # or record a write-off
record = run_cycle(fund, as_of, broker, client, universe) Defensive patterns
Strategy: validation
Validate before calling
from datetime import date, timedelta
def stale_held_positions(client, broker, as_of: str, lookback_days: int = 7) -> list[str]:
"""Held tickers with no bar in the mark window — the exact raise condition."""
start = (date.fromisoformat(as_of) - timedelta(days=lookback_days)).isoformat()
return [
t for t, _ in broker.positions().items()
if not any(p.time[:10] <= as_of for p in client.get_prices(t, start, as_of))
] Type guard
def position_is_markable(bars: list, as_of: str) -> bool:
return any(b.time[:10] <= as_of for b in bars) Try / catch
try:
record = run_cycle(fund, as_of, broker, client, universe)
except ValueError as e:
if "cannot value the book" in str(e):
# deliberate policy: either liquidate the stale name and restart the
# cycle, or abort the backtest. Do NOT mark it at zero silently.
raise SystemExit(f"unmarkable position: {e}") from e
raise Prevention
- Before each cycle, scan held positions for a bar within _MARK_LOOKBACK_DAYS (7) and act (liquidate/flag) instead of letting the mark step raise.
- Ensure cached price data covers every held ticker through end of the backtest window.
- Prefer liquid tickers; know that any halt/delisting mid-run will abort the run by design.
- Treat widening _MARK_LOOKBACK_DAYS as a valuation-policy change, not a quick fix.
When it happens
Trigger: A held ticker is delisted, halted, or thinly traded (no close in the 7-day window ending at as_of): e.g. an earnings halt, a suspension, or a backtest grid date that ran past the ticker's last trade. Also possible: the cached price data simply ends before end of the backtest window. Only fires when ticker in held — universe-only names never trigger it.
Common situations: Backtests that hold small-caps through halts; a delisting mid-window (data stops); using as_of dates beyond the last cached bar; data provider gaps of >7 days for OTC names.
Related errors
AI-assisted analysis of virattt/ai-hedge-fund@eff8a7320f (2026-08-15).
Data as JSON: /api/errors/16500965454b1edc.
Report an issue: GitHub.