Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016DvdRZkyCy9NJpHYDdPqmS
44 lines
1.7 KiB
Python
44 lines
1.7 KiB
Python
"""Time budgeting for the nightly run.
|
|
|
|
Each agent gets a wall-clock deadline = its fair share of the remaining window, capped by
|
|
the per-agent config, and never past the hard stop. If an agent finishes early the freed
|
|
time rolls over to those remaining (bounded by the cap), and the hard stop always holds.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime, timedelta
|
|
from zoneinfo import ZoneInfo
|
|
|
|
|
|
def local_hard_stop_ts(hard_stop_hhmm: str, local_tz: str) -> float:
|
|
"""Epoch seconds of the next HH:MM in local time (today if still ahead, else tomorrow)."""
|
|
tz = ZoneInfo(local_tz)
|
|
now = datetime.now(tz)
|
|
hh, mm = (int(x) for x in hard_stop_hhmm.split(":"))
|
|
target = now.replace(hour=hh, minute=mm, second=0, microsecond=0)
|
|
if target <= now:
|
|
target += timedelta(days=1)
|
|
return target.timestamp()
|
|
|
|
|
|
def next_deadline(
|
|
now_ts: float, hard_stop_ts: float, agents_remaining: int, per_agent_cap_s: float
|
|
) -> float:
|
|
"""Deadline for the current agent: min(fair share, cap), never past hard stop."""
|
|
if agents_remaining <= 0 or now_ts >= hard_stop_ts:
|
|
return hard_stop_ts
|
|
fair_share = (hard_stop_ts - now_ts) / agents_remaining
|
|
span = min(fair_share, per_agent_cap_s) if per_agent_cap_s else fair_share
|
|
return now_ts + max(span, 60.0)
|
|
|
|
|
|
def next_market_trade_date(market_tz: str) -> str:
|
|
"""Best-effort next trading date (ET), rolling weekends to Monday. Holidays handled in
|
|
Phase 3 via Alpaca's calendar; this is only for naming the nightly output folder."""
|
|
tz = ZoneInfo(market_tz)
|
|
d = datetime.now(tz).date()
|
|
while d.weekday() >= 5: # 5 = Sat, 6 = Sun
|
|
d += timedelta(days=1)
|
|
return d.isoformat()
|