diff --git a/.github/workflows/auction-sheet.yml b/.github/workflows/auction-sheet.yml index 9fd52236..0aee9bdd 100644 --- a/.github/workflows/auction-sheet.yml +++ b/.github/workflows/auction-sheet.yml @@ -5,7 +5,8 @@ name: auction-sheet # closed at today's official open, today's ADR book is settled open -> close, and tonight's # book is opened at today's official close - all in the sheet's "Auction book" and # "Auction trades" tabs. Nothing is sent to Alpaca. The two crons cover ~16:25 New York in -# summer and winter time; the script itself only runs between 16:20 and midnight New York, once a day. +# summer and winter time; the script itself only runs between 16:50 and midnight New York (Yahoo's Asian bars, needed by +# the ADR book, are missing around 16:30-16:45), once a day. on: schedule: @@ -14,7 +15,7 @@ on: workflow_dispatch: inputs: force: - description: "run now even outside 16:20-24:00 New York" + description: "run now even outside 16:50-24:00 New York" default: "false" permissions: @@ -27,7 +28,7 @@ concurrency: jobs: sleeve: runs-on: ubuntu-latest - timeout-minutes: 40 + timeout-minutes: 75 env: ALPACA_API_KEY: ${{ secrets.ALPACA_API_KEY }} # market data only ALPACA_SECRET_KEY: ${{ secrets.ALPACA_SECRET_KEY }} diff --git a/qtrade/clock.py b/qtrade/clock.py index 3b39d273..edb49b88 100644 --- a/qtrade/clock.py +++ b/qtrade/clock.py @@ -36,7 +36,7 @@ class Job: window_min=20), Job("hft-shadow.yml", NY, ("09:26",), window_min=300), # its own chain then runs the session Job("flip-sleeve.yml", NY, ("15:45",), window_min=400), - Job("auction-sheet.yml", NY, ("16:30",), window_min=400), + Job("auction-sheet.yml", NY, ("16:55",), window_min=400), # after Yahoo's ~16:30-16:45 refresh of Asian bars Job("hoard-archive.yml", NY, ("16:45",), inputs=(("job", "eod"),), window_min=400), Job("foreign-sheet.yml", "UTC", ("17:15", "23:30"), window_min=200), # 23:30: Asian bars Yahoo posted late Job("hoard-archive.yml", "UTC", ("07:15",), weekdays=False, inputs=(("job", "archive"),), window_min=300), diff --git a/qtrade/intraday/adr_book.py b/qtrade/intraday/adr_book.py index 4a21e637..d3bc35c2 100644 --- a/qtrade/intraday/adr_book.py +++ b/qtrade/intraday/adr_book.py @@ -125,6 +125,11 @@ def home_moves(today: pd.Timestamp, tickers: list[str] | None = None) -> pd.Data f = f[f.index.normalize() < day] # FX up to yesterday only rows.append({"adr": t, "market": mkt, "home_ret": float(np.log(c.iloc[-1] / c.iloc[-2])), "fx_ret_prev": float(f.iloc[-1]) if len(f) else np.nan}) + if not rows: # say why, so an empty ADR book is never a mystery + sample = [h for h in homes if h in md.close][:4] + LOG.warning("ADR book: no home session dated %s; index ends %s; last bars %s", day.date(), + [str(d.date()) for d in md.close.index[-3:]], + {h: [str(d.date()) for d in md.close[h].dropna().index[-2:]] for h in sample}) return pd.DataFrame(rows) diff --git a/qtrade/intraday/auction_sheet.py b/qtrade/intraday/auction_sheet.py index bcee6c0b..ba111788 100644 --- a/qtrade/intraday/auction_sheet.py +++ b/qtrade/intraday/auction_sheet.py @@ -32,7 +32,7 @@ NY = "America/New_York" TAB, TRADES_TAB = "Auction book", "Auction trades" -ADR_TRIES, ADR_RETRY_S = 4, 120 +ADR_TRIES, ADR_RETRY_S = 10, 180 # Yahoo's Asian bars vanished ~16:30-16:40 NY (Nov 3-4), back by 16:50 LOG_HEADER = ["Date", "Overnight P&L $", "ADR P&L $", "Day P&L $", "Day %", "Equity $", "Overnight longs", "Overnight shorts", "Overnight gross $", "ADR names", "Capital $"] HOLD_HEADER = ["Symbol", "Shares", "Entry (official close)", "Position $"] @@ -106,7 +106,7 @@ def day_inputs(cfg, tickers: list[str], signs: dict, day: pd.Timestamp) -> dict: try: moves = pd.DataFrame() for attempt in range(ADR_TRIES): # Yahoo sometimes serves the Asian daily bars late - moves = home_moves(day, adr_names) # (2026-11-03: none at 21:31 UTC, 24 at 21:50) + moves = home_moves(day, adr_names) # (none at 16:31-16:38 NY on Nov 3-4, all back by 16:50) LOG.info("ADR book: %d ADRs with a home session today (try %d)", len(moves), attempt + 1) if len(moves) or day.weekday() >= 5: break @@ -252,11 +252,11 @@ def main(argv=None): ap.add_argument("--tickers", default="data/auction_tickers.json") ap.add_argument("--signs", default="data/auction_signs.json") ap.add_argument("--dry-run", action="store_true") - ap.add_argument("--force", action="store_true", help="run outside 16:20-24:00 New York") + ap.add_argument("--force", action="store_true", help="run outside 16:50-24:00 New York") args = ap.parse_args(argv) setup_logging() now = pd.Timestamp.now(tz=NY) - if not args.force and not (now.weekday() < 5 and 16 * 60 + 20 <= now.hour * 60 + now.minute < 24 * 60): + if not args.force and not (now.weekday() < 5 and 16 * 60 + 50 <= now.hour * 60 + now.minute < 24 * 60): print({"skipped": f"outside the window ({now:%H:%M} NY)"}) return sleeve = book_split().get("auction", 0.0)