libre-alpha 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- libre_alpha-0.1.0/MANIFEST.in +3 -0
- libre_alpha-0.1.0/PKG-INFO +240 -0
- libre_alpha-0.1.0/README.md +208 -0
- libre_alpha-0.1.0/liberal_alpha/__init__.py +27 -0
- libre_alpha-0.1.0/liberal_alpha/client.py +1437 -0
- libre_alpha-0.1.0/liberal_alpha/config.py +5 -0
- libre_alpha-0.1.0/liberal_alpha/crypto.py +235 -0
- libre_alpha-0.1.0/liberal_alpha/exceptions.py +77 -0
- libre_alpha-0.1.0/liberal_alpha/history_upload.py +672 -0
- libre_alpha-0.1.0/liberal_alpha/proto/__init__.py +25 -0
- libre_alpha-0.1.0/liberal_alpha/proto/data_entry_pb2.py +34 -0
- libre_alpha-0.1.0/liberal_alpha/proto/service_pb2.py +55 -0
- libre_alpha-0.1.0/liberal_alpha/proto/service_pb2_grpc.py +189 -0
- libre_alpha-0.1.0/liberal_alpha/proto_utils.py +31 -0
- libre_alpha-0.1.0/liberal_alpha/subscriber.py +335 -0
- libre_alpha-0.1.0/liberal_alpha/utils.py +214 -0
- libre_alpha-0.1.0/libre_alpha.egg-info/PKG-INFO +240 -0
- libre_alpha-0.1.0/libre_alpha.egg-info/SOURCES.txt +22 -0
- libre_alpha-0.1.0/libre_alpha.egg-info/dependency_links.txt +1 -0
- libre_alpha-0.1.0/libre_alpha.egg-info/entry_points.txt +2 -0
- libre_alpha-0.1.0/libre_alpha.egg-info/requires.txt +6 -0
- libre_alpha-0.1.0/libre_alpha.egg-info/top_level.txt +1 -0
- libre_alpha-0.1.0/setup.cfg +4 -0
- libre_alpha-0.1.0/setup.py +42 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: libre-alpha
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Liberal Alpha Python SDK for interacting with gRPC-based backend
|
|
5
|
+
Home-page: https://github.com/capybaralabs-xyz/Liberal_Alpha
|
|
6
|
+
Author: capybaralabs
|
|
7
|
+
Author-email: donny@capybaralabs.xyz
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Requires-Python: >=3.8
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: grpcio>=1.30.0
|
|
18
|
+
Requires-Dist: protobuf>=5.29.0
|
|
19
|
+
Requires-Dist: requests>=2.20.0
|
|
20
|
+
Requires-Dist: coincurve>=13.0.0
|
|
21
|
+
Requires-Dist: pycryptodome>=3.9.0
|
|
22
|
+
Requires-Dist: websockets>=8.0.0
|
|
23
|
+
Dynamic: author
|
|
24
|
+
Dynamic: author-email
|
|
25
|
+
Dynamic: classifier
|
|
26
|
+
Dynamic: description
|
|
27
|
+
Dynamic: description-content-type
|
|
28
|
+
Dynamic: home-page
|
|
29
|
+
Dynamic: requires-dist
|
|
30
|
+
Dynamic: requires-python
|
|
31
|
+
Dynamic: summary
|
|
32
|
+
|
|
33
|
+
# Liberal Alpha Python SDK (Historical HTTP APIs)
|
|
34
|
+
|
|
35
|
+
This SDK provides **historical upload**, **historical download**, and **real-time subscription** APIs (HTTP + WebSocket).
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install libre-alpha
|
|
41
|
+
# Optional: override default API base (default is https://api.librealpha.com)
|
|
42
|
+
export LIBALPHA_API_BASE="https://api.librealpha.com"
|
|
43
|
+
|
|
44
|
+
# Auth (X-API-Key)
|
|
45
|
+
export LIBALPHA_API_KEY="YOUR_API_KEY"
|
|
46
|
+
|
|
47
|
+
# Optional: record encryption key (used to encrypt uploads for encrypted records, and decrypt encrypted payloads)
|
|
48
|
+
export LIBALPHA_RECORD_ENCRYPTION_KEY="YOUR_RECORD_ENCRYPTION_KEY"
|
|
49
|
+
|
|
50
|
+
Initialize Client
|
|
51
|
+
|
|
52
|
+
from liberal_alpha.client import LiberalAlphaClient
|
|
53
|
+
|
|
54
|
+
# api_base defaults to https://api.librealpha.com
|
|
55
|
+
# can be overridden by env LIBALPHA_API_BASE or by passing api_base=...
|
|
56
|
+
client = LiberalAlphaClient(
|
|
57
|
+
api_key="YOUR_API_KEY", # optional if using env LIBALPHA_API_KEY
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
## Historical Upload API (Python) - protobuf stream (/api/entries/history/upload)
|
|
61
|
+
|
|
62
|
+
This is a **historical backfill** uploader that sends protobuf `DataEntry` messages in a length-prefixed stream:
|
|
63
|
+
|
|
64
|
+
- Request body format: `[4-byte big-endian length][DataEntry][4-byte length][DataEntry]...`
|
|
65
|
+
- Backend groups entries **by minute**; this SDK implementation will also **bucket by minute** and send **1 minute per request**.
|
|
66
|
+
- Auth: `X-API-Key` (required).
|
|
67
|
+
- Data is carried per-symbol in `symbol_values`:
|
|
68
|
+
- For non-encrypted records: `symbol_values[i].values.items = [float, ...]`
|
|
69
|
+
- For encrypted records: `symbol_values[i].encrypted_payload = <bytes>` (AES-256-GCM)
|
|
70
|
+
|
|
71
|
+
Client method (recommended):
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
def upload_data(
|
|
75
|
+
record_id: int,
|
|
76
|
+
df: pd.DataFrame,
|
|
77
|
+
encryption_key: str | None = None,
|
|
78
|
+
) -> None:
|
|
79
|
+
...
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- Auth is always taken from `client.api_key` (or env `LIBALPHA_API_KEY` when constructing the client).
|
|
83
|
+
- `encryption_key`: **record encryption key** for encrypting payload when the record is encrypted. If omitted, the SDK uses env
|
|
84
|
+
`LIBALPHA_RECORD_ENCRYPTION_KEY`.
|
|
85
|
+
|
|
86
|
+
Note: the SDK also exposes a module-level `upload_data(...)` with the same arguments; `client.upload_data(...)`
|
|
87
|
+
delegates to it internally. (We only show the signature once here to avoid confusion.)
|
|
88
|
+
|
|
89
|
+
DataFrame format:
|
|
90
|
+
|
|
91
|
+
- Required columns:
|
|
92
|
+
- `symbol` (str): **symbol_id string** (e.g. `"1"`, `"3"`). If you pass non-numeric symbol names, the SDK will
|
|
93
|
+
best-effort map them to symbol_id via `/api/records/user-records` (`target_symbols`).
|
|
94
|
+
- `timestamp` (datetime64 or unix timestamp; will be normalized to UTC and uploaded in **microseconds**)
|
|
95
|
+
- Feature columns (choose ONE approach):
|
|
96
|
+
- Provide a `features` column containing a list of floats (or a string like `"[1.0, nan, 2.0]"`), OR
|
|
97
|
+
- Provide user-defined float columns matching the record schema; the SDK will fetch feature order from `/api/records/{id}`.
|
|
98
|
+
|
|
99
|
+
Upload packing behavior:
|
|
100
|
+
|
|
101
|
+
- 1 request = 1 minute (backend requirement)
|
|
102
|
+
- Within that minute, the SDK **aggregates rows by exact timestamp** into 1 `DataEntry`.
|
|
103
|
+
- Each aggregated `DataEntry` carries multiple symbols in `symbol_values`:
|
|
104
|
+
`[{symbol_id, values.items}, {symbol_id, values.items}, ...]`
|
|
105
|
+
|
|
106
|
+
Example (upload one minute from a previously-downloaded CSV):
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
import pandas as pd
|
|
110
|
+
from liberal_alpha.client import LiberalAlphaClient
|
|
111
|
+
|
|
112
|
+
df = pd.read_csv("history_1h_record12_BTC.csv")
|
|
113
|
+
|
|
114
|
+
# pick the first minute only (backend requires 1 minute per upload)
|
|
115
|
+
first_minute = int(df["timestamp"].iloc[0] // 60_000_000)
|
|
116
|
+
df1 = df[df["timestamp"].apply(lambda x: int(x // 60_000_000) == first_minute)].copy()
|
|
117
|
+
df1 = df1[["symbol", "timestamp", "features"]]
|
|
118
|
+
|
|
119
|
+
client = LiberalAlphaClient(api_key="YOUR_API_KEY")
|
|
120
|
+
client.upload_data(
|
|
121
|
+
record_id=12,
|
|
122
|
+
df=df1,
|
|
123
|
+
# encryption_key="YOUR_RECORD_ENCRYPTION_KEY", # only needed if record is encrypted (or set LIBALPHA_RECORD_ENCRYPTION_KEY)
|
|
124
|
+
)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Historical File Download API (Python) - protobuf (length-prefixed)
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
def download_history_data(
|
|
131
|
+
record_id: int,
|
|
132
|
+
symbols: list[str],
|
|
133
|
+
start: datetime | str | int,
|
|
134
|
+
end: datetime | str | int,
|
|
135
|
+
encryption_key: str | None = None,
|
|
136
|
+
) -> pandas.DataFrame:
|
|
137
|
+
pass
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Parameters:
|
|
141
|
+
|
|
142
|
+
- record_id: the record id to download
|
|
143
|
+
- symbols: list of symbol names (e.g. `["BINANCE_BTCUSDT"]`). If you pass `[]`, the SDK will best-effort fetch all symbols from `/api/records/user-records` (`target_symbols`).
|
|
144
|
+
- start/end: datetime | ISO string | unix timestamp (sec/ms/us)
|
|
145
|
+
- encryption_key: optional, record encryption key for decrypting `symbol_values[].encrypted_payload`
|
|
146
|
+
|
|
147
|
+
Notes:
|
|
148
|
+
|
|
149
|
+
- Backend `/api/entries/download-links` enforces `end-start <= 24h` (microseconds). The SDK automatically
|
|
150
|
+
splits long ranges into multiple 24h windows and merges results locally.
|
|
151
|
+
- Returned timestamps are in microseconds.
|
|
152
|
+
- DataEntry uses `repeated SymbolValues symbol_values`; each row corresponds to one symbol_id.
|
|
153
|
+
- When data is encrypted (`symbol_values[].encrypted_payload` present), pass `encryption_key` to decrypt.
|
|
154
|
+
|
|
155
|
+
Example
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from liberal_alpha.client import LiberalAlphaClient
|
|
159
|
+
import datetime as dt
|
|
160
|
+
|
|
161
|
+
client = LiberalAlphaClient(
|
|
162
|
+
api_key="YOUR_API_KEY",
|
|
163
|
+
api_base="https://api.librealpha.com",
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
start = dt.datetime(2026, 1, 1, 0, 0, 0, tzinfo=dt.timezone.utc)
|
|
167
|
+
end = dt.datetime(2026, 1, 3, 0, 0, 0, tzinfo=dt.timezone.utc) # >24h is OK (SDK will split)
|
|
168
|
+
|
|
169
|
+
df = client.download_history_data(
|
|
170
|
+
record_id=2,
|
|
171
|
+
symbols=["BINANCE_BTCUSDT"],
|
|
172
|
+
start=start,
|
|
173
|
+
end=end,
|
|
174
|
+
encryption_key="YOUR_RECORD_ENCRYPTION_KEY", # for decrypting encrypted data
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
print(df.head())
|
|
178
|
+
print("rows:", len(df))
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Real-time Subscribe API (Python) - WebSocket ws/data
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
from typing import List, Callable
|
|
185
|
+
|
|
186
|
+
def subscribe_data(
|
|
187
|
+
record_id: int,
|
|
188
|
+
*,
|
|
189
|
+
symbols: List[str],
|
|
190
|
+
on_data_fn: Callable[[str, pd.DataFrame], None],
|
|
191
|
+
encryption_key: str | None = None,
|
|
192
|
+
) -> None:
|
|
193
|
+
"""Subscribes to live data for a record into a DataFrame (schema matches publish).
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
record_id: Backend record identifier.
|
|
197
|
+
symbols: Symbols to filter by (required).
|
|
198
|
+
on_data_fn: Callback function invoked upon receiving new data. The
|
|
199
|
+
function takes two arguments: the symbol (str) and a DataFrame
|
|
200
|
+
containing the new data for that symbol.
|
|
201
|
+
encryption_key: Optional, record encryption key for decrypting encrypted_payload.
|
|
202
|
+
"""
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Parameters:
|
|
206
|
+
|
|
207
|
+
- record_id: must be in user's subscriptions (from /api/subscriptions)
|
|
208
|
+
- symbols: **symbol_id strings** to filter by (required). Only rows with symbol_id in this list are passed to on_data_fn.
|
|
209
|
+
- on_data_fn: callback invoked upon receiving new data. Takes (symbol: str, df: DataFrame).
|
|
210
|
+
- encryption_key: optional, record encryption key for decrypting `symbol_values[].encrypted_payload`; required if data is encrypted.
|
|
211
|
+
|
|
212
|
+
Notes:
|
|
213
|
+
|
|
214
|
+
- Connects to ws/data, sends { wallet_address, record_id } on open.
|
|
215
|
+
- If data is encrypted (`symbol_values[].encrypted_payload` has value), pass encryption_key or set LIBALPHA_RECORD_ENCRYPTION_KEY.
|
|
216
|
+
- DataFrame schema matches download_history_data (entry_id, record_id, symbol, features, timestamps).
|
|
217
|
+
- Blocking; run in a daemon thread for long-lived subscription.
|
|
218
|
+
|
|
219
|
+
Example
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
import threading
|
|
223
|
+
import pandas as pd
|
|
224
|
+
from liberal_alpha import LiberalAlphaClient
|
|
225
|
+
|
|
226
|
+
client = LiberalAlphaClient(api_key="YOUR_API_KEY", api_base="https://api.librealpha.com")
|
|
227
|
+
|
|
228
|
+
def on_data_fn(symbol: str, df: pd.DataFrame):
|
|
229
|
+
print(f"[{symbol}] entry_id={df['entry_id'].iloc[0]} features_len={len(df['features'].iloc[0])}")
|
|
230
|
+
|
|
231
|
+
t = threading.Thread(
|
|
232
|
+
target=client.subscribe_data,
|
|
233
|
+
kwargs={"record_id": 2, "symbols": ["1", "3"], "on_data_fn": on_data_fn, "encryption_key": "YOUR_RECORD_ENCRYPTION_KEY"},
|
|
234
|
+
daemon=True,
|
|
235
|
+
)
|
|
236
|
+
t.start()
|
|
237
|
+
```
|
|
238
|
+
print(df.head())
|
|
239
|
+
print("rows:", len(df))
|
|
240
|
+
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# Liberal Alpha Python SDK (Historical HTTP APIs)
|
|
2
|
+
|
|
3
|
+
This SDK provides **historical upload**, **historical download**, and **real-time subscription** APIs (HTTP + WebSocket).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install libre-alpha
|
|
9
|
+
# Optional: override default API base (default is https://api.librealpha.com)
|
|
10
|
+
export LIBALPHA_API_BASE="https://api.librealpha.com"
|
|
11
|
+
|
|
12
|
+
# Auth (X-API-Key)
|
|
13
|
+
export LIBALPHA_API_KEY="YOUR_API_KEY"
|
|
14
|
+
|
|
15
|
+
# Optional: record encryption key (used to encrypt uploads for encrypted records, and decrypt encrypted payloads)
|
|
16
|
+
export LIBALPHA_RECORD_ENCRYPTION_KEY="YOUR_RECORD_ENCRYPTION_KEY"
|
|
17
|
+
|
|
18
|
+
Initialize Client
|
|
19
|
+
|
|
20
|
+
from liberal_alpha.client import LiberalAlphaClient
|
|
21
|
+
|
|
22
|
+
# api_base defaults to https://api.librealpha.com
|
|
23
|
+
# can be overridden by env LIBALPHA_API_BASE or by passing api_base=...
|
|
24
|
+
client = LiberalAlphaClient(
|
|
25
|
+
api_key="YOUR_API_KEY", # optional if using env LIBALPHA_API_KEY
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
## Historical Upload API (Python) - protobuf stream (/api/entries/history/upload)
|
|
29
|
+
|
|
30
|
+
This is a **historical backfill** uploader that sends protobuf `DataEntry` messages in a length-prefixed stream:
|
|
31
|
+
|
|
32
|
+
- Request body format: `[4-byte big-endian length][DataEntry][4-byte length][DataEntry]...`
|
|
33
|
+
- Backend groups entries **by minute**; this SDK implementation will also **bucket by minute** and send **1 minute per request**.
|
|
34
|
+
- Auth: `X-API-Key` (required).
|
|
35
|
+
- Data is carried per-symbol in `symbol_values`:
|
|
36
|
+
- For non-encrypted records: `symbol_values[i].values.items = [float, ...]`
|
|
37
|
+
- For encrypted records: `symbol_values[i].encrypted_payload = <bytes>` (AES-256-GCM)
|
|
38
|
+
|
|
39
|
+
Client method (recommended):
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
def upload_data(
|
|
43
|
+
record_id: int,
|
|
44
|
+
df: pd.DataFrame,
|
|
45
|
+
encryption_key: str | None = None,
|
|
46
|
+
) -> None:
|
|
47
|
+
...
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
- Auth is always taken from `client.api_key` (or env `LIBALPHA_API_KEY` when constructing the client).
|
|
51
|
+
- `encryption_key`: **record encryption key** for encrypting payload when the record is encrypted. If omitted, the SDK uses env
|
|
52
|
+
`LIBALPHA_RECORD_ENCRYPTION_KEY`.
|
|
53
|
+
|
|
54
|
+
Note: the SDK also exposes a module-level `upload_data(...)` with the same arguments; `client.upload_data(...)`
|
|
55
|
+
delegates to it internally. (We only show the signature once here to avoid confusion.)
|
|
56
|
+
|
|
57
|
+
DataFrame format:
|
|
58
|
+
|
|
59
|
+
- Required columns:
|
|
60
|
+
- `symbol` (str): **symbol_id string** (e.g. `"1"`, `"3"`). If you pass non-numeric symbol names, the SDK will
|
|
61
|
+
best-effort map them to symbol_id via `/api/records/user-records` (`target_symbols`).
|
|
62
|
+
- `timestamp` (datetime64 or unix timestamp; will be normalized to UTC and uploaded in **microseconds**)
|
|
63
|
+
- Feature columns (choose ONE approach):
|
|
64
|
+
- Provide a `features` column containing a list of floats (or a string like `"[1.0, nan, 2.0]"`), OR
|
|
65
|
+
- Provide user-defined float columns matching the record schema; the SDK will fetch feature order from `/api/records/{id}`.
|
|
66
|
+
|
|
67
|
+
Upload packing behavior:
|
|
68
|
+
|
|
69
|
+
- 1 request = 1 minute (backend requirement)
|
|
70
|
+
- Within that minute, the SDK **aggregates rows by exact timestamp** into 1 `DataEntry`.
|
|
71
|
+
- Each aggregated `DataEntry` carries multiple symbols in `symbol_values`:
|
|
72
|
+
`[{symbol_id, values.items}, {symbol_id, values.items}, ...]`
|
|
73
|
+
|
|
74
|
+
Example (upload one minute from a previously-downloaded CSV):
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
import pandas as pd
|
|
78
|
+
from liberal_alpha.client import LiberalAlphaClient
|
|
79
|
+
|
|
80
|
+
df = pd.read_csv("history_1h_record12_BTC.csv")
|
|
81
|
+
|
|
82
|
+
# pick the first minute only (backend requires 1 minute per upload)
|
|
83
|
+
first_minute = int(df["timestamp"].iloc[0] // 60_000_000)
|
|
84
|
+
df1 = df[df["timestamp"].apply(lambda x: int(x // 60_000_000) == first_minute)].copy()
|
|
85
|
+
df1 = df1[["symbol", "timestamp", "features"]]
|
|
86
|
+
|
|
87
|
+
client = LiberalAlphaClient(api_key="YOUR_API_KEY")
|
|
88
|
+
client.upload_data(
|
|
89
|
+
record_id=12,
|
|
90
|
+
df=df1,
|
|
91
|
+
# encryption_key="YOUR_RECORD_ENCRYPTION_KEY", # only needed if record is encrypted (or set LIBALPHA_RECORD_ENCRYPTION_KEY)
|
|
92
|
+
)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Historical File Download API (Python) - protobuf (length-prefixed)
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
def download_history_data(
|
|
99
|
+
record_id: int,
|
|
100
|
+
symbols: list[str],
|
|
101
|
+
start: datetime | str | int,
|
|
102
|
+
end: datetime | str | int,
|
|
103
|
+
encryption_key: str | None = None,
|
|
104
|
+
) -> pandas.DataFrame:
|
|
105
|
+
pass
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Parameters:
|
|
109
|
+
|
|
110
|
+
- record_id: the record id to download
|
|
111
|
+
- symbols: list of symbol names (e.g. `["BINANCE_BTCUSDT"]`). If you pass `[]`, the SDK will best-effort fetch all symbols from `/api/records/user-records` (`target_symbols`).
|
|
112
|
+
- start/end: datetime | ISO string | unix timestamp (sec/ms/us)
|
|
113
|
+
- encryption_key: optional, record encryption key for decrypting `symbol_values[].encrypted_payload`
|
|
114
|
+
|
|
115
|
+
Notes:
|
|
116
|
+
|
|
117
|
+
- Backend `/api/entries/download-links` enforces `end-start <= 24h` (microseconds). The SDK automatically
|
|
118
|
+
splits long ranges into multiple 24h windows and merges results locally.
|
|
119
|
+
- Returned timestamps are in microseconds.
|
|
120
|
+
- DataEntry uses `repeated SymbolValues symbol_values`; each row corresponds to one symbol_id.
|
|
121
|
+
- When data is encrypted (`symbol_values[].encrypted_payload` present), pass `encryption_key` to decrypt.
|
|
122
|
+
|
|
123
|
+
Example
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from liberal_alpha.client import LiberalAlphaClient
|
|
127
|
+
import datetime as dt
|
|
128
|
+
|
|
129
|
+
client = LiberalAlphaClient(
|
|
130
|
+
api_key="YOUR_API_KEY",
|
|
131
|
+
api_base="https://api.librealpha.com",
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
start = dt.datetime(2026, 1, 1, 0, 0, 0, tzinfo=dt.timezone.utc)
|
|
135
|
+
end = dt.datetime(2026, 1, 3, 0, 0, 0, tzinfo=dt.timezone.utc) # >24h is OK (SDK will split)
|
|
136
|
+
|
|
137
|
+
df = client.download_history_data(
|
|
138
|
+
record_id=2,
|
|
139
|
+
symbols=["BINANCE_BTCUSDT"],
|
|
140
|
+
start=start,
|
|
141
|
+
end=end,
|
|
142
|
+
encryption_key="YOUR_RECORD_ENCRYPTION_KEY", # for decrypting encrypted data
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
print(df.head())
|
|
146
|
+
print("rows:", len(df))
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Real-time Subscribe API (Python) - WebSocket ws/data
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from typing import List, Callable
|
|
153
|
+
|
|
154
|
+
def subscribe_data(
|
|
155
|
+
record_id: int,
|
|
156
|
+
*,
|
|
157
|
+
symbols: List[str],
|
|
158
|
+
on_data_fn: Callable[[str, pd.DataFrame], None],
|
|
159
|
+
encryption_key: str | None = None,
|
|
160
|
+
) -> None:
|
|
161
|
+
"""Subscribes to live data for a record into a DataFrame (schema matches publish).
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
record_id: Backend record identifier.
|
|
165
|
+
symbols: Symbols to filter by (required).
|
|
166
|
+
on_data_fn: Callback function invoked upon receiving new data. The
|
|
167
|
+
function takes two arguments: the symbol (str) and a DataFrame
|
|
168
|
+
containing the new data for that symbol.
|
|
169
|
+
encryption_key: Optional, record encryption key for decrypting encrypted_payload.
|
|
170
|
+
"""
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Parameters:
|
|
174
|
+
|
|
175
|
+
- record_id: must be in user's subscriptions (from /api/subscriptions)
|
|
176
|
+
- symbols: **symbol_id strings** to filter by (required). Only rows with symbol_id in this list are passed to on_data_fn.
|
|
177
|
+
- on_data_fn: callback invoked upon receiving new data. Takes (symbol: str, df: DataFrame).
|
|
178
|
+
- encryption_key: optional, record encryption key for decrypting `symbol_values[].encrypted_payload`; required if data is encrypted.
|
|
179
|
+
|
|
180
|
+
Notes:
|
|
181
|
+
|
|
182
|
+
- Connects to ws/data, sends { wallet_address, record_id } on open.
|
|
183
|
+
- If data is encrypted (`symbol_values[].encrypted_payload` has value), pass encryption_key or set LIBALPHA_RECORD_ENCRYPTION_KEY.
|
|
184
|
+
- DataFrame schema matches download_history_data (entry_id, record_id, symbol, features, timestamps).
|
|
185
|
+
- Blocking; run in a daemon thread for long-lived subscription.
|
|
186
|
+
|
|
187
|
+
Example
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
import threading
|
|
191
|
+
import pandas as pd
|
|
192
|
+
from liberal_alpha import LiberalAlphaClient
|
|
193
|
+
|
|
194
|
+
client = LiberalAlphaClient(api_key="YOUR_API_KEY", api_base="https://api.librealpha.com")
|
|
195
|
+
|
|
196
|
+
def on_data_fn(symbol: str, df: pd.DataFrame):
|
|
197
|
+
print(f"[{symbol}] entry_id={df['entry_id'].iloc[0]} features_len={len(df['features'].iloc[0])}")
|
|
198
|
+
|
|
199
|
+
t = threading.Thread(
|
|
200
|
+
target=client.subscribe_data,
|
|
201
|
+
kwargs={"record_id": 2, "symbols": ["1", "3"], "on_data_fn": on_data_fn, "encryption_key": "YOUR_RECORD_ENCRYPTION_KEY"},
|
|
202
|
+
daemon=True,
|
|
203
|
+
)
|
|
204
|
+
t.start()
|
|
205
|
+
```
|
|
206
|
+
print(df.head())
|
|
207
|
+
print("rows:", len(df))
|
|
208
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Top-level package exports.
|
|
3
|
+
|
|
4
|
+
Important: keep this module lightweight. Some environments may want to import
|
|
5
|
+
submodules (e.g. `liberal_alpha.proto.data_entry_pb2`) without having the full
|
|
6
|
+
crypto stack installed (dependencies like coincurve/pycryptodome, etc). We therefore lazily import the heavy
|
|
7
|
+
client on attribute access.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
__all__ = ["LiberalAlphaClient", "initialize", "liberal", "upload_data"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def __getattr__(name: str) -> Any: # pragma: no cover
|
|
18
|
+
if name in ("LiberalAlphaClient", "initialize", "liberal"):
|
|
19
|
+
from .client import LiberalAlphaClient, initialize, liberal
|
|
20
|
+
|
|
21
|
+
return {"LiberalAlphaClient": LiberalAlphaClient, "initialize": initialize, "liberal": liberal}[name]
|
|
22
|
+
if name == "upload_data":
|
|
23
|
+
from .history_upload import upload_data
|
|
24
|
+
|
|
25
|
+
return upload_data
|
|
26
|
+
raise AttributeError(name)
|
|
27
|
+
|