1"""Synthetic bars in the exact FinData schema (license-free fixtures for tests and the release package)."""2from __future__ import annotations34import numpy as np5import polars as pl6import pyarrow as pa78from .calendar import sessions91011def synthetic_bars(symbol: str = "SYN", start: str = "2024-01-02", end: str = "2024-03-28", seed: int = 0,12 p_missing: float = 0.02, split: tuple[str, float] | None = None, extended: bool = True,13 sigma: float = 4e-4, asset_class: str = "stock") -> tuple[pl.DataFrame, list[dict]]:14 rng = np.random.default_rng(seed)15 rows = []16 price = 100.017 actions = []18 if split:19 actions.append({"action_type": "split", "ex_date": split[0], "value": split[1]})20 for s in sessions(start, end):21 n = s.n_slots22 # extended hours bars before the open (04:00 -> 09:29) and after (16:01 -> 19:59)23 slots = list(range(-330, 0)) + list(range(0, n)) + list(range(n, n + 239)) if extended else list(range(0, n))24 for k in slots:25 ts = s.open_utc + np.timedelta64(k, "m")26 is_regular = 0 <= k < n27 if is_regular and k != 0 and k != n - 1 and rng.random() < p_missing:28 continue29 if not is_regular and rng.random() < 0.85:30 continue31 r = rng.standard_t(4) * sigma * (1.6 if k < 30 or k > n - 30 else 1.0)32 o = price33 c = price * np.exp(r)34 h = max(o, c) * np.exp(abs(rng.normal(0, sigma / 2)))35 lo = min(o, c) * np.exp(-abs(rng.normal(0, sigma / 2)))36 v = float(rng.integers(100, 50_000)) * (10 if k == n - 1 else 1)37 price = c38 raw_factor = 1.039 if split and str(s.date.date()) < split[0]:40 raw_factor = split[1] # raw prices before ex-date are "pre-split" (higher)41 rows.append({"instrument_id": f"firstrate:{asset_class}:{symbol}", "symbol_as_reported": symbol,42 "asset_class": asset_class, "interval": "1m", "timestamp_utc": ts.to_pydatetime(),43 "session_date": s.date.date(), "open": o * raw_factor, "high": h * raw_factor,44 "low": lo * raw_factor, "close": c * raw_factor, "volume": v / raw_factor, "currency": "USD",45 "adjustment_type": "raw", "source_id": "synthetic", "quality_flags": [], "revision_id": 1})46 df = pl.DataFrame(rows).with_columns(pl.col("timestamp_utc").dt.replace_time_zone("UTC"),47 pl.col("quality_flags").cast(pl.List(pl.Utf8)))48 return df, actions495051def to_arrow(df: pl.DataFrame) -> pa.Table:52 return df.to_arrow()53