File size: 6,924 Bytes
672df78 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | """Build the curated eval dataset repo: Avi-L/crypto-quant-eval-data.
Sources (all verified to resolve on the HF Hub, 2026-10-23):
bars : vaquum/binance_btcusdt_1m_klines (BTCUSDT spot 1m, 2020->present)
fund : Torch-Trade/btcusdt_perp_funding_8h_05_2021_to_02_2026 (MIT)
Torch-Trade/ethusdt_perp_funding_8h_05_2021_to_02_2026 (MIT)
chain : coinmetrics-community btc.csv (GitHub, no usable HF on-chain repo)
ETH spot 1m is NOT ingested: the only verified ETH 1m repo
(HaianCao/Binance-Public-OHLCV) is 34.6 GB — out of scope for this job;
ETH enters via funding. Documented in the dataset README.
Outputs (uploaded to the dataset repo):
bars_BTCUSDT_1h.parquet, bars_BTCUSDT_1d.parquet (datetime index, OHLCV
+ taker_volume_share when available, funding_rate_last, funding_tick)
funding_BTCUSDT.parquet, funding_ETHUSDT.parquet (funding_rate, 8h)
onchain_btc_daily.parquet
manifest.json (sha256 of every output + source file hashes)
"""
from __future__ import annotations
import hashlib
import json
import tempfile
from pathlib import Path
import pandas as pd
import pyarrow.parquet as pq
from huggingface_hub import HfApi, snapshot_download
DEST_REPO = "Avi-L/crypto-quant-eval-data"
BARS_REPO = "vaquum/binance_btcusdt_1m_klines"
FUNDING_REPOS = {
"BTCUSDT": "Torch-Trade/btcusdt_perp_funding_8h_05_2021_to_02_2026",
"ETHUSDT": "Torch-Trade/ethusdt_perp_funding_8h_05_2021_to_02_2026",
}
ONCHAIN_URL = (
"https://raw.githubusercontent.com/coinmetrics/data/master/csv/btc.csv"
)
MANIFEST: dict = {"generated": "2026-10-23", "sources": {}, "outputs": {}}
def sha256_file(path: Path) -> str:
h = hashlib.sha256()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(1 << 20), b""):
h.update(chunk)
return h.hexdigest()
def load_bars_streaming(cache: Path) -> pd.DataFrame:
"""Read the vaquum 1m parquet files one at a time, resample to 1h/1d."""
d = Path(snapshot_download(repo_id=BARS_REPO, repo_type="dataset",
local_dir=cache / "bars"))
files = sorted(d.rglob("*.parquet"))
if not files:
raise ValueError(f"no parquet files under {d}")
daily, hourly = [], []
for f in files:
df = pd.read_parquet(f)
# vaquum schema: datetime (ts[ms,UTC]) + OHLCV-ish columns
tcol = "datetime" if "datetime" in df.columns else df.columns[0]
ts = pd.to_datetime(df[tcol], utc=True)
df = df.set_index(ts).sort_index()
agg = {"open": "first", "high": "max", "low": "min", "close": "last",
"volume": "sum"}
# optional columns present in some schemas
for col, how in (("quote_volume", "sum"), ("count", "sum"),
("no_of_trades", "sum"),
("taker_buy_volume", "sum")):
if col in df.columns:
agg[col] = how
agg_df = df[list(agg)].resample("1h").agg(agg).dropna(
subset=["open", "close"])
hourly.append(agg_df)
daily.append(df[list(agg)].resample("1d").agg(agg).dropna(
subset=["open", "close"]))
h = pd.concat(hourly)
dd = pd.concat(daily)
return h[~h.index.duplicated()].sort_index(), dd[~dd.index.duplicated()].sort_index()
def load_funding(cache: Path, repo_id: str) -> pd.DataFrame:
d = Path(snapshot_download(repo_id=repo_id, repo_type="dataset",
local_dir=cache / repo_id.replace("/", "_")))
csvs = sorted(d.rglob("*.csv"))
pqs = sorted(d.rglob("*.parquet"))
if csvs:
df = pd.concat(pd.read_csv(f) for f in csvs)
elif pqs:
df = pd.concat(pd.read_parquet(f) for f in pqs)
else:
raise ValueError(f"no csv/parquet under {d}")
if "timestamp" in df.columns:
df.index = pd.to_datetime(df["timestamp"], utc=True, unit="ms")
elif "datetime" in df.columns:
df.index = pd.to_datetime(df["datetime"], utc=True)
else:
raise ValueError(f"funding has no time column: {list(df.columns)}")
if "funding_rate" not in df.columns:
raise ValueError(f"funding has no funding_rate: {list(df.columns)}")
return df[["funding_rate"]].sort_index()
def merge_funding(bars: pd.DataFrame, funding: pd.DataFrame) -> pd.DataFrame:
out = bars.copy()
f = funding.reindex(out.index, method="ffill")["funding_rate"]
out["funding_rate_last"] = f
out["funding_tick"] = out.index.isin(funding.index)
return out
def onchain_daily(out: Path) -> None:
df = pd.read_csv(ONCHAIN_URL)
df["time"] = pd.to_datetime(df["time"], utc=True)
want = [c for c in ("time", "AdrActCnt", "BlkCnt", "CapMrktCurUSD",
"FeeMeanNtv", "IssuanceNtv", "TxCnt", "NVTAdj")
if c in df.columns]
df[want].set_index("time").to_parquet(out / "onchain_btc_daily.parquet")
MANIFEST["sources"]["coinmetrics-community btc.csv"] = ONCHAIN_URL
def main() -> None:
with tempfile.TemporaryDirectory() as tmp:
cache = Path(tmp)
out = cache / "curated"
out.mkdir()
print("[ingest] bars: streaming + resampling", flush=True)
hourly, daily = load_bars_streaming(cache)
funding_btc = load_funding(cache, FUNDING_REPOS["BTCUSDT"])
funding_eth = load_funding(cache, FUNDING_REPOS["ETHUSDT"])
for freq, bars in (("1h", hourly), ("1d", daily)):
merged = merge_funding(bars, funding_btc)
p = out / f"bars_BTCUSDT_{freq}.parquet"
merged.reset_index(names="datetime").to_parquet(p)
MANIFEST["outputs"][p.name] = {"sha256": sha256_file(p),
"rows": len(merged), "freq": freq}
print(f"[ingest] {p.name}: {len(merged):,} rows", flush=True)
for sym, df in (("BTCUSDT", funding_btc), ("ETHUSDT", funding_eth)):
p = out / f"funding_{sym}.parquet"
df.to_parquet(p)
MANIFEST["outputs"][p.name] = {"sha256": sha256_file(p),
"rows": len(df), "freq": "8h"}
print(f"[ingest] {p.name}: {len(df):,} ticks", flush=True)
print("[ingest] on-chain metrics", flush=True)
onchain_daily(out)
p = out / "onchain_btc_daily.parquet"
MANIFEST["outputs"][p.name] = {"sha256": sha256_file(p),
"rows": pq.ParquetFile(p).metadata.num_rows,
"freq": "1d"}
(out / "manifest.json").write_text(json.dumps(MANIFEST, indent=2))
api = HfApi()
api.upload_folder(
repo_id=DEST_REPO, repo_type="dataset",
folder_path=str(out), commit_message="curated eval dataset v1",
)
print(f"[ingest] uploaded {len(MANIFEST['outputs'])} files + manifest "
f"to {DEST_REPO}", flush=True)
if __name__ == "__main__":
main()
|