EOD, events and exec trades
Three small, unrelated collectors on one page — daily index bars from Yahoo, the data.vike.io live events API, and a venue account's own trade-history export.
These three vike-backfill bins share nothing but the store they write. Each takes --store DIR, resolved through the store's own ladder — $VIKE_HIST_STORE and a $VIKE_SETTINGS_DIR-declared project both above <repo>/market_data/hist — and each is idempotent through the store's commit-key append, so a repeated run is a no-op rather than a duplicate. What they fill is described on the history store; the column shape of each kind is under concepts/data.
eod_backfill — daily bars
eod_backfill --source yahoo --symbols SPX=^GSPC,VIX=^VIX,NDX=^NDX,DJI=^DJI --years 10 [--venue V] [--store DIR]Daily OHLCV for equity indices and stocks, ingested as kind=bar at interval 1d, idempotent per run-day. --symbols is a comma list of vikeSymbol=sourceTicker pairs; a bare SYM means the source ticker equals the symbol. --years defaults to 10. --venue defaults to the source name, so provenance lives in the partition.
crates/vike-backfill/src/eod/mod.rs is explicit that these are read-only data sources, not execution venues, which is why they live in the backfill crate rather than under crates/bridges/. The extensibility point is the EodSource trait: a provider builds a URL, GETs it, and hands the body to a pure, fixture-tested parser — parse_yahoo_json for the one shipped provider. vike_backfill::eod::SOURCES is exactly ["yahoo"]. A stooq provider was dropped after a live test found it fronting its CSV endpoint with a JavaScript proof-of-work wall a plain HTTP client cannot pass.
The Yahoo source (crates/vike-backfill/src/eod/yahoo.rs) requests query1.finance.yahoo.com/v8/finance/chart/{ticker}?interval=1d&range={years}y. It needs a User-Agent header — Yahoo answers 403 without one — and index tickers carry a ^ prefix (^GSPC, ^VIX, ^NDX, ^DJI). A day with any null OHLC field is skipped, which is how Yahoo marks a no-trade day.
Keyless, and unofficial
The module doc calls this source keyless but ToS-gray and best-effort: the official Yahoo API is retired, and the unofficial v8 endpoint can break without notice. The bin exits non-zero only when every symbol failed; a single failed ticker is logged and skipped.
events_api_backfill — the live events API
events_api_backfill --from YYYY-MM-DD --to YYYY-MM-DD --tokens-file PATH [--tokens T1,T2,...]
[--store DIR] [--base URL] [--kind book|trade|quote|all] [--concurrency N] [--dry-run]This is the no-ClickHouse, no-multi-GB-download route to Polymarket L2 history: GET https://data.vike.io/v1/events?token_id=...&from_ms=...&to_ms=...&limit=...[&cursor=...], paged JSON, one token at a time — the rows a customer wants for the one token they care about. Its bulk-Parquet sibling is vike_archive_backfill on the Data API page; the two share the vike-archive Cargo feature (this bin is built only under it) and the same X-API-Key credential, VIKE_ARCHIVE_API_KEY, which the bin reads from the credential store and passes into the library as a plain parameter — vike_backfill::events_api itself never touches the environment. Series land under venue=polymarket with commit key eventsapi:{kind}:{token}:{day}, keyed per token because the endpoint is fetched per token.
One (date, token) pair is one unit of work: the UTC calendar day [midnight, next midnight), paged to completion by EventsClient::fetch_all_single. --concurrency (default 6) runs that many fetch loops on worker threads — the measured ~0.81 s per market round trip is latency-bound, so overlapping requests is the win — while every decode and store append happens on the main thread, single-writer. --tokens and --tokens-file (one id per line, # comments allowed) union into one list; giving neither is an error, since "no filter" has no meaning for a per-token endpoint. --dry-run prints the work-unit count and the base URL with no network call and no store opened.
crates/vike-backfill/src/events_api.rs documents three ways this endpoint differs from every sibling path, all verified against real traffic on 2026-07-28:
- A
price_changedelta row already carries its one-level ladder inbids/asks, including a zero-size removal, so book rows are parsed uniformly for snapshots and deltas. best_bid/best_askare always0.0— more than 10,000 consecutive rows of an actively tradedbtc-5mmarket showed no non-zero value.--kind quoteis therefore a harmless no-op lane, and an events-API-sourced store has no quote lane until the server populates those fields.- No
tick_sizefield exists on the wire.book_update_from_rowdefaults toFALLBACK_TICK_SIZE(0.01, the venue's standard tick) because0.0is actively harmful:L2Book::newfalls back to1.0for a non-positive tick, quantizing a[0.0, 1.0]market into two buckets. Over a real 26-market slice every tick-sensitive maker config traded zero times whiletrailing_scalper, which prices off the trade tape, traded normally — the data was sound; the tick metadata was missing. A market on a genuinely finer or coarser tick is still misquantized.
Multi-token requests are detected, not assumed: with two or more tokens the bin makes one probe_multi_token call first. On 2026-07-28 the live endpoint still answered HTTP 400, so one unit per token is what actually runs; the decoder already parses a per-row token_id, so a batched response will decode the day it ships.
exec_trade_backfill — your own fills
exec_trade_backfill --file <myTrades.json> --symbol BTCUSDT [--venue binance] [--store DIR]This one imports a venue account's trade-history export into the kind=exec_fill execution log — the offline twin of the live JournalMaterializer. Both write the same series, so live and historical fills converge into one queryable log. The v1 input is a Binance myTrades JSON export, which needs no credentials. The commit key embeds the fill window, so re-importing the same export is a store no-op.
vike_backfill::exec_import::parse_binance_my_trades is pure: no clock, no network. A malformed or short row is skipped rather than aborting a bulk import; numeric strings are taken in Binance's wire format; side is +1 for a buyer and −1 for a seller; commission is the positive charged fee; isMaker maps to maker/taker and, when absent from a minimal export, the liquidity side stays empty rather than guessed. An ExecTradeSource trait exists for a signed live-REST source per venue, but v1 registers none — the export file is the only path.