Source code for orion_finance_sdk_py.stats.panels
"""Build labeled price panels from on-chain history dicts."""
from __future__ import annotations
from collections.abc import Iterable, Mapping, Sequence
from typing import Any
import pandas as pd
def _asset_label(addr: str, names: Mapping[str, str] | None) -> str:
"""Prefer a whitelist name; otherwise a shortened address."""
if names:
for key in (addr, addr.lower()):
if key in names:
return str(names[key])
if len(addr) >= 10:
lowered = addr.lower()
return f"{lowered[:6]}...{lowered[-4:]}"
return addr
def _to_utc_index(index: pd.Index) -> pd.DatetimeIndex:
"""Interpret unix-second labels as UTC timestamps."""
return pd.to_datetime(index, unit="s", utc=True)
[docs]
def from_price_history(
series: Sequence[Mapping[str, Any]],
*,
decimals: int,
names: Mapping[str, str] | None = None,
min_obs: int | None = None,
) -> pd.DataFrame:
"""Turn ``PriceAdapterRegistry.price_history`` dicts into a price panel.
Args:
series: List of ``{"timestamp", "block", "prices"}`` records.
decimals: ``price_adapter_decimals`` used to scale integer prices.
names: Optional address → display name map.
min_obs: Drop columns with fewer than this many non-null observations.
"""
if decimals < 0:
raise ValueError("decimals must be non-negative")
scale = 10**decimals
rows: list[dict[str, Any]] = []
for point in series:
row: dict[str, Any] = {"timestamp": point["timestamp"]}
prices = point.get("prices") or {}
for addr, px in prices.items():
row[_asset_label(str(addr), names)] = float(px) / scale
rows.append(row)
if not rows:
return pd.DataFrame()
frame = (
pd.DataFrame(rows)
.drop_duplicates(subset=["timestamp"], keep="last")
.set_index("timestamp")
.sort_index()
)
frame.index = _to_utc_index(frame.index)
frame = frame.astype(float)
if min_obs is not None:
frame = frame.dropna(axis=1, thresh=min_obs)
return frame
[docs]
def from_share_price_histories(
histories: Mapping[str, Iterable[Mapping[str, Any]]],
*,
min_obs: int | None = None,
) -> pd.DataFrame:
"""Turn vault ``share_price_history`` dicts into a labeled price panel.
Args:
histories: Map of column name → list of
``{"timestamp", "block", "share_price"}`` records.
min_obs: Drop columns with fewer than this many non-null observations.
"""
frames: list[pd.DataFrame] = []
for name, points in histories.items():
rows = [
{"timestamp": point["timestamp"], name: point["share_price"]}
for point in points
]
if not rows:
continue
frame = (
pd.DataFrame(rows)
.drop_duplicates(subset=["timestamp"], keep="last")
.set_index("timestamp")
)
frames.append(frame)
if not frames:
return pd.DataFrame()
prices = pd.concat(frames, axis=1).sort_index()
prices.index = _to_utc_index(prices.index)
prices = prices.astype(float)
if min_obs is not None:
prices = prices.dropna(axis=1, thresh=min_obs)
return prices
[docs]
def normalized_prices(prices: pd.DataFrame) -> pd.DataFrame:
"""Rebase each column to 1.0 at its first valid observation."""
out = prices.copy()
for col in out.columns:
valid = out[col].dropna()
if valid.empty:
continue
base = float(valid.iloc[0])
if base <= 0.0:
continue
out[col] = out[col] / base
return out