from typing import Any, Tuple, Union

import numpy as np
import pandas as pd

class Bar:
    time: int
    open: float
    high: float
    low: float
    close: float
    volume: float
    bought_volume: float
    volume_quote: float
    bought_volume_quote: float
    trade_count: int
    def __init__(
        self,
        time,
        open,
        high,
        low,
        close,
        volume,
        bought_volume=0.0,
        volume_quote=0.0,
        bought_volume_quote=0.0,
        trade_count=0,
    ): ...
    def update(
        self,
        price: float,
        volume: float,
        bought_volume: float = 0,
        volume_quote: float = 0,
        bought_volume_quote: float = 0,
        trade_count: int = 0,
    ) -> "Bar": ...
    def to_dict(self, skip_time: bool = False) -> dict: ...

class Quote:
    time: int
    bid: float
    ask: float
    bid_size: float
    ask_size: float

    def __init__(self, time, bid, ask, bid_size, ask_size): ...
    def mid_price(self) -> float: ...

class Trade:
    time: int
    price: float
    size: float
    side: int
    trade_id: int
    def __init__(self, time, price, size, side=0, trade_id=0): ...

class OrderBook:
    time: int
    top_bid: float
    top_ask: float
    tick_size: float
    bids: np.ndarray
    asks: np.ndarray

    def __init__(self, time, top_bid, top_ask, tick_size, bids, asks): ...
    def to_quote(self) -> Quote: ...
    def mid_price(self) -> float: ...

class TradeArray:
    trades: np.ndarray  # structured array with time, price, size, side fields
    size: int
    time: int
    total_size: float
    buy_size: float
    sell_size: float
    min_buy_price: float
    max_buy_price: float
    min_sell_price: float
    max_sell_price: float

    def __init__(self, data: np.ndarray | None = None, initial_capacity: int = 1000) -> None: ...
    def add(self, time: int, price: float, size: float, side: int) -> None: ...
    def traded_range_from(self, time: int) -> tuple: ...
    def clear(self) -> None: ...
    def __len__(self) -> int: ...
    def __getitem__(self, idx: int) -> Trade: ...  # Only allow single trade access

class Locator:
    def __getitem__(self, idx): ...
    def find(self, t: str) -> Tuple[np.datetime64, Any]: ...

class Indexed:
    def __getitem__(self, idx): ...
    def lookup_idx(self, value, method: str) -> int: ...

class TimeSeries:
    name: str
    loc: Locator
    timeframe: int
    max_series_length: int
    times: Indexed
    values: Indexed
    def __init__(self, name, timeframe, max_series_length=np.inf, process_every_update=True) -> None: ...
    def __getitem__(self, idx): ...
    def __len__(self) -> int: ...
    def __add__(self, other: Union[TimeSeries, float, int]) -> "TimeSeries": ...
    def __sub__(self, other: Union[TimeSeries, float, int]) -> "TimeSeries": ...
    def __mul__(self, other: Union[TimeSeries, float, int]) -> "TimeSeries": ...
    def __truediv__(self, other: Union[TimeSeries, float, int]) -> "TimeSeries": ...
    def __lt__(self, other: Union[TimeSeries, float, int]) -> "TimeSeries": ...
    def __le__(self, other: Union[TimeSeries, float, int]) -> "TimeSeries": ...
    def __gt__(self, other: Union[TimeSeries, float, int]) -> "TimeSeries": ...
    def __ge__(self, other: Union[TimeSeries, float, int]) -> "TimeSeries": ...
    def __eq__(self, other: Union[TimeSeries, float, int]) -> "TimeSeries": ...
    def __ne__(self, other: Union[TimeSeries, float, int]) -> "TimeSeries": ...
    def __neg__(self) -> "TimeSeries": ...
    def __abs__(self) -> "TimeSeries": ...
    def update(self, time: int, value: float) -> bool: ...
    def copy(self, start: int, stop: int) -> "TimeSeries": ...
    def shift(self, period: int) -> "TimeSeries": ...
    def diff(self, period: int = 1) -> "TimeSeries": ...
    def resample(self, timeframe: str, max_series_length=np.inf, process_every_update=True) -> "TimeSeries": ...
    def get_indicators(self) -> dict: ...
    def plot(self, *args, **kwargs): ...
    def to_records(self) -> dict: ...
    def to_series(self, length: int | None = None) -> pd.Series: ...
    def pd(self, length: int | None = None) -> pd.Series: ...

class OHLCV(TimeSeries):
    open: TimeSeries
    high: TimeSeries
    low: TimeSeries
    close: TimeSeries
    volume: TimeSeries
    bvolume: TimeSeries
    volume_quote: TimeSeries
    bvolume_quote: TimeSeries
    trade_count: TimeSeries

    def __init__(self, name, timeframe, max_series_length: int | float = np.inf) -> None: ...
    def __len__(self) -> int: ...
    def update(
        self,
        time: int,
        price: float,
        volume: float = 0.0,
        bvolume: float = 0.0,
        volume_quote: float = 0.0,
        bought_volume_quote: float = 0.0,
        trade_count: int = 0,
    ) -> bool: ...
    def update_by_bar(
        self,
        time: int,
        open: float,
        high: float,
        low: float,
        close: float,
        vol_incr: float = 0.0,
        b_vol_incr: float = 0.0,
        volume_quote_incr: float = 0.0,
        bought_volume_quote_incr: float = 0.0,
        trade_count_incr: float = 0.0,
        is_incremental: bool = True,
    ) -> bool: ...
    def update_by_bars(self, bars: list[Bar]) -> bool: ...
    def to_records(self) -> dict: ...
    def to_series(self, length: int | None = None) -> pd.DataFrame: ...
    def pd(self, length: int | None = None) -> pd.DataFrame: ...
    def resample(self, timeframe: str, max_series_length=np.inf) -> "OHLCV": ...
    @staticmethod
    def from_dataframe(df_p: pd.DataFrame, name: str = "ohlc") -> "OHLCV": ...

class Indicator(TimeSeries):
    name: str
    series: TimeSeries

    def update(self, time: int, value, new_item_started: bool) -> object: ...
    @classmethod
    def wrap(cls, series: TimeSeries, *args, **kwargs) -> "Indicator": ...
    @property
    def is_initial_recalculate(self) -> bool: ...

class IndicatorOHLC(Indicator):
    series: OHLCV
    def _copy_internal_series(self, start: int, stop: int, *origins): ...

class GenericSeries(TimeSeries):
    """
    Generic series for storing any Timestamped data type (Quote, Trade, FundingRate, etc.).
    Unlike OHLCV which decomposes Bar into component series, this stores complete objects.
    """
    def __init__(self, name: str, timeframe, max_series_length: int | float = ...): ...
    def update(self, timestamped_obj: Any) -> bool:
        """
        Update series with a Timestamped object.

        Args:
            timestamped_obj: Any object with a .time attribute (Quote, Trade, FundingRate, etc.)

        Returns:
            True if a new item was started, False if existing item was updated
        """
        ...

class ColumnarSeries(GenericSeries):
    """
    Series that decomposes objects into column TimeSeries (like OHLCV but with dynamic columns).

    When update() is called with an object:
    1. Store the complete object (like GenericSeries)
    2. Also update child TimeSeries for each column

    Use attribute access to get child TimeSeries by column name.

    Example:
        ser = ColumnarSeries("BTCUSDT", "1h", ["buy_volume", "sell_volume", "ratio"])
        ser.update(TimestampedDict(time=t, data={"buy_volume": 100, "sell_volume": 80, "ratio": 1.25}))

        # Access column as TimeSeries
        ratio_ts = ser.ratio  # Returns TimeSeries
        sma = ta.Sma.wrap(ser.ratio, 20)  # Attach indicator to column
    """
    _column_names: list[str]
    _columns: dict[str, TimeSeries]

    def __init__(self, name: str, timeframe, columns: list[str], max_series_length: int | float = ...) -> None: ...
    def __getattr__(self, name: str) -> TimeSeries: ...
    def __getitem__(self, idx: int | str) -> Any: ...
    def get_indicators(self) -> dict[str, Any]: ...

    @property
    def columns(self) -> dict[str, TimeSeries]: ...
    @property
    def column_names(self) -> list[str]: ...


class BundledSeries(TimeSeries):
    """
    Virtual series that bundles fields from multiple source TimeSeries.
    Subscribes to ALL source series - triggers indicators when ANY source updates.

    When any source updates, gathers latest values from all sources into a dict
    and triggers attached indicators with this bundled value.

    Example:
        # Create source series
        vtwap_series = ColumnarSeries("vtwap", "1m", ["twap", "vwap"])
        ohlcv_series = OHLCV("ohlcv", "1m")

        # Bundle specific fields from multiple sources
        bundle = BundledSeries("vwap_close", "1m", {
            "vwap": vtwap_series.vwap,
            "close": ohlcv_series.close
        })

        # Attach indicator that receives dict with all fields
        class VwapSpread(IndicatorGeneric):
            def calculate(self, time, values, new_item_started):
                return (values["close"] - values["vwap"]) / values["close"]

        spread = VwapSpread("spread", bundle)
    """
    _fields: dict[str, TimeSeries]
    _field_names: list[str]

    def __init__(self, name: str, timeframe, fields: dict[str, TimeSeries], max_series_length: int | float = ...) -> None: ...
    def update(self, time: int, value: Any, new_item_started: bool) -> Any: ...
    def to_series(self, length: int | None = None) -> pd.DataFrame: ...

    @property
    def fields(self) -> dict[str, TimeSeries]: ...
    @property
    def field_names(self) -> list[str]: ...


class IndicatorGeneric(Indicator):
    """
    Base class for indicators that work with GenericSeries containing arbitrary Timestamped objects.
    """
    def calculate(self, time: int, value: Any, new_item_started: bool) -> Any:
        """
        Calculate indicator value based on the timestamped object.

        Args:
            time: Timestamp in nanoseconds
            value: The Timestamped object (Quote, Trade, FundingRate, etc.)
            new_item_started: True if this is a new bar, False if updating existing

        Returns:
            Calculated indicator value
        """
        ...

def time_as_nsec(time: Any) -> int: ...

class RollingSum:
    is_init_stage: bool
    def __init__(self, period: int) -> None: ...
    def update(self, value: float, new_item_started: bool) -> float: ...

def compare(series0: TimeSeries, series1: TimeSeries) -> "Indicator":
    """
    Compare two time series and return an indicator that shows the relationship between them.
    Returns +1 when series0 > series1, -1 when series0 < series1, and 0 when equal.
    Both series must have the same timeframe.
    """
    ...

def lag(series: TimeSeries, period: int) -> "Indicator":
    """
    Returns a new time series shifted by the specified period.
    Only positive shift (from past) period is allowed.
    """
    ...

class SeriesCachedValue: ...
