examples: migrate to the native data layer (drop ws/coder-websocket/jackson/jsonlite) (#316)

Stacked on #315 (the native Binance REST fetcher). Retarget to `main` once #315 merges.

Migrates the runnable examples off third-party data-I/O packages onto Wickra's
native data layer (`CandleReader`, `Resampler`, `BinanceFeed`, `fetch_*klines`).

## Third-party packages removed (the zero-dep selling point)
- **Node**: `ws` (live feed → BinanceFeed) — dropped from package.json + lockfile
- **Go**: `github.com/coder/websocket` — dropped from go.mod / go.sum (`go mod tidy`)
- **Java**: `jackson-databind` (live feed + REST fetch) — dropped from pom.xml
- **R**: `jsonlite` + `websocket` + `later` — dropped from the README notes

Each language's CSV loading now goes through `CandleReader`, manual resampling
through `Resampler`, the live feed through `BinanceFeed`, and (Java/R) the REST
download through the native fetcher.

## Verification
Ran the offline examples per language against the bundled data — backtest and
multi_timeframe produce identical output across Python / Node / Go / Java / R
(e.g. ATR(14) last 345.1010; 1h→5m resamples to 240 bars, →15m to 80 bars).

C# / C / WASM (stdlib-only, no third-party deps to remove) follow in this branch.

Note: the streaming `strategy_*` examples have pre-existing candle-indicator
runtime bugs (CI only syntax-smokes them); the CSV migration preserves their
shape and leaves those bugs for a separate fix.
This commit is contained in:
kingchenc
2026-06-17 01:49:11 +02:00
committed by GitHub
parent 2ae76bb90e
commit 677ea37402
40 changed files with 576 additions and 1102 deletions
+19 -46
View File
@@ -14,20 +14,14 @@ indicators are wired correctly without pulling in pandas or a charting stack.
from __future__ import annotations
import argparse
import csv
import sys
from dataclasses import dataclass
from typing import List
import numpy as np
import wickra as ta
# Columns the OHLCV layout requires; the CSV header must name every one.
REQUIRED_COLUMNS = ("timestamp", "open", "high", "low", "close", "volume")
@dataclass
class History:
timestamp: np.ndarray
@@ -39,51 +33,30 @@ class History:
def read_history(path: str) -> History:
"""Load an OHLCV CSV into typed NumPy columns.
"""Load an OHLCV CSV into typed NumPy columns with Wickra's native
``CandleReader`` — no manual CSV parsing.
``CandleReader`` validates the header (``timestamp,open,high,low,close,volume``),
tolerates a UTF-8 BOM and surrounding whitespace, and raises ``ValueError`` on a
missing column or a non-numeric / invalid OHLC row.
Raises:
ValueError: if the file has no header, is missing a required column,
holds no data rows, or contains a non-numeric value (the message
pinpoints the offending row and column instead of surfacing an
opaque ``KeyError`` or NumPy ``ValueError``).
ValueError: if the CSV header or a data row is malformed.
"""
rows: List[List[str]] = []
with open(path, newline="") as f:
reader = csv.DictReader(f)
if reader.fieldnames is None:
raise ValueError(f"{path}: CSV has no header row")
missing = [c for c in REQUIRED_COLUMNS if c not in reader.fieldnames]
if missing:
raise ValueError(
f"{path}: CSV is missing required column(s): "
f"{', '.join(missing)}; found: {', '.join(reader.fieldnames)}"
)
for row in reader:
rows.append([row[c] for c in REQUIRED_COLUMNS])
if not rows:
with open(path, encoding="utf-8") as f:
candles = ta.CandleReader(f.read()).read()
if not candles:
raise ValueError(f"{path}: CSV has a header but no data rows")
try:
arr = np.array(rows, dtype=np.float64)
except (TypeError, ValueError) as exc:
# NumPy's own message names neither the row nor the column — locate
# the first non-numeric cell and report it precisely.
for line_no, row in enumerate(rows, start=2):
for col, value in zip(REQUIRED_COLUMNS, row):
try:
float(value)
except (TypeError, ValueError):
raise ValueError(
f"{path}: row {line_no} column '{col}' is not "
f"numeric: {value!r}"
) from exc
raise # could not localise — surface the original error
# CandleReader yields (open, high, low, close, volume, timestamp) tuples.
# Transpose into contiguous 1-D columns (batch() needs C-contiguous arrays).
o, h, l, c, v, ts = (np.array(col, dtype=np.float64) for col in zip(*candles))
return History(
timestamp=arr[:, 0].astype(np.int64),
open=arr[:, 1],
high=arr[:, 2],
low=arr[:, 3],
close=arr[:, 4],
volume=arr[:, 5],
timestamp=ts.astype(np.int64),
open=o,
high=h,
low=l,
close=c,
volume=v,
)