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:
+19
-46
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user