Metadata-Version: 2.4
Name: liberal_alpha
Version: 0.1.22
Summary: Liberal Alpha Python SDK for interacting with gRPC-based backend
Home-page: https://github.com/capybaralabs-xyz/Liberal_Alpha
Author: capybaralabs
Author-email: donny@capybaralabs.xyz
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: grpcio>=1.30.0
Requires-Dist: protobuf>=5.29.0
Requires-Dist: requests>=2.20.0
Requires-Dist: coincurve>=13.0.0
Requires-Dist: pycryptodome>=3.9.0
Requires-Dist: eth-account>=0.5.0
Requires-Dist: eth-keys>=0.3.0
Requires-Dist: websockets>=8.0.0
Requires-Dist: msgpack>=1.0.0
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# Liberal Alpha Python SDK (Historical HTTP APIs)

This SDK provides **historical upload**, **historical download**, and **real-time subscription** APIs (HTTP + WebSocket).

## Install

```bash
pip install liberal_alpha
# Optional: override default API base (default is https://api.librealpha.com)
export LIBALPHA_API_BASE="https://api.librealpha.com"

# Upload auth (X-API-Key)
export LIBALPHA_API_KEY="YOUR_API_KEY"

# Download auth (used to obtain JWT via /api/users/auth)
export LIBALPHA_PRIVATE_KEY="0xYOUR_PRIVATE_KEY"

# Optional: record private key (used to encrypt uploads for encrypted records, and decrypt encrypted WS payloads)
export LIBALPHA_RECORD_PRIVATE_KEY="0xYOUR_RECORD_PRIVATE_KEY"

Initialize Client

from liberal_alpha.client import LiberalAlphaClient

# api_base defaults to https://api.librealpha.com
# can be overridden by env LIBALPHA_API_BASE or by passing api_base=...
client = LiberalAlphaClient(
    api_key="YOUR_API_KEY",            # optional if using env LIBALPHA_API_KEY
    private_key="0xYOUR_PRIVATE_KEY",  # optional if using env LIBALPHA_PRIVATE_KEY
)

## Historical Upload API (Python) - protobuf stream (/api/entries/history/upload)

This is a **historical backfill** uploader that sends protobuf `DataEntry` messages in a length-prefixed stream:

- Request body format: `[4-byte big-endian length][DataEntry][4-byte length][DataEntry]...`
- Backend groups entries **by minute**; this SDK implementation will also **bucket by minute** and send **1 minute per request**.
- Auth: `X-API-Key` (required).
- Data is carried per-symbol in `symbol_values`:
  - For non-encrypted records: `symbol_values[i].values.items = [float, ...]`
  - For encrypted records: `symbol_values[i].encrypted_payload = <bytes>` (AES-256-GCM)

Client method (recommended):

```python
def upload_data(
    record_id: int,
    df: pd.DataFrame,
    api_key: str | None = None,
    private_key: str | None = None,
) -> None:
    ...
```

- `api_key`: optional override; falls back to `client.api_key`, then env `LIBALPHA_API_KEY`.
- `private_key`: **record private key** for encrypting payload when the record is encrypted. If omitted, the SDK uses env
  `LIBALPHA_RECORD_PRIVATE_KEY` (if set). This is different from `LIBALPHA_PRIVATE_KEY` (user auth key for JWT).

Note: the SDK also exposes a module-level `upload_data(...)` with the same arguments; `client.upload_data(...)`
delegates to it internally. (We only show the signature once here to avoid confusion.)

DataFrame format:

- Required columns:
  - `symbol` (str): **symbol_id string** (e.g. `"1"`, `"3"`). If you pass non-numeric symbol names, the SDK will
    best-effort map them to symbol_id via `/api/records/user-records` (`target_symbols`).
  - `timestamp` (datetime64 or unix timestamp; will be normalized to UTC and uploaded in **microseconds**)
- Feature columns (choose ONE approach):
  - Provide a `features` column containing a list of floats (or a string like `"[1.0, nan, 2.0]"`), OR
  - Provide user-defined float columns matching the record schema; the SDK will fetch feature order from `/api/records/{id}`.

Upload packing behavior:

- 1 request = 1 minute (backend requirement)
- Within that minute, the SDK **aggregates rows by exact timestamp** into 1 `DataEntry`.
- Each aggregated `DataEntry` carries multiple symbols in `symbol_values`:
  `[{symbol_id, values.items}, {symbol_id, values.items}, ...]`

Example (upload one minute from a previously-downloaded CSV):

```python
import pandas as pd
from liberal_alpha.client import LiberalAlphaClient

df = pd.read_csv("history_1h_record12_BTC.csv")

# pick the first minute only (backend requires 1 minute per upload)
first_minute = int(df["timestamp"].iloc[0] // 60_000_000)
df1 = df[df["timestamp"].apply(lambda x: int(x // 60_000_000) == first_minute)].copy()
df1 = df1[["symbol", "timestamp", "features"]]

client = LiberalAlphaClient(api_key="YOUR_API_KEY")
client.upload_data(
    record_id=12,
    df=df1,
    # api_key="YOUR_API_KEY",  # optional override; otherwise uses client.api_key (or env LIBALPHA_API_KEY)
    # private_key="YOUR_RECORD_PRIVATE_KEY",  # only needed if record is encrypted (or set LIBALPHA_RECORD_PRIVATE_KEY)
)
```

Historical Download API (Python)

def download_data(
    record_id: int,
    symbols: list[str],
    dates: list[int],
    tz_info: datetime.tzinfo | str = "Asia/Singapore"
) -> pandas.DataFrame:
    pass

## Historical File Download API (Python) - protobuf (length-prefixed)

```python
def download_history_data(
    record_id: int,
    symbol: str,
    start: datetime | str | int,
    end: datetime | str | int,
    private_key: str | None = None,
) -> pandas.DataFrame:
    pass
```

Parameters:

- record_id: the record id to download
- symbol: **symbol_id string** (e.g. `"1"`, `"3"`)
- start/end: datetime | ISO string | unix timestamp (sec/ms/us)
- private_key: optional, record private key for decrypting `symbol_values[].encrypted_payload`

Notes:

- Backend `/api/entries/download-links` enforces `end-start <= 24h` (microseconds). The SDK automatically
  splits long ranges into multiple 24h windows and merges results locally.
- Returned timestamps are in microseconds.
- DataEntry uses `repeated SymbolValues symbol_values`; each row corresponds to one symbol_id.
- When data is encrypted (`symbol_values[].encrypted_payload` present), pass `private_key` to decrypt.

Example

```python
from liberal_alpha.client import LiberalAlphaClient
import datetime as dt

client = LiberalAlphaClient(
    private_key="0xYOUR_PRIVATE_KEY",  # or api_key="YOUR_API_KEY"
    api_base="https://api.librealpha.com",
)

start = dt.datetime(2026, 1, 1, 0, 0, 0, tzinfo=dt.timezone.utc)
end   = dt.datetime(2026, 1, 3, 0, 0, 0, tzinfo=dt.timezone.utc)  # >24h is OK (SDK will split)

df = client.download_history_data(
    record_id=2,
    symbol="1",
    start=start,
    end=end,
    private_key="YOUR_RECORD_PRIVATE_KEY",  # for decrypting encrypted data
)

print(df.head())
print("rows:", len(df))
```

## Real-time Subscribe API (Python) - WebSocket ws/data

```python
from typing import List, Callable

def subscribe_data(
    record_id: int,
    *,
    symbols: List[str],
    on_data_fn: Callable[[str, pd.DataFrame], None],
    private_key: str | None = None,
) -> None:
    """Subscribes to live data for a record into a DataFrame (schema matches publish).

    Args:
        record_id: Backend record identifier.
        symbols: Symbols to filter by (required).
        on_data_fn: Callback function invoked upon receiving new data. The
            function takes two arguments: the symbol (str) and a DataFrame
            containing the new data for that symbol.
        private_key: Optional, record private key for decrypting encrypted_payload.
    """
```

Parameters:

- record_id: must be in user's subscriptions (from /api/subscriptions)
- symbols: **symbol_id strings** to filter by (required). Only rows with symbol_id in this list are passed to on_data_fn.
- on_data_fn: callback invoked upon receiving new data. Takes (symbol: str, df: DataFrame).
- private_key: optional, record private key for decrypting `symbol_values[].encrypted_payload`; required if data is encrypted.

Notes:

- Connects to ws/data, sends { wallet_address, record_id } on open.
- If data is encrypted (`symbol_values[].encrypted_payload` has value), pass private_key or set LIBALPHA_RECORD_PRIVATE_KEY.
- DataFrame schema matches download_history_data (entry_id, record_id, symbol, features, timestamps).
- Blocking; run in a daemon thread for long-lived subscription.

Example

```python
import threading
import pandas as pd
from liberal_alpha import LiberalAlphaClient

client = LiberalAlphaClient(api_key="YOUR_API_KEY", api_base="https://api.librealpha.com")

def on_data_fn(symbol: str, df: pd.DataFrame):
    print(f"[{symbol}] entry_id={df['entry_id'].iloc[0]} features_len={len(df['features'].iloc[0])}")

t = threading.Thread(
    target=client.subscribe_data,
    kwargs={"record_id": 2, "symbols": ["1", "3"], "on_data_fn": on_data_fn, "private_key": "YOUR_RECORD_KEY"},
    daemon=True,
)
t.start()
```

## Historical Download API (download_data) Parameters

-record_id: the record id to download

-symbols: list of symbols, e.g. ["BTCUSDT", "ETHUSDT"]

If you pass [], the SDK will automatically fetch all symbols (if supported by backend).

-dates: list of local dates in YYYYMMDD format, e.g. [20251214, 20251215]

If you pass [], no date filter is applied.

-tz_info: controls how local_date is computed

"Asia/Singapore" (IANA tz string)

numeric offsets like 8, -4, or strings like "+8", "-4"

8 means UTC+8 (SGT/HKT)

-4 means UTC-4 (NYC during DST)

Example

from liberal_alpha.client import LiberalAlphaClient

client = LiberalAlphaClient(
    private_key="0xYOUR_PRIVATE_KEY",
    # api_base defaults to https://api.librealpha.com
)

df = client.download_data(
    record_id=24,
    symbols=[],              # empty => auto fetch all symbols
    dates=[],                # empty => no date filter
    tz_info="Asia/Singapore" # or tz_info=8
)

print(df.head())
print("rows:", len(df))

