research: auction audit of the overnight sleeve's prices (PASS: 0.2 bp a side)
The overnight sleeve buys at the closing cross and sells at the opening cross; the backtest used Yahoo open/close and 1 bp a side. On 800 random nights in 2016-26 (7,856 name-nights), the backtest night return exceeds the cross-to-cross return by +0.12 bp (SE 0.24, median 0): Yahoo's prices are the crosses. With the SEC fee, cost is +0.20 bp a side, so 1 bp was conservative. At the Reg T cap this lifts three_sleeve_overnight to 39%/yr (MaxDD -27%) and the 12_1 variant to 44%. Live fills at the cross remain unverified (paper does not run auctions). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -113,6 +113,7 @@ PYTHONPATH=. uv run --with numba python -m research.lab.svr_combo # low_svr2
|
||||
PYTHONPATH=. uv run python -m research.lab.fetch_options snapshot|status # daily S&P 500 option chains after the close: indicative quotes (forward-only from 2026-10-12) + OPRA daily bars + print-vs-quote quality sample; data/options/us/; timer quant-trading-options
|
||||
PYTHONPATH=. uv run python -m research.lab.fetch_options backfill # OPRA daily option bars since 2024-02 for every S&P 500 member since then (resumable, ~2-3 h; pauses 16:10-16:55 NY); then builds volume_panel.csv.gz
|
||||
PYTHONPATH=. uv run python -m research.lab.fetch_options volume-panel # date x underlying option volume (all 0-100 DTE strikes and the 5-100 DTE / 0.7-1.3 band), os_volume, pc_volume
|
||||
PYTHONPATH=. uv run python -m research.lab.auction_audit [fetch] # overnight sleeve: Yahoo open/close vs the primary exchange's opening/closing cross prints (Alpaca SIP trades), 800 nights 2016-26 (preregistered; PASS 2026-10-12: +0.20 bp a side incl. SEC fee, the 1 bp backtest cost is conservative)
|
||||
PYTHONPATH=. uv run python -m research.lab.option_overlay [fetch] # leverage routes (Reg T margin, box, MES futures, calls) and S&P 500 puts (Cboe PPUT3M/PPUT held to expiry) for $10k-$100k accounts (preregistered; 2026-10-12: no route reaches 50% under Reg T, cap L 1.71 -> 36%/yr; puts B FAIL +1.0pp)
|
||||
PYTHONPATH=. uv run --with numba python -m research.lab.option_volume # Johnson-So O/S and put/call volume on OPRA bars 2024-03..2026-09 (preregistered; STOP 2026-10-12: O/S sign reversed, t -1.6, corr -0.65 with momentum; put/call t 0.4)
|
||||
uv run python -m research.lab.fetch_us_fundamentals --universe mid # EDGAR PIT facts for S&P 400 ever-members (name-checked CIKs)
|
||||
|
||||
336
research/lab/auction_audit.py
Normal file
336
research/lab/auction_audit.py
Normal file
@@ -0,0 +1,336 @@
|
||||
"""
|
||||
Do the overnight sleeve's backtest prices match the auctions it would trade
|
||||
in? The sleeve buys market-on-close and sells market-on-open, so a real
|
||||
night earns the primary exchange's closing cross to its next opening cross.
|
||||
The backtest (research/lab/overnight_only.py) earns Yahoo open_t / close_t-1
|
||||
and charges 1 bp a side. This measures the gap.
|
||||
|
||||
PYTHONPATH=. uv run python -m research.lab.auction_audit fetch # sample, SIP trades, daily bars (resumable)
|
||||
PYTHONPATH=. uv run python -m research.lab.auction_audit # compare (refuses to overwrite results)
|
||||
|
||||
Sample: 800 nights drawn at random (seed 20261012) from 2016-01-05..2026-09-24
|
||||
(Alpaca SIP history starts 2016). On each night it takes every name the sleeve
|
||||
holds (sleeve_score, the live score, top 10 by score at close t-2) plus the
|
||||
overnight_12_1 names (reported).
|
||||
|
||||
Auction prints (Alpaca SIP trades, feed=sip): the cross is the largest-size
|
||||
print with condition 'O' (open) or '6' (close). That is the primary listing
|
||||
exchange's cross: Nasdaq x=Q (x=T in older data), NYSE x=N.
|
||||
close d-1, 15:59:59-16:10:00 New York; NYSE close prints can arrive after 16:03
|
||||
open d, 09:29:59-09:30:03; names still missing 09:30:03-09:31:00, then
|
||||
09:31:00-09:40:00 (NYSE opens can come seconds to minutes late)
|
||||
Adjustment: F = close(adjustment=all) / close(raw) from Alpaca daily bars, so
|
||||
real night R_real = open_x(d) F(d) / (close_x(d-1) F(d-1)) - 1
|
||||
backtest R_bt = Yahoo open(d) / Yahoo close(d-1) - 1 (research panel)
|
||||
shortfall R_bt - R_real, per name-night; the night mean is the unit of
|
||||
observation (SE over nights)
|
||||
split R_bt - R_bar (Yahoo vs Alpaca daily-bar open/close) and
|
||||
R_bar - R_real (bar open/close vs the crosses)
|
||||
Fees on top: SEC Section 31 fee about 0.28 bp on sells (0.14 bp a side on
|
||||
average); FINRA TAF is negligible. Commissions are $0 at Alpaca.
|
||||
|
||||
Rule (fixed before any comparison was computed):
|
||||
cost per side = night-mean shortfall / 2 + 0.14 bp, for sleeve_score names
|
||||
<= 1.0 bp PASS: the backtest's 1 bp a side holds; the Reg T-cap ranges stand
|
||||
(research/lab/option_overlay.py: about 36% three_sleeve_overnight, 41% 12_1)
|
||||
1.0-2.5 bp plan with the measured cost (about 2 bp: 32% / 37% at the cap)
|
||||
> 2.5 bp the overnight sleeve's edge over the all-day sleeve is gone
|
||||
(overnight_book: 4 bp a side is the baseline's Sharpe); no live overnight sleeve
|
||||
Reported: by year, 2016-20 vs 2021-26, Nasdaq vs NYSE listing, overnight_12_1
|
||||
names, trimmed mean, the share of name-nights without a cross print.
|
||||
|
||||
Not measured: whether orders reach the auction (Alpaca's MOC cutoff 15:50,
|
||||
MOO before 09:28), impact at larger sizes, partial fills (a market order in
|
||||
a cross fills unless the stock is halted).
|
||||
|
||||
Result (2026-10-12): PASS. The cost per side is +0.20 bp for the
|
||||
sleeve_score names and +0.28 bp for overnight_12_1, including the SEC fee.
|
||||
|
||||
sleeve_score: 7,856 name-nights over 795 nights, 2016-01..2026-09
|
||||
R_bt - R_real +0.12 bp/night (SE 0.24), median 0.00, trimmed +0.23
|
||||
Yahoo vs bar -0.02 (SE 0.09)
|
||||
bar vs cross +0.14 (SE 0.24): the closing cross equals the bar close
|
||||
(0.00); the bar open vs the opening cross is +0.16
|
||||
by year within +-1 bp except 2020, -1.6 (SE 0.8);
|
||||
2016-20 -0.24, 2021-26 +0.48
|
||||
by listing NYSE +0.18, Nasdaq -0.02
|
||||
coverage 122 / 74 of 8,000 name-nights had no close / open
|
||||
cross print (halts, symbol changes); 2 outliers over
|
||||
5% were dropped
|
||||
|
||||
- Yahoo's open and close are the primary exchange's crosses (median
|
||||
difference 0). The backtest's prices are the prices a MOC/MOO order
|
||||
gets, so the 1 bp a side the books assumed was conservative. About
|
||||
0.2 bp a side remains, which is the SEC fee.
|
||||
- Recomputed after the run at 0.2 bp a side, 2006-10..2026-09 with
|
||||
margin at T-bill + 1.5% (CAGR / MaxDD):
|
||||
|
||||
book 1x Reg T cap
|
||||
three_sleeve_overnight 23.9% 39.2% at 1.7x (-26.7%)
|
||||
overnight_12_1 26.6% 44.3% at 1.7x (-26.2%)
|
||||
two_sleeve_overnight_trend 32.0% 50.6% at 1.6x (-28.7%)
|
||||
|
||||
two_sleeve_overnight_trend makes 64% a year in 2017-26 and 39% in
|
||||
2006-16. It leans on TrendRider's last decade, which the 1990-2026
|
||||
synthetic test does not support.
|
||||
- Not measured:
|
||||
- whether a live MOC/MOO order fills at the cross. Alpaca's paper engine
|
||||
does not run auctions, so only a small live account can check this.
|
||||
- impact at larger size. Orders here are thousands of dollars against
|
||||
crosses of millions.
|
||||
- whole-share sizing in small accounts.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import datetime as dt
|
||||
import json
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from research.lab.factor_scan import RESULTS, load
|
||||
|
||||
OUT = os.path.join(RESULTS, "auction_audit")
|
||||
RAW = os.path.join("data", "research", "auction_audit")
|
||||
TRADES_URL = "https://data.alpaca.markets/v2/stocks/trades"
|
||||
BARS_URL = "https://data.alpaca.markets/v2/stocks/bars"
|
||||
NY = "America/New_York"
|
||||
SAMPLE_START, SAMPLE_END, N_NIGHTS, SEED = "2016-01-05", "2026-09-24", 800, 20261012
|
||||
TOP_N = 10
|
||||
SEC_FEE_SIDE_BP = 0.14
|
||||
PASS_BP, STOP_BP = 1.0, 2.5
|
||||
|
||||
PREREG = {
|
||||
"question": "How far is the overnight sleeve's backtest night return (Yahoo open/close) from the closing-cross "
|
||||
"to opening-cross return it would actually earn?",
|
||||
"sample": {"nights": N_NIGHTS, "from": SAMPLE_START, "to": SAMPLE_END, "seed": SEED,
|
||||
"names": "sleeve_score top 10 (score at close t-2); overnight_12_1 names reported"},
|
||||
"cross": "largest-size SIP print with condition 'O' (open) / '6' (close)",
|
||||
"adjustment": "Alpaca daily bars: F = close(all) / close(raw)",
|
||||
"metric": "cost per side = night-mean (R_bt - R_real) / 2 + 0.14 bp SEC fee",
|
||||
"rule": {"PASS": "<= 1.0 bp/side: the 1 bp backtest cost holds",
|
||||
"MEASURED": "1.0-2.5 bp/side: re-plan returns with the measured cost",
|
||||
"STOP": "> 2.5 bp/side: no live overnight sleeve"},
|
||||
"data_status": "no auction prints compared before this rule; the 09:30 5-min bar check (overnight_only N5) "
|
||||
"was seen: Yahoo vs bar overnight returns corr 0.996 on selected names, mean diff 0.03-0.3 bp",
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# sample
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def selections(p) -> dict[str, pd.DataFrame]:
|
||||
from research.lab.factors import overnight_mom, rec_126
|
||||
from research.lab.overnight_only import top_mask
|
||||
from research.lab.us_sweep import _blend
|
||||
mem = p.member
|
||||
scores = {"sleeve_score": _blend(*[f.where(mem).rank(axis=1, pct=True)
|
||||
for f in (overnight_mom(p), rec_126(p))]).where(mem),
|
||||
"overnight_12_1": overnight_mom(p).where(mem)}
|
||||
return {k: top_mask(sc.shift(2), TOP_N) for k, sc in scores.items()}
|
||||
|
||||
|
||||
def sample_nights(p, sels: dict) -> pd.DataFrame:
|
||||
dates = p.dates[(p.dates >= SAMPLE_START) & (p.dates <= SAMPLE_END)]
|
||||
dates = [d for d in dates if sels["sleeve_score"].loc[d].sum() == TOP_N]
|
||||
rng = np.random.default_rng(SEED)
|
||||
nights = sorted(rng.choice(np.array(dates, dtype="datetime64[ns]"), size=N_NIGHTS, replace=False))
|
||||
rows = []
|
||||
for d in pd.DatetimeIndex(nights):
|
||||
prev = p.dates[p.dates.get_loc(d) - 1]
|
||||
for k, sel in sels.items():
|
||||
for s in sel.columns[sel.loc[d].values]:
|
||||
rows.append({"night": d, "prev": prev, "symbol": s, "candidate": k})
|
||||
return pd.DataFrame(rows)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# fetch
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _ts(day: pd.Timestamp, hhmmss: str) -> str:
|
||||
t = pd.Timestamp(f"{day.date()} {hhmmss}").tz_localize(NY)
|
||||
return t.isoformat()
|
||||
|
||||
|
||||
def alp(sym: str) -> str:
|
||||
return sym.replace("-", ".")
|
||||
|
||||
|
||||
def crosses(api, syms: list[str], day: pd.Timestamp, a: str, b: str, cond: str) -> dict[str, dict]:
|
||||
"""Largest-size print with condition ``cond`` per symbol in [a, b] New York time."""
|
||||
best: dict[str, dict] = {}
|
||||
params = {"symbols": ",".join(alp(s) for s in syms), "start": _ts(day, a), "end": _ts(day, b),
|
||||
"feed": "sip", "limit": 10000}
|
||||
for page in api.pages(TRADES_URL, params, "trades"):
|
||||
if not page:
|
||||
continue
|
||||
for s, ts in page.items():
|
||||
for t in ts:
|
||||
if cond in (t.get("c") or []) and t["s"] > best.get(s, {}).get("s", -1):
|
||||
best[s] = {"p": t["p"], "s": t["s"], "x": t["x"], "t": t["t"]}
|
||||
return {s.replace(".", "-"): v for s, v in best.items()}
|
||||
|
||||
|
||||
def fetch() -> None:
|
||||
import research.lab.fetch_options as fo
|
||||
os.makedirs(RAW, exist_ok=True)
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
prereg = os.path.join(OUT, "prereg.json")
|
||||
if not os.path.exists(prereg):
|
||||
with open(prereg, "w") as fh:
|
||||
json.dump(PREREG, fh, indent=2)
|
||||
p, _ = load("us")
|
||||
sample_path = os.path.join(RAW, "sample.csv")
|
||||
if os.path.exists(sample_path):
|
||||
S = pd.read_csv(sample_path, parse_dates=["night", "prev"])
|
||||
else:
|
||||
S = sample_nights(p, selections(p))
|
||||
S.to_csv(sample_path, index=False)
|
||||
api = fo.Api(min_interval=0.4, yield_to_snapshot=True)
|
||||
prints_path = os.path.join(RAW, "prints.csv")
|
||||
done = set()
|
||||
if os.path.exists(prints_path):
|
||||
done = set(pd.read_csv(prints_path, usecols=["night"])["night"].astype(str))
|
||||
nights = sorted(S["night"].unique())
|
||||
print(f"{len(nights)} nights, {S['symbol'].nunique()} symbols, {len(done)} done", flush=True)
|
||||
for i, d in enumerate(nights):
|
||||
d = pd.Timestamp(d)
|
||||
if str(d.date()) in done:
|
||||
continue
|
||||
g = S[S["night"] == d]
|
||||
syms, prev = sorted(g["symbol"].unique()), pd.Timestamp(g["prev"].iloc[0])
|
||||
close = crosses(api, syms, prev, "15:59:59", "16:10:00", "6")
|
||||
opn = crosses(api, syms, d, "09:29:59", "09:30:03", "O")
|
||||
for a, b in (("09:30:03", "09:31:00"), ("09:31:00", "09:40:00")):
|
||||
missing = [s for s in syms if s not in opn]
|
||||
if not missing:
|
||||
break
|
||||
opn.update(crosses(api, missing, d, a, b, "O"))
|
||||
rows = []
|
||||
for s in syms:
|
||||
c, o = close.get(s, {}), opn.get(s, {})
|
||||
rows.append({"night": str(d.date()), "prev": str(prev.date()), "symbol": s,
|
||||
"close_p": c.get("p"), "close_s": c.get("s"), "close_x": c.get("x"), "close_t": c.get("t"),
|
||||
"open_p": o.get("p"), "open_s": o.get("s"), "open_x": o.get("x"), "open_t": o.get("t")})
|
||||
pd.DataFrame(rows).to_csv(prints_path, mode="a", header=not os.path.exists(prints_path), index=False)
|
||||
if (i + 1) % 25 == 0:
|
||||
print(f" [{i + 1}/{len(nights)}] {d.date()} {api.n} requests", flush=True)
|
||||
bars_path = os.path.join(RAW, "bars.pkl")
|
||||
if not os.path.exists(bars_path):
|
||||
syms = sorted(S["symbol"].unique())
|
||||
out = {}
|
||||
for adj in ("raw", "all"):
|
||||
rows = []
|
||||
for k in range(0, len(syms), 100):
|
||||
params = {"symbols": ",".join(alp(s) for s in syms[k:k + 100]), "timeframe": "1Day",
|
||||
"start": "2015-12-01", "end": "2026-09-30", "adjustment": adj, "feed": "sip",
|
||||
"limit": 10000}
|
||||
for page in api.pages(BARS_URL, params, "bars"):
|
||||
if not page:
|
||||
continue
|
||||
for s, bl in page.items():
|
||||
rows += [{"symbol": s.replace(".", "-"), "date": b["t"][:10], "o": b["o"], "c": b["c"]}
|
||||
for b in bl]
|
||||
out[adj] = pd.DataFrame(rows)
|
||||
print(f"bars {adj}: {len(rows)} rows", flush=True)
|
||||
pd.to_pickle(out, bars_path)
|
||||
print(f"done, {api.n} requests", flush=True)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# compare
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def night_stats(x: pd.Series, nights: pd.Series) -> dict:
|
||||
"""Mean over nights of the night-mean, its SE, and name-level median."""
|
||||
nm = x.groupby(nights).mean().dropna()
|
||||
return {"nights": int(len(nm)), "name_nights": int(x.notna().sum()),
|
||||
"mean_bp": float(nm.mean() * 1e4), "se_bp": float(nm.std() / np.sqrt(len(nm)) * 1e4),
|
||||
"median_bp": float(x.median() * 1e4),
|
||||
"trimmed_mean_bp": float(x.clip(x.quantile(0.025), x.quantile(0.975)).mean() * 1e4)}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
res_path = os.path.join(OUT, "results.json")
|
||||
if os.path.exists(res_path):
|
||||
raise SystemExit(f"{res_path} exists; this study does not overwrite results")
|
||||
S = pd.read_csv(os.path.join(RAW, "sample.csv"), parse_dates=["night", "prev"])
|
||||
P = pd.read_csv(os.path.join(RAW, "prints.csv"), parse_dates=["night", "prev"]).drop_duplicates(["night", "symbol"])
|
||||
bars = pd.read_pickle(os.path.join(RAW, "bars.pkl"))
|
||||
p, _ = load("us")
|
||||
|
||||
def wide(df, col):
|
||||
return df.pivot_table(index=pd.to_datetime(df["date"]), columns="symbol", values=col, aggfunc="last")
|
||||
|
||||
braw_c, braw_o, ball_c = wide(bars["raw"], "c"), wide(bars["raw"], "o"), wide(bars["all"], "c")
|
||||
F = ball_c / braw_c
|
||||
X = S.merge(P, on=["night", "prev", "symbol"], how="left")
|
||||
|
||||
def at(df, dates, syms):
|
||||
return np.array([df.at[d, s] if (d in df.index and s in df.columns) else np.nan for d, s in zip(dates, syms)],
|
||||
dtype=float)
|
||||
|
||||
X["F_prev"], X["F_night"] = at(F, X["prev"], X["symbol"]), at(F, X["night"], X["symbol"])
|
||||
X["bar_c_prev"], X["bar_o_night"] = at(braw_c, X["prev"], X["symbol"]), at(braw_o, X["night"], X["symbol"])
|
||||
X["y_c_prev"] = at(p.close, X["prev"], X["symbol"])
|
||||
X["y_o_night"] = at(p.open, X["night"], X["symbol"])
|
||||
X["R_bt"] = X["y_o_night"] / X["y_c_prev"] - 1
|
||||
X["R_real"] = (X["open_p"] * X["F_night"]) / (X["close_p"] * X["F_prev"]) - 1
|
||||
X["R_bar"] = (X["bar_o_night"] * X["F_night"]) / (X["bar_c_prev"] * X["F_prev"]) - 1
|
||||
X["short"] = X["R_bt"] - X["R_real"]
|
||||
X["yahoo_vs_bar"] = X["R_bt"] - X["R_bar"]
|
||||
X["bar_vs_cross"] = X["R_bar"] - X["R_real"]
|
||||
X["close_cross_vs_bar"] = X["close_p"] / X["bar_c_prev"] - 1 # same day, raw: + = paid more than the bar close
|
||||
X["open_bar_vs_cross"] = X["bar_o_night"] / X["open_p"] - 1 # same day, raw: + = sold below the bar open
|
||||
X["listing"] = np.where(X["open_x"].isin(["N"]) | X["close_x"].isin(["N"]), "NYSE",
|
||||
np.where(X["open_x"].isin(["Q", "T"]) | X["close_x"].isin(["Q", "T"]), "Nasdaq", "other"))
|
||||
ok = X[["R_bt", "R_real"]].notna().all(axis=1) & (X["short"].abs() < 0.05)
|
||||
|
||||
res = {"coverage": {}, "candidates": {}}
|
||||
for k in ("sleeve_score", "overnight_12_1"):
|
||||
g = X[X["candidate"] == k]
|
||||
res["coverage"][k] = {"name_nights": int(len(g)), "no_close_cross": int(g["close_p"].isna().sum()),
|
||||
"no_open_cross": int(g["open_p"].isna().sum()),
|
||||
"no_yahoo_or_factor": int((g[["R_bt", "F_prev", "F_night"]].isna().any(axis=1)).sum()),
|
||||
"outliers_over_5pct": int((g["short"].abs() >= 0.05).sum())}
|
||||
print("coverage:", json.dumps(res["coverage"]))
|
||||
for k in ("sleeve_score", "overnight_12_1"):
|
||||
g = X[(X["candidate"] == k) & ok]
|
||||
d = {c: night_stats(g[c], g["night"]) for c in ("short", "yahoo_vs_bar", "bar_vs_cross",
|
||||
"close_cross_vs_bar", "open_bar_vs_cross")}
|
||||
d["cost_per_side_bp"] = d["short"]["mean_bp"] / 2 + SEC_FEE_SIDE_BP
|
||||
d["by_year"] = {int(y): night_stats(h["short"], h["night"]) for y, h in g.groupby(g["night"].dt.year)}
|
||||
d["by_period"] = {lab: night_stats(h["short"], h["night"]) for lab, h in
|
||||
(("2016-20", g[g["night"] < "2021-01-01"]), ("2021-26", g[g["night"] >= "2021-01-01"]))}
|
||||
d["by_listing"] = {lab: night_stats(h["short"], h["night"]) for lab, h in g.groupby("listing")}
|
||||
res["candidates"][k] = d
|
||||
s = d["short"]
|
||||
print(f"\n== {k}: {s['name_nights']} name-nights over {s['nights']} nights")
|
||||
print(f" shortfall R_bt - R_real: mean {s['mean_bp']:+.2f} bp/night (SE {s['se_bp']:.2f}), median "
|
||||
f"{s['median_bp']:+.2f}, trimmed {s['trimmed_mean_bp']:+.2f} -> cost per side {d['cost_per_side_bp']:+.2f} bp")
|
||||
for c in ("yahoo_vs_bar", "bar_vs_cross", "close_cross_vs_bar", "open_bar_vs_cross"):
|
||||
print(f" {c:20s} mean {d[c]['mean_bp']:+.2f} bp (SE {d[c]['se_bp']:.2f}) median {d[c]['median_bp']:+.2f}")
|
||||
print(" by year:", " ".join(f"{y} {v['mean_bp']:+.1f}±{v['se_bp']:.1f}" for y, v in d["by_year"].items()))
|
||||
print(" by period:", {a: round(v["mean_bp"], 2) for a, v in d["by_period"].items()},
|
||||
" by listing:", {a: (round(v["mean_bp"], 2), v["name_nights"]) for a, v in d["by_listing"].items()})
|
||||
cost = res["candidates"]["sleeve_score"]["cost_per_side_bp"]
|
||||
verdict = "PASS" if cost <= PASS_BP else ("MEASURED" if cost <= STOP_BP else "STOP")
|
||||
res["verdict"] = {"cost_per_side_bp": cost, "decision": verdict}
|
||||
print(f"\nverdict: cost per side {cost:+.2f} bp -> {verdict}")
|
||||
with open(res_path, "w") as fh:
|
||||
json.dump(res, fh, indent=2)
|
||||
X.to_csv(os.path.join(OUT, "name_nights.csv.gz"), index=False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("cmd", nargs="?", default="run", choices=["run", "fetch"])
|
||||
if ap.parse_args().cmd == "fetch":
|
||||
fetch()
|
||||
else:
|
||||
main()
|
||||
Reference in New Issue
Block a user