From 6d72732fb8593a86eb0ff887b748049480b59db6 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 04:16:38 +0000 Subject: [PATCH 1/2] adr-probe: one-off diagnosis of the ADR book's home-market data Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NsPrq53XXinQsUZsWqeKht --- .github/workflows/adr-probe.yml | 45 +++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/workflows/adr-probe.yml diff --git a/.github/workflows/adr-probe.yml b/.github/workflows/adr-probe.yml new file mode 100644 index 00000000..1458e208 --- /dev/null +++ b/.github/workflows/adr-probe.yml @@ -0,0 +1,45 @@ +name: adr-probe + +# One-off diagnosis: what the ADR book's home-market data looks like (why it found no home sessions). + +on: + push: + branches: ["claude/**"] + paths: [".github/workflows/adr-probe.yml"] + workflow_dispatch: + +permissions: + contents: read + +jobs: + probe: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: pip + - run: pip install -r requirements.txt + - name: Probe + run: | + python - <<'PY' + import pandas as pd + from qtrade.intraday.adr_book import ADRS, home_moves + from qtrade.data.loader import download_prices + pd.set_option("display.width", 250); pd.set_option("display.max_columns", 60) + homes = sorted({h for h, _, _ in ADRS.values()}) + fxs = sorted({f.removeprefix("1/") for _, f, _ in ADRS.values()}) + md = download_prices(homes + fxs, start="2026-10-20") + print("index:", md.close.index[-6:].tolist(), "tz:", md.close.index.tz) + print(md.close.iloc[-5:, :12].round(2)) + print("last valid date per ticker:") + print(md.close.apply(lambda s: s.last_valid_index()).value_counts()) + for d in ["2026-10-30", "2026-11-02", "2026-11-03"]: + m = home_moves(pd.Timestamp(d)) + print(d, "->", len(m), "ADRs with a home session;", m.groupby("market").size().to_dict() if len(m) else {}) + import yfinance as yf + raw = yf.download(["0700.HK", "7203.T", "INFY.NS"], start="2026-10-28", auto_adjust=False, progress=False) + print(raw["Close"]); print("raw index:", raw.index) + PY From 10af3027a88328feb342241d331bc8abf7c42aad Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 04:19:02 +0000 Subject: [PATCH 2/2] ADR book trades every day: find today's home bar even when Yahoo lists the next session home_moves kept a home market only if its *latest* Yahoo row was today. The auction sleeve books after the US close, when Sydney, Tokyo and Seoul are about to open and Yahoo already lists their next session. Every market looked as if it hadn't traded today, so the ADR book was empty on 2026-11-02. A probe run on 2026-11-03 confirmed it: Nov 2 finds only Japan, which is closed on Nov 3, and Oct 30 finds nothing. The series is now cut at the day being booked before checking for its bar. Regression test added; the one-off probe workflow is removed. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NsPrq53XXinQsUZsWqeKht --- .github/workflows/adr-probe.yml | 45 --------------------------------- qtrade/intraday/adr_book.py | 3 +++ tests/test_adr_book.py | 14 ++++++++++ 3 files changed, 17 insertions(+), 45 deletions(-) delete mode 100644 .github/workflows/adr-probe.yml diff --git a/.github/workflows/adr-probe.yml b/.github/workflows/adr-probe.yml deleted file mode 100644 index 1458e208..00000000 --- a/.github/workflows/adr-probe.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: adr-probe - -# One-off diagnosis: what the ADR book's home-market data looks like (why it found no home sessions). - -on: - push: - branches: ["claude/**"] - paths: [".github/workflows/adr-probe.yml"] - workflow_dispatch: - -permissions: - contents: read - -jobs: - probe: - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: "3.11" - cache: pip - - run: pip install -r requirements.txt - - name: Probe - run: | - python - <<'PY' - import pandas as pd - from qtrade.intraday.adr_book import ADRS, home_moves - from qtrade.data.loader import download_prices - pd.set_option("display.width", 250); pd.set_option("display.max_columns", 60) - homes = sorted({h for h, _, _ in ADRS.values()}) - fxs = sorted({f.removeprefix("1/") for _, f, _ in ADRS.values()}) - md = download_prices(homes + fxs, start="2026-10-20") - print("index:", md.close.index[-6:].tolist(), "tz:", md.close.index.tz) - print(md.close.iloc[-5:, :12].round(2)) - print("last valid date per ticker:") - print(md.close.apply(lambda s: s.last_valid_index()).value_counts()) - for d in ["2026-10-30", "2026-11-02", "2026-11-03"]: - m = home_moves(pd.Timestamp(d)) - print(d, "->", len(m), "ADRs with a home session;", m.groupby("market").size().to_dict() if len(m) else {}) - import yfinance as yf - raw = yf.download(["0700.HK", "7203.T", "INFY.NS"], start="2026-10-28", auto_adjust=False, progress=False) - print(raw["Close"]); print("raw index:", raw.index) - PY diff --git a/qtrade/intraday/adr_book.py b/qtrade/intraday/adr_book.py index 6a54e31d..4a21e637 100644 --- a/qtrade/intraday/adr_book.py +++ b/qtrade/intraday/adr_book.py @@ -115,6 +115,9 @@ def home_moves(today: pd.Timestamp, tickers: list[str] | None = None) -> pd.Data if home not in md.close: continue c = md.close[home].dropna() + # after the US close Yahoo already lists the next Asian session (Sydney, Tokyo and Seoul open + # around then), so look for today's bar among the rows up to today, not at the end + c = c[c.index.normalize() <= day] if len(c) < 2 or c.index[-1].normalize() != day: continue # the home market didn't trade today fxt = fx.removeprefix("1/") diff --git a/tests/test_adr_book.py b/tests/test_adr_book.py index cdfc412d..39141546 100644 --- a/tests/test_adr_book.py +++ b/tests/test_adr_book.py @@ -39,6 +39,20 @@ def test_home_moves_needs_todays_home_session(monkeypatch): assert np.isclose(m["fx_ret_prev"].iloc[0], fx.iloc[-2]) # yesterday's FX, not today's +def test_home_moves_ignores_the_next_sessions_row(monkeypatch): + """After the US close Yahoo already lists the next Asian session; today's bar must still be found + (on 2026-11-02 its absence left the ADR book empty).""" + from qtrade.data import loader + + days = pd.bdate_range("2026-10-26", "2026-11-03") # 11-03 = tomorrow's session already listed + close = pd.DataFrame({"9988.HK": np.linspace(100, 107, len(days)), "HKD=X": 7.8}, index=days) + md = MarketData(open=close, high=close, low=close, close=close, volume=close * 0 + 1) + monkeypatch.setattr(loader, "download_prices", lambda t, start=None: md) + m = B.home_moves(pd.Timestamp("2026-11-02"), ["BABA"]) + assert list(m["adr"]) == ["BABA"] + assert np.isclose(m["home_ret"].iloc[0], np.log(close.loc["2026-11-02", "9988.HK"] / close.loc["2026-10-30", "9988.HK"])) + + def test_build_targets_end_to_end(): acfg = Config().auction moves = pd.DataFrame([{"adr": "BABA", "market": "China", "home_ret": 0.04, "fx_ret_prev": 0.0},