spb/hfmarketdata
Public
Open high-frequency market data platform — FirstRate full-history downloader, DuckDB/Parquet lake, open REST API and React docs platform (www.hfmarketdata.io)
JavaScript 53.7%
Python 38.3%
CSS 4.6%
TypeScript 3.1%
1#!/usr/bin/env python32"""Build server-side continuous futures series (v2) with several roll/adjust choices and compare them3to each other and to the vendor continuous series (v1).45 continuous_compare.py --root CL --start 2018-01-01 --rolls volume,calendar --adjusts back_adjusted,ratio \6 [--depth 1] [--timeframe 1day] [--vendor contin_adj_ratio] [--out cl.csv] [--plot cl.png]7"""8from __future__ import annotations910import argparse11import sys12from pathlib import Path1314import numpy as np15import pandas as pd1617sys.path.insert(0, str(Path(__file__).parent))18import hfmd # noqa: E402192021def describe_rolls(name: str, df: pd.DataFrame, meta: dict) -> None:22 rolls = meta.get("roll_dates") or []23 if df.empty:24 print(f"\n[{name}] no data")25 return26 years = max((df["datetime"].iloc[-1] - df["datetime"].iloc[0]).days / 365.25, 1e-9)27 roll_txt = f"{len(rolls)} rolls ({len(rolls) / years:.1f}/yr)" if rolls or not name.startswith("vendor") else "roll dates not exposed (vendor construction)"28 print(f"\n[{name}] {len(df):,} bars {df['datetime'].iloc[0].date()} → {df['datetime'].iloc[-1].date()} · {roll_txt}")29 if rolls:30 shown = rolls if len(rolls) <= 12 else rolls[:6] + ["…"] + rolls[-6:]31 print(" roll dates:", ", ".join(str(r) if isinstance(r, str) else str(r.get("date", r)) for r in shown))32 if "close" in df and len(df) > 1:33 print(f" last close {df['close'].iloc[-1]:.4f} · first close {df['close'].iloc[0]:.4f}"34 + (" (adjusted levels are NOT tradable prices)" if "adjust=none" not in name else ""))353637def main() -> int:38 ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)39 ap.add_argument("--root", required=True)40 ap.add_argument("--start", default="2015-01-01")41 ap.add_argument("--end")42 ap.add_argument("--timeframe", default="1day")43 ap.add_argument("--depth", type=int, default=1)44 ap.add_argument("--rolls", default="volume", help="comma list: volume, open_interest, calendar")45 ap.add_argument("--adjusts", default="back_adjusted", help="comma list: none, back_adjusted, ratio")46 ap.add_argument("--vendor", help="also fetch the v1 vendor continuous with this adjustment: contin_UNadj, contin_adj_ratio, contin_adj_absolute")47 ap.add_argument("--out", help="CSV with all series' closes aligned on datetime")48 ap.add_argument("--plot")49 a = ap.parse_args()50 root = a.root.upper()5152 series: dict[str, pd.Series] = {}53 for roll in a.rolls.split(","):54 for adj in a.adjusts.split(","):55 name = f"v2 roll={roll} adjust={adj} depth={a.depth}"56 try:57 df, meta = hfmd.continuous(root, roll=roll, adjust=adj, depth=a.depth, timeframe=a.timeframe, start=a.start, end=a.end)58 except hfmd.HfmdError as e:59 print(f"\n[{name}] {e}", file=sys.stderr)60 if e.status == 404:61 print(" → the v2 continuous endpoint is not deployed on this server yet; use scripts/stitch_local.py", file=sys.stderr)62 continue63 describe_rolls(name, df, meta)64 if not df.empty:65 series[name] = df.set_index("datetime")["close"].astype(float)6667 if a.vendor:68 name = f"vendor {a.vendor}"69 try:70 vdf = hfmd.bars("futures", root, a.timeframe, a.start, a.end, a.vendor)71 describe_rolls(name, vdf, {})72 if not vdf.empty:73 series[name] = vdf.set_index("datetime")["close"].astype(float)74 except hfmd.HfmdError as e:75 print(f"\n[{name}] {e}", file=sys.stderr)7677 if not series:78 print("\nno series retrieved", file=sys.stderr)79 return 18081 wide = pd.concat(series, axis=1).sort_index()82 rets = wide.pct_change()83 print("\nreturn correlation between series (daily pct changes; 'none'/UNadj variants include roll gaps):")84 with pd.option_context("display.width", 200):85 print(rets.corr().round(4).to_string())86 if len(series) > 1:87 names = list(series)88 base = names[-1]89 print(f"\nannualised tracking difference of log-returns vs '{base}':")90 for n in names[:-1]:91 d = (np.log1p(rets[n]) - np.log1p(rets[base])).dropna()92 print(f" {n:<45} mean={d.mean() * 252:+.4%}/yr std={d.std() * np.sqrt(252):.4%} n={len(d)}")9394 if a.out:95 wide.to_csv(a.out)96 print(f"\nwrote {a.out} ({len(wide):,} rows × {wide.shape[1]} series)")97 if a.plot:98 try:99 import matplotlib100 matplotlib.use("Agg")101 import matplotlib.pyplot as plt102 except ImportError:103 print("matplotlib not installed", file=sys.stderr)104 return 0105 fig, ax = plt.subplots(figsize=(11, 5))106 for n, s in series.items():107 ax.plot(s.index, s / s.dropna().iloc[0] * 100, lw=1.0, label=n)108 ax.set_title(f"{root} continuous series rebased to 100 (differences = roll/adjust methodology)")109 ax.grid(alpha=0.25)110 ax.legend(fontsize=8)111 fig.tight_layout()112 fig.savefig(a.plot, dpi=130)113 print(f"wrote {a.plot}")114 return 0115116117if __name__ == "__main__":118 sys.exit(main())119