Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 39 additions & 14 deletions scripts/publish_ibit_btc_platform_handoff.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
from datetime import UTC, date, datetime, timedelta
from pathlib import Path

BINANCE_BTCUSDT_DAILY_URL = (
"https://api.binance.com/api/v3/klines?symbol=BTCUSDT&interval=1d&limit=800"
BINANCE_BTCUSDT_DAILY_URLS = (
"https://data-api.binance.vision/api/v3/klines?symbol=BTCUSDT&interval=1d&limit=800",
"https://api.binance.us/api/v3/klines?symbol=BTCUSDT&interval=1d&limit=800",
"https://api.binance.com/api/v3/klines?symbol=BTCUSDT&interval=1d&limit=800",
)
DEFAULT_CONSUMER = "us_equity:ibit_smart_dca"
DEFAULT_STRATEGY = "ibit_smart_dca"
Expand All @@ -35,17 +37,11 @@ def resolve_as_of(*, csv_path: Path, requested: str | None, today: date | None =
return rows[-1]


def fetch_binance_btc_daily_csv(output_path: Path) -> int:
def fetch_binance_btc_daily_csv(output_path: Path) -> tuple[int, str]:
"""Download BTCUSDT daily OHLCV from Binance public API into a local CSV."""
output_path.parent.mkdir(parents=True, exist_ok=True)
try:
with urllib.request.urlopen(BINANCE_BTCUSDT_DAILY_URL, timeout=30) as response:
payload = json.loads(response.read().decode())
except urllib.error.URLError as exc:
raise RuntimeError(f"failed to download Binance BTCUSDT daily klines: {exc}") from exc

if not payload:
raise RuntimeError("Binance BTCUSDT daily klines response was empty")
payload, source_url = _download_binance_btcusdt_daily_klines()
provider_name = _provider_name_for_url(source_url)

rows: list[dict[str, object]] = []
for entry in payload:
Expand All @@ -70,7 +66,33 @@ def fetch_binance_btc_daily_csv(output_path: Path) -> int:
writer.writeheader()
writer.writerows(rows)

return len(rows)
print(f"downloaded BTCUSDT daily rows from {provider_name}")
return len(rows), provider_name


def _download_binance_btcusdt_daily_klines() -> tuple[list[object], str]:
errors: list[str] = []
for url in BINANCE_BTCUSDT_DAILY_URLS:
try:
with urllib.request.urlopen(url, timeout=30) as response:
payload = json.loads(response.read().decode())
except urllib.error.URLError as exc:
errors.append(f"{url}: {exc}")
continue
if not payload:
errors.append(f"{url}: empty response")
continue
return payload, url
joined = "; ".join(errors) or "no Binance endpoints configured"
raise RuntimeError(f"failed to download Binance BTCUSDT daily klines: {joined}")


def _provider_name_for_url(url: str) -> str:
if "binance.vision" in url:
return "binance_vision_public"
if "binance.us" in url:
return "binance_us_public"
return "binance_public"


def build_ibit_btc_platform_handoff(
Expand All @@ -80,6 +102,7 @@ def build_ibit_btc_platform_handoff(
as_of: str,
code_commit: str,
source_version: str,
provider: str = "binance_vision_public",
consumer: str = DEFAULT_CONSUMER,
strategy: str = DEFAULT_STRATEGY,
) -> dict[str, Path]:
Expand All @@ -104,7 +127,7 @@ def build_ibit_btc_platform_handoff(
"--as-of",
as_of,
"--provider",
"binance_public",
provider,
"--provider-dataset",
"btcusdt_daily_klines",
"--source-version",
Expand Down Expand Up @@ -222,8 +245,9 @@ def main(argv: list[str] | None = None) -> int:
input_csv = args.input_csv or (work_dir / "inputs" / "btc_daily.csv")
code_commit = args.code_commit or __import__("os").environ.get("GITHUB_SHA", "0" * 40)

provider = "local_csv"
if args.input_csv is None:
row_count = fetch_binance_btc_daily_csv(input_csv)
row_count, provider = fetch_binance_btc_daily_csv(input_csv)
print(f"downloaded {row_count} BTCUSDT daily rows to {input_csv}")
elif not input_csv.is_file():
print(f"error: input CSV not found: {input_csv}", file=sys.stderr)
Expand All @@ -238,6 +262,7 @@ def main(argv: list[str] | None = None) -> int:
as_of=as_of,
code_commit=code_commit,
source_version=args.source_version,
provider=provider,
consumer=args.consumer,
strategy=args.strategy,
)
Expand Down
8 changes: 8 additions & 0 deletions tests/test_publish_ibit_btc_platform_handoff.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import pytest

from scripts.publish_ibit_btc_platform_handoff import (
BINANCE_BTCUSDT_DAILY_URLS,
_provider_name_for_url,
default_as_of,
resolve_as_of,
)
Expand Down Expand Up @@ -54,3 +56,9 @@ def test_resolve_as_of_raises_for_empty_csv(tmp_path: Path) -> None:

with pytest.raises(ValueError, match="no dates found"):
resolve_as_of(csv_path=csv_path, requested="2026-06-25")


def test_provider_name_for_url() -> None:
assert _provider_name_for_url(BINANCE_BTCUSDT_DAILY_URLS[0]) == "binance_vision_public"
assert _provider_name_for_url(BINANCE_BTCUSDT_DAILY_URLS[1]) == "binance_us_public"
assert _provider_name_for_url(BINANCE_BTCUSDT_DAILY_URLS[2]) == "binance_public"
Loading