"""Synthetic bars in the exact FinData schema (license-free fixtures for tests and the release package).""" from __future__ import annotations import numpy as np import polars as pl import pyarrow as pa from .calendar import sessions def synthetic_bars(symbol: str = "SYN", start: str = "2024-01-02", end: str = "2024-03-28", seed: int = 0, p_missing: float = 0.02, split: tuple[str, float] | None = None, extended: bool = True, sigma: float = 4e-4, asset_class: str = "stock") -> tuple[pl.DataFrame, list[dict]]: rng = np.random.default_rng(seed) rows = [] price = 100.0 actions = [] if split: actions.append({"action_type": "split", "ex_date": split[0], "value": split[1]}) for s in sessions(start, end): n = s.n_slots # extended hours bars before the open (04:00 -> 09:29) and after (16:01 -> 19:59) slots = list(range(-330, 0)) + list(range(0, n)) + list(range(n, n + 239)) if extended else list(range(0, n)) for k in slots: ts = s.open_utc + np.timedelta64(k, "m") is_regular = 0 <= k < n if is_regular and k != 0 and k != n - 1 and rng.random() < p_missing: continue if not is_regular and rng.random() < 0.85: continue r = rng.standard_t(4) * sigma * (1.6 if k < 30 or k > n - 30 else 1.0) o = price c = price * np.exp(r) h = max(o, c) * np.exp(abs(rng.normal(0, sigma / 2))) lo = min(o, c) * np.exp(-abs(rng.normal(0, sigma / 2))) v = float(rng.integers(100, 50_000)) * (10 if k == n - 1 else 1) price = c raw_factor = 1.0 if split and str(s.date.date()) < split[0]: raw_factor = split[1] # raw prices before ex-date are "pre-split" (higher) rows.append({"instrument_id": f"firstrate:{asset_class}:{symbol}", "symbol_as_reported": symbol, "asset_class": asset_class, "interval": "1m", "timestamp_utc": ts.to_pydatetime(), "session_date": s.date.date(), "open": o * raw_factor, "high": h * raw_factor, "low": lo * raw_factor, "close": c * raw_factor, "volume": v / raw_factor, "currency": "USD", "adjustment_type": "raw", "source_id": "synthetic", "quality_flags": [], "revision_id": 1}) df = pl.DataFrame(rows).with_columns(pl.col("timestamp_utc").dt.replace_time_zone("UTC"), pl.col("quality_flags").cast(pl.List(pl.Utf8))) return df, actions def to_arrow(df: pl.DataFrame) -> pa.Table: return df.to_arrow()