luxorasap 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl
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.
- luxorasap/__init__.py +1 -1
- luxorasap/btgapi/__init__.py +16 -0
- luxorasap/btgapi/auth.py +58 -0
- luxorasap/btgapi/reports.py +189 -0
- luxorasap/btgapi/trades.py +181 -0
- luxorasap/ingest/cloud/__init__.py +2 -2
- luxorasap/utils/dataframe/__init__.py +3 -1
- luxorasap/utils/dataframe/reader.py +19 -0
- luxorasap/utils/dataframe/transforms.py +0 -2
- luxorasap/utils/storage/blob.py +7 -3
- luxorasap-0.1.2.dist-info/METADATA +241 -0
- luxorasap-0.1.2.dist-info/RECORD +21 -0
- luxorasap-0.1.0.dist-info/METADATA +0 -78
- luxorasap-0.1.0.dist-info/RECORD +0 -16
- {luxorasap-0.1.0.dist-info → luxorasap-0.1.2.dist-info}/WHEEL +0 -0
- {luxorasap-0.1.0.dist-info → luxorasap-0.1.2.dist-info}/entry_points.txt +0 -0
- {luxorasap-0.1.0.dist-info → luxorasap-0.1.2.dist-info}/top_level.txt +0 -0
luxorasap/__init__.py
CHANGED
|
@@ -13,7 +13,7 @@ from types import ModuleType
|
|
|
13
13
|
try:
|
|
14
14
|
__version__: str = metadata.version(__name__)
|
|
15
15
|
except metadata.PackageNotFoundError: # editable install
|
|
16
|
-
__version__ = "0.1.
|
|
16
|
+
__version__ = "0.1.2"
|
|
17
17
|
|
|
18
18
|
# ─── Lazy loader ─────────────────────────────────────────────────
|
|
19
19
|
def __getattr__(name: str) -> ModuleType:
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Wrapper para as APIs do BTG Pactual."""
|
|
2
|
+
|
|
3
|
+
from .auth import get_access_token, BTGApiError
|
|
4
|
+
from .reports import request_portfolio, await_report_ticket_result, process_zip_to_dfs, request_investors_transactions_report
|
|
5
|
+
from .trades import submit_offshore_equity_trades, await_transaction_ticket_result
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"BTGApiError",
|
|
9
|
+
"get_access_token",
|
|
10
|
+
"request_portfolio",
|
|
11
|
+
"await_report_ticket_result",
|
|
12
|
+
"submit_offshore_equity_trades",
|
|
13
|
+
"await_transaction_ticket_result",
|
|
14
|
+
"process_zip_to_dfs",
|
|
15
|
+
"request_investors_transactions_report"
|
|
16
|
+
]
|
luxorasap/btgapi/auth.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import requests
|
|
3
|
+
from dotenv import load_dotenv
|
|
4
|
+
from loguru import logger
|
|
5
|
+
|
|
6
|
+
__all__ = ["BTGApiError", "get_access_token"]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BTGApiError(Exception):
|
|
10
|
+
"""Erro genérico da API do BTG."""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_access_token(*, client_id=None, client_secret=None, test_env: bool = True,
|
|
14
|
+
timeout: int = 20) -> str:
|
|
15
|
+
"""Obtém JWT válido por ~1 h para autenticação nas APIs BTG.
|
|
16
|
+
Args:
|
|
17
|
+
client_id: ID do cliente (opcional, lê de env var se None).
|
|
18
|
+
client_secret: Segredo do cliente (opcional, lê de env var se None).
|
|
19
|
+
test_env: Ambiente de teste (True) ou produção (False).
|
|
20
|
+
timeout: Timeout da requisição em segundos.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Token de acesso.
|
|
24
|
+
|
|
25
|
+
Raises:
|
|
26
|
+
BTGApiError: Se as credenciais não estiverem disponíveis ou a requisição falhar.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
if not client_id or not client_secret:
|
|
30
|
+
load_dotenv()
|
|
31
|
+
client_id = os.getenv("BTG_CLIENT_ID")
|
|
32
|
+
client_secret = os.getenv("BTG_CLIENT_SECRET")
|
|
33
|
+
if not client_id or not client_secret:
|
|
34
|
+
raise BTGApiError("BTG_CLIENT_ID ou BTG_CLIENT_SECRET não definidos no ambiente")
|
|
35
|
+
|
|
36
|
+
url = (
|
|
37
|
+
"https://funds-uat.btgpactual.com/connect/token"
|
|
38
|
+
if test_env
|
|
39
|
+
else "https://funds.btgpactual.com/connect/token"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
resp = requests.post(
|
|
43
|
+
url,
|
|
44
|
+
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
|
45
|
+
data={
|
|
46
|
+
"grant_type": "client_credentials",
|
|
47
|
+
"client_id": client_id,
|
|
48
|
+
"client_secret": client_secret,
|
|
49
|
+
},
|
|
50
|
+
timeout=timeout,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
if resp.ok:
|
|
54
|
+
token = resp.json().get("access_token")
|
|
55
|
+
len_token = len(token) if token else None
|
|
56
|
+
logger.debug(f"Token BTG obtido (len={len_token})")
|
|
57
|
+
return token or ""
|
|
58
|
+
raise BTGApiError(f"Falha ao autenticar: HTTP {resp.status_code} – {resp.text}")
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import datetime as dt
|
|
2
|
+
import io
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
import zipfile
|
|
6
|
+
from typing import Optional, Dict
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import requests
|
|
9
|
+
from loguru import logger
|
|
10
|
+
|
|
11
|
+
from .auth import BTGApiError
|
|
12
|
+
from luxorasap.utils.dataframe import read_bytes
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"request_portfolio",
|
|
16
|
+
"check_report_ticket",
|
|
17
|
+
"await_report_ticket_result",
|
|
18
|
+
"process_zip_to_dfs",
|
|
19
|
+
"request_investors_transactions_report"
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
_REPORT_ENDPOINT = "https://funds.btgpactual.com/reports/Portfolio"
|
|
23
|
+
_TICKET_ENDPOINT = "https://funds.btgpactual.com/reports/Ticket"
|
|
24
|
+
_INVESTOR_TX_ENDPOINT = (
|
|
25
|
+
"https://funds.btgpactual.com/reports/RTA/InvestorTransactionsFileReport"
|
|
26
|
+
)
|
|
27
|
+
_REPORT_TYPES = {"excel": 10, "xml5": 81, "pdf": 2}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def request_portfolio(token: str, fund_name: str, start_date: dt.date, end_date: dt.date,
|
|
31
|
+
format: str = "excel") -> str:
|
|
32
|
+
"""Envia requisição de carteira; retorna *ticket*.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
token: Token de autenticação.
|
|
36
|
+
fund_name: Nome do fundo.
|
|
37
|
+
start_date: Data de início.
|
|
38
|
+
end_date: Data de fim.
|
|
39
|
+
format: Formato do relatório ("excel", "xml5", "pdf").
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Ticket da requisição.
|
|
43
|
+
"""
|
|
44
|
+
body = {
|
|
45
|
+
"contract": {
|
|
46
|
+
"startDate": f"{start_date}T00:00:00Z",
|
|
47
|
+
"endDate": f"{end_date}T00:00:00Z",
|
|
48
|
+
"typeReport": _REPORT_TYPES[format],
|
|
49
|
+
"fundName": fund_name,
|
|
50
|
+
},
|
|
51
|
+
"pageSize": 100,
|
|
52
|
+
"webhookEndpoint": "string",
|
|
53
|
+
}
|
|
54
|
+
r = requests.post(
|
|
55
|
+
_REPORT_ENDPOINT,
|
|
56
|
+
headers={"X-SecureConnect-Token": token, "Content-Type": "application/json"},
|
|
57
|
+
json=body,
|
|
58
|
+
timeout=30,
|
|
59
|
+
)
|
|
60
|
+
if r.ok:
|
|
61
|
+
return r.json()["ticket"]
|
|
62
|
+
raise BTGApiError(f"Erro ao solicitar relatório: {r.status_code} – {r.text}")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _download_url(download_url: str) -> bytes:
|
|
66
|
+
r = requests.get(download_url, timeout=60)
|
|
67
|
+
if r.ok:
|
|
68
|
+
return r.content
|
|
69
|
+
raise BTGApiError(f"Falha no download: {r.status_code} – {r.text}")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def check_report_ticket(token: str, ticket: str, *, page: Optional[int] = None) -> bytes:
|
|
73
|
+
"""Consulta único ticket; devolve bytes se pronto, lança BTGApiError caso contrário."""
|
|
74
|
+
|
|
75
|
+
params = {"ticketId": ticket}
|
|
76
|
+
if page is not None:
|
|
77
|
+
params["pageNumber"] = str(page)
|
|
78
|
+
|
|
79
|
+
r = requests.get(
|
|
80
|
+
_TICKET_ENDPOINT,
|
|
81
|
+
params=params,
|
|
82
|
+
headers={"X-SecureConnect-Token": token},
|
|
83
|
+
timeout=30,
|
|
84
|
+
)
|
|
85
|
+
# 1. Se resposta é ZIP direto → retornamos conteúdo
|
|
86
|
+
try:
|
|
87
|
+
payload = r.json()
|
|
88
|
+
except json.JSONDecodeError:
|
|
89
|
+
if r.ok:
|
|
90
|
+
return r.content
|
|
91
|
+
raise BTGApiError(f"Resposta inesperada: {r.status_code} – {r.text}")
|
|
92
|
+
|
|
93
|
+
# 2. Caso contrário tenta decodificar JSON
|
|
94
|
+
|
|
95
|
+
result = payload.get("result")
|
|
96
|
+
|
|
97
|
+
if result == "Processando" or result == 'Aguardando processamento':
|
|
98
|
+
raise BTGApiError("Processando")
|
|
99
|
+
|
|
100
|
+
# 3. Quando pronto, result é JSON string com UrlDownload
|
|
101
|
+
if isinstance(result, str):
|
|
102
|
+
try:
|
|
103
|
+
info: Dict[str, str] = json.loads(result)
|
|
104
|
+
url = info["UrlDownload"]
|
|
105
|
+
return _download_url(url)
|
|
106
|
+
except Exception as exc:
|
|
107
|
+
raise BTGApiError(f"Falha ao interpretar resultado: {exc}") from exc
|
|
108
|
+
|
|
109
|
+
raise BTGApiError("Formato de resposta desconhecido")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def await_report_ticket_result(token: str, ticket: str, *, attempts: int = 10,
|
|
113
|
+
interval: int = 15) -> bytes:
|
|
114
|
+
"""Espera até que o relatório esteja pronto e devolve conteúdo binário.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
token: Token de autenticação.
|
|
118
|
+
ticket: Ticket da requisição.
|
|
119
|
+
attempts: Número de tentativas.
|
|
120
|
+
interval: Intervalo entre tentativas em segundos.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Conteúdo binário do relatório (arquivo ZIP).
|
|
124
|
+
|
|
125
|
+
Raises:
|
|
126
|
+
BTGApiError: Se o relatório não ficar pronto ou falhar.
|
|
127
|
+
"""
|
|
128
|
+
for i in range(attempts):
|
|
129
|
+
try:
|
|
130
|
+
return check_report_ticket(token, ticket)
|
|
131
|
+
except BTGApiError as err:
|
|
132
|
+
if "Processando" in str(err):
|
|
133
|
+
logger.debug(f"Ticket {ticket} pendente ({i+1}/{attempts})")
|
|
134
|
+
time.sleep(interval)
|
|
135
|
+
continue
|
|
136
|
+
raise
|
|
137
|
+
raise BTGApiError("Relatório não ficou pronto no tempo limite")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def process_zip_to_dfs(zip_bytes: bytes) -> dict[str, pd.DataFrame]:
|
|
141
|
+
"""Extrai todos os arquivos do ZIP e devolve DataFrames por nome."""
|
|
142
|
+
out: dict[str, pd.DataFrame] = {}
|
|
143
|
+
with zipfile.ZipFile(io.BytesIO(zip_bytes)) as zf:
|
|
144
|
+
for name in zf.namelist():
|
|
145
|
+
if name.endswith("/"):
|
|
146
|
+
continue
|
|
147
|
+
out[name] = read_bytes(zf.read(name), filename=name)
|
|
148
|
+
|
|
149
|
+
return out
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def request_investors_transactions_report( token: str, query_date: dt.date, *,
|
|
153
|
+
distributors: list[str] | None = None, fund_names: list[str] | None = None,
|
|
154
|
+
consolidate_by_account: bool = True, page_size: int = 100) -> str:
|
|
155
|
+
"""
|
|
156
|
+
Gera um ticket para o relatório de transações de cotistas (RTA).
|
|
157
|
+
Args:
|
|
158
|
+
token: Token de autenticação.
|
|
159
|
+
query_date: Data da consulta.
|
|
160
|
+
distributors: Lista de nomes de distribuidores (opcional).
|
|
161
|
+
fund_names: Lista de nomes de fundos (opcional).
|
|
162
|
+
consolidate_by_account: Consolidar por conta (default True).
|
|
163
|
+
page_size: Tamanho da página (default 100).
|
|
164
|
+
|
|
165
|
+
Retorna *ticket* (string) a ser usado em `await_report_ticket_result`.
|
|
166
|
+
"""
|
|
167
|
+
body = {
|
|
168
|
+
"contract": {
|
|
169
|
+
"distributors": distributors or [],
|
|
170
|
+
"queryDate": f"{query_date.isoformat()}T00:00:00Z",
|
|
171
|
+
"accountNumber": "",
|
|
172
|
+
"consolidateByAccount": str(consolidate_by_account).lower(),
|
|
173
|
+
"fundNames": fund_names or [],
|
|
174
|
+
},
|
|
175
|
+
"pageSize": page_size,
|
|
176
|
+
"webhookEndpoint": "string",
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
r = requests.post(
|
|
180
|
+
_INVESTOR_TX_ENDPOINT,
|
|
181
|
+
headers={"X-SecureConnect-Token": token, "Content-Type": "application/json"},
|
|
182
|
+
json=body,
|
|
183
|
+
timeout=30,
|
|
184
|
+
)
|
|
185
|
+
if r.ok:
|
|
186
|
+
return r.json()["ticket"]
|
|
187
|
+
raise BTGApiError(
|
|
188
|
+
f"Erro InvestorTransactionsFileReport: {r.status_code} – {r.text}"
|
|
189
|
+
)
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from typing import List, Dict
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import requests
|
|
6
|
+
from loguru import logger
|
|
7
|
+
|
|
8
|
+
from .auth import BTGApiError
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"submit_offshore_equity_trades",
|
|
12
|
+
"get_submitted_transactions",
|
|
13
|
+
"await_transaction_ticket_result",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
_EP_SUBMIT_TEST = "https://funds-uat.btgpactual.com/offshore/TradeOffShore/Equity"
|
|
17
|
+
_EP_SUBMIT_PROD = "https://funds.btgpactual.com/offshore/TradeOffShore/Equity"
|
|
18
|
+
_EP_TICKET_TEST = "https://funds-uat.btgpactual.com/offshore/Ticket"
|
|
19
|
+
_EP_TICKET_PROD = "https://funds.btgpactual.com/offshore/Ticket"
|
|
20
|
+
|
|
21
|
+
_MARKET_IDS = {
|
|
22
|
+
"equity": 20,
|
|
23
|
+
"future": 22,
|
|
24
|
+
"bonds": 24,
|
|
25
|
+
"repo": 28,
|
|
26
|
+
"portfolio_swap": 29,
|
|
27
|
+
"interest_rate_swap": 30,
|
|
28
|
+
"performance_swap": 31,
|
|
29
|
+
"variance_swap": 32,
|
|
30
|
+
"equity_option": 33,
|
|
31
|
+
"future_option": 34,
|
|
32
|
+
"fx_option_vanilla": 35,
|
|
33
|
+
"fx_option_barrier": 36,
|
|
34
|
+
"fx": 25,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def submit_offshore_equity_trades(token: str, trades: list[dict], *, test_env: bool = True) -> str:
|
|
39
|
+
"""
|
|
40
|
+
Submete lista de trades de Equity Offshore para a API do BTG.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
token: Token de autenticação.
|
|
44
|
+
trades: Lista de dicionários representando os trades. Cada dict deve ter
|
|
45
|
+
a estrutura esperada pela API. Modelo:
|
|
46
|
+
[{
|
|
47
|
+
"currency": "USD",
|
|
48
|
+
"price": "60.12",
|
|
49
|
+
"productCodeValue": "...",
|
|
50
|
+
"glAccount": "...",
|
|
51
|
+
"primeBroker": "...",
|
|
52
|
+
"side": "Buy",
|
|
53
|
+
"tradeQuantity": "1000",
|
|
54
|
+
"commissionAmount": "12.50",
|
|
55
|
+
"settlementCurrency": "USD",
|
|
56
|
+
"fXRate": "1.0",
|
|
57
|
+
"externalReference": "TRADE-001",
|
|
58
|
+
"counterparty": "...",
|
|
59
|
+
"fundNickname": "my_fund",
|
|
60
|
+
"orderIdentification": "...",
|
|
61
|
+
"book": "some_book",
|
|
62
|
+
"tradeDate": "2025-02-19T16:03:53.596Z"
|
|
63
|
+
}]
|
|
64
|
+
|
|
65
|
+
test_env: Ambiente de teste (True) ou produção (False).
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Ticket da requisição.
|
|
69
|
+
|
|
70
|
+
Raises:
|
|
71
|
+
BTGApiError: Se a requisição falhar.
|
|
72
|
+
|
|
73
|
+
"""
|
|
74
|
+
url = _EP_SUBMIT_TEST if test_env else _EP_SUBMIT_PROD
|
|
75
|
+
r = requests.post(
|
|
76
|
+
url,
|
|
77
|
+
headers={
|
|
78
|
+
"X-SecureConnect-Token": token,
|
|
79
|
+
"Content-Type": "application/json-patch+json",
|
|
80
|
+
},
|
|
81
|
+
json={"results": trades},
|
|
82
|
+
timeout=30,
|
|
83
|
+
)
|
|
84
|
+
if r.status_code in (200, 201):
|
|
85
|
+
ticket = r.json()["ticket"]
|
|
86
|
+
logger.debug("Trades submetidos, ticket %s", ticket)
|
|
87
|
+
return ticket
|
|
88
|
+
raise BTGApiError(f"Falha no submit: {r.status_code} – {r.text}")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def get_submitted_transactions(token: str, *, ticket_id: str = "", start_date: str = "",
|
|
92
|
+
end_date: str = "", market: str = "", test_env: bool = True) -> Dict:
|
|
93
|
+
"""Consulta status detalhado de ticket ou filtro de datas/mercado.
|
|
94
|
+
Args:
|
|
95
|
+
token: Token de autenticação.
|
|
96
|
+
ticket_id: ID do ticket (opcional).
|
|
97
|
+
start_date: Data de início (opcional, formato YYYY-MM-DD).
|
|
98
|
+
end_date: Data de fim (opcional, formato YYYY-MM-DD).
|
|
99
|
+
market: Mercado (opcional. Valores válidos: "equity", "future", "bonds", "repo",
|
|
100
|
+
"portfolio_swap", "interest_rate_swap", "performance_swap", "variance_swap",
|
|
101
|
+
"equity_option", "future_option", "fx_option_vanilla", "fx_option_barrier", "fx"]).
|
|
102
|
+
test_env: Ambiente de teste (True) ou produção (False).
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Dicionário com os dados da resposta.
|
|
106
|
+
|
|
107
|
+
Raises:
|
|
108
|
+
BTGApiError: Se a requisição falhar ou a resposta for inválida.
|
|
109
|
+
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
base_url = _EP_TICKET_TEST if test_env else _EP_TICKET_PROD
|
|
113
|
+
|
|
114
|
+
if ticket_id:
|
|
115
|
+
params = {"Ticket": ticket_id, "Detailed": "true"}
|
|
116
|
+
elif start_date and end_date and market:
|
|
117
|
+
params = {
|
|
118
|
+
"StartDate": start_date,
|
|
119
|
+
"EndDate": end_date,
|
|
120
|
+
"Market": _MARKET_IDS.get(market.lower(), market),
|
|
121
|
+
"Detailed": "true",
|
|
122
|
+
}
|
|
123
|
+
else:
|
|
124
|
+
raise BTGApiError("Forneça ticket_id OU start_date+end_date+market")
|
|
125
|
+
|
|
126
|
+
r = requests.get(
|
|
127
|
+
base_url,
|
|
128
|
+
headers={"X-SecureConnect-Token": token},
|
|
129
|
+
params=params,
|
|
130
|
+
timeout=30,
|
|
131
|
+
)
|
|
132
|
+
try:
|
|
133
|
+
return r.json()
|
|
134
|
+
except Exception as exc:
|
|
135
|
+
raise BTGApiError(f"Resposta inválida: {r.status_code}") from exc
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def await_transaction_ticket_result( token: str, ticket_id: str, *, attempts: int = 10,
|
|
139
|
+
interval: int = 30, test_env: bool = True) -> pd.DataFrame:
|
|
140
|
+
"""Espera a conclusão do ticket e devolve DataFrame com metadados.
|
|
141
|
+
Args:
|
|
142
|
+
token: Token de autenticação.
|
|
143
|
+
ticket_id: ID do ticket.
|
|
144
|
+
attempts: Número de tentativas.
|
|
145
|
+
interval: Intervalo entre tentativas em segundos.
|
|
146
|
+
test_env: Ambiente de teste (True) ou produção (False).
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
DataFrame com o status detalhado das transações.
|
|
150
|
+
|
|
151
|
+
Raises:
|
|
152
|
+
BTGApiError: Se o ticket não for finalizado ou falhar.
|
|
153
|
+
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
cols = ["Status", "Ticket", "TradeId", "Env", "Msg"]
|
|
157
|
+
results = pd.DataFrame(columns=cols)
|
|
158
|
+
|
|
159
|
+
for i in range(attempts):
|
|
160
|
+
data = get_submitted_transactions(token, ticket_id=ticket_id, test_env=test_env)
|
|
161
|
+
trades_info = data["trades"]
|
|
162
|
+
ticket_status = trades_info[0]["Status"].lower()
|
|
163
|
+
|
|
164
|
+
# ambiente de produção pode ficar em pendente; aguardamos
|
|
165
|
+
if ticket_status == "pendente":
|
|
166
|
+
logger.debug("Ticket %s pendente (%d/%d)", ticket_id, i + 1, attempts)
|
|
167
|
+
time.sleep(interval)
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
trades = trades_info[0]["Details"]["TicketDetalhesEquity"]
|
|
171
|
+
for tr in trades:
|
|
172
|
+
results.loc[len(results)] = [
|
|
173
|
+
tr["stateItemFile"].lower(),
|
|
174
|
+
ticket_id.lower(),
|
|
175
|
+
tr["externalReference"],
|
|
176
|
+
"test" if test_env else "prod",
|
|
177
|
+
tr["mensagens"],
|
|
178
|
+
]
|
|
179
|
+
return results
|
|
180
|
+
|
|
181
|
+
raise BTGApiError("Ticket não finalizado no tempo limite")
|
|
@@ -18,7 +18,7 @@ def save_table(
|
|
|
18
18
|
*,
|
|
19
19
|
index: bool = False,
|
|
20
20
|
index_name: str = "index",
|
|
21
|
-
normalize_columns: bool =
|
|
21
|
+
normalize_columns: bool = True,
|
|
22
22
|
directory: str = "enriched/parquet",
|
|
23
23
|
):
|
|
24
24
|
"""Salva DataFrame como Parquet em ADLS (sobrescrevendo)."""
|
|
@@ -34,7 +34,7 @@ def incremental_load(
|
|
|
34
34
|
increment_column: str = "Date",
|
|
35
35
|
index: bool = False,
|
|
36
36
|
index_name: str = "index",
|
|
37
|
-
normalize_columns: bool =
|
|
37
|
+
normalize_columns: bool = True,
|
|
38
38
|
directory: str = "enriched/parquet",
|
|
39
39
|
):
|
|
40
40
|
"""Concatena novos dados aos existentes, cortando duplicados pela data."""
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
from .transforms import prep_for_save, persist_column_formatting, text_to_lowercase
|
|
2
|
-
|
|
2
|
+
from .reader import read_bytes
|
|
3
|
+
|
|
4
|
+
__all__ = ["prep_for_save", "persist_column_formatting", "text_to_lowercase", "read_bytes"]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import io, pandas as pd, pyarrow.parquet as pq
|
|
2
|
+
|
|
3
|
+
def read_bytes(buf: bytes, *, filename: str) -> pd.DataFrame:
|
|
4
|
+
"""Detecta a extensão e carrega em DataFrame."""
|
|
5
|
+
ext = filename.split(".")[-1].lower()
|
|
6
|
+
f = io.BytesIO(buf)
|
|
7
|
+
|
|
8
|
+
if ext in {"xlsx", "xls"}:
|
|
9
|
+
return pd.read_excel(f)
|
|
10
|
+
if ext == "parquet":
|
|
11
|
+
return pq.read_table(f).to_pandas()
|
|
12
|
+
if ext == "csv":
|
|
13
|
+
try:
|
|
14
|
+
return pd.read_csv(f, encoding="utf-8")
|
|
15
|
+
except UnicodeDecodeError:
|
|
16
|
+
f.seek(0)
|
|
17
|
+
return pd.read_csv(f, encoding="latin1")
|
|
18
|
+
|
|
19
|
+
raise ValueError(f"Extensão {ext} não suportada")
|
luxorasap/utils/storage/blob.py
CHANGED
|
@@ -5,6 +5,9 @@ import pandas as pd
|
|
|
5
5
|
import pyarrow as pa, pyarrow.parquet as pq
|
|
6
6
|
from azure.storage.blob import BlobServiceClient
|
|
7
7
|
|
|
8
|
+
from ..dataframe import read_bytes
|
|
9
|
+
|
|
10
|
+
|
|
8
11
|
class BlobParquetClient:
|
|
9
12
|
"""Leitura/gravacao de Parquet em Azure Blob – stateless & reutilizável."""
|
|
10
13
|
|
|
@@ -22,9 +25,10 @@ class BlobParquetClient:
|
|
|
22
25
|
buf = io.BytesIO()
|
|
23
26
|
try:
|
|
24
27
|
self._blob(blob_path).download_blob().readinto(buf)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
return (
|
|
29
|
+
read_bytes(buf.getvalue(), filename=PurePosixPath(blob_path).name),
|
|
30
|
+
True,
|
|
31
|
+
)
|
|
28
32
|
except Exception:
|
|
29
33
|
return None, False
|
|
30
34
|
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: luxorasap
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Luxor’s unified toolbox for data ingestion, querying and analytics.
|
|
5
|
+
Author-email: Luxor Group <backoffice@luxor.com.br>
|
|
6
|
+
License: Proprietary – All rights reserved
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: Other/Proprietary License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: pandas>=2.2
|
|
13
|
+
Requires-Dist: numpy>=1.25
|
|
14
|
+
Requires-Dist: loguru>=0.7
|
|
15
|
+
Requires-Dist: python-dotenv>=1.0
|
|
16
|
+
Requires-Dist: azure-storage-blob>=12.19
|
|
17
|
+
Requires-Dist: pyarrow>=15.0
|
|
18
|
+
Requires-Dist: requests>=2.32
|
|
19
|
+
Requires-Dist: pydantic>=2.7
|
|
20
|
+
Requires-Dist: scipy>=1.13
|
|
21
|
+
Requires-Dist: openpyxl
|
|
22
|
+
Provides-Extra: storage
|
|
23
|
+
Requires-Dist: azure-storage-blob>=12.19; extra == "storage"
|
|
24
|
+
Requires-Dist: pyarrow>=15.0; extra == "storage"
|
|
25
|
+
Provides-Extra: dataframe
|
|
26
|
+
Requires-Dist: pandas>=2.2; extra == "dataframe"
|
|
27
|
+
Provides-Extra: datareader
|
|
28
|
+
Requires-Dist: luxorasap[dataframe,storage]; extra == "datareader"
|
|
29
|
+
Requires-Dist: numpy>=1.25; extra == "datareader"
|
|
30
|
+
Requires-Dist: scipy>=1.13; extra == "datareader"
|
|
31
|
+
Provides-Extra: ingest
|
|
32
|
+
Requires-Dist: luxorasap[dataframe,storage]; extra == "ingest"
|
|
33
|
+
Requires-Dist: pandas>=2.2; extra == "ingest"
|
|
34
|
+
Provides-Extra: btgapi
|
|
35
|
+
Requires-Dist: requests>=2.32; extra == "btgapi"
|
|
36
|
+
Requires-Dist: pydantic>=2.7; extra == "btgapi"
|
|
37
|
+
Provides-Extra: dev
|
|
38
|
+
Requires-Dist: pytest>=8.2; extra == "dev"
|
|
39
|
+
Requires-Dist: requests-mock>=1.11; extra == "dev"
|
|
40
|
+
Requires-Dist: black>=24.4.0; extra == "dev"
|
|
41
|
+
Requires-Dist: isort>=5.13; extra == "dev"
|
|
42
|
+
Requires-Dist: bumpver>=2024.3; extra == "dev"
|
|
43
|
+
Requires-Dist: pre-commit>=3.7; extra == "dev"
|
|
44
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
45
|
+
|
|
46
|
+
# 📚 Documentação LuxorASAP
|
|
47
|
+
|
|
48
|
+
> Guia do desenvolvedor para os subpacotes **datareader**, **ingest**, **btgapi** e **utils**.
|
|
49
|
+
>
|
|
50
|
+
> • Instalação rápida • Visão arquitetural • APIs detalhadas • Exemplos de uso • Extras opcional
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Índice
|
|
55
|
+
|
|
56
|
+
1. [Visão Geral](#visao-geral)
|
|
57
|
+
2. [Instalação](#instalacao)
|
|
58
|
+
3. [utils](#utils)
|
|
59
|
+
4. [datareader](#datareader)
|
|
60
|
+
5. [ingest](#ingest)
|
|
61
|
+
6. [btgapi](#btgapi)
|
|
62
|
+
7. [Roadmap & Contribuições](#roadmap)
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 1. Visão Geral
|
|
67
|
+
|
|
68
|
+
LuxorASAP é o *toolbox* unificado da Luxor para ingestão, consulta e automação de dados financeiros.
|
|
69
|
+
|
|
70
|
+
| Subpacote | Função‑chave | Extras PyPI |
|
|
71
|
+
| -------------- | --------------------------------------------------------------------- | ---------------------- |
|
|
72
|
+
| **utils** | Utilidades puras (I/O ADLS, transformação de DataFrame, decorators) | `storage`, `dataframe` |
|
|
73
|
+
| **datareader** | Consulta de preços/tabelas no data lake via `LuxorQuery` | `datareader` |
|
|
74
|
+
| **ingest** | Carga de dados nova (parquet/zip/excel) em ADLS + loader legado | `ingest` |
|
|
75
|
+
| **btgapi** | Wrapper autenticado para as APIs do BTG Pactual (relatórios & trades) | `btgapi` |
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 2. Instalação
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# core + leitura de dados
|
|
83
|
+
pip install luxorasap[datareader]
|
|
84
|
+
|
|
85
|
+
# tudo incluído (leitura, ingest, btg)
|
|
86
|
+
pip install luxorasap[datareader,ingest,btgapi]
|
|
87
|
+
|
|
88
|
+
# desenvolvimento
|
|
89
|
+
pip install -e ".[dev]"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Extras podem ser combinados à vontade (`luxorasap[storage]`, etc.).
|
|
93
|
+
|
|
94
|
+
Configuração obrigatória do **ADLS**:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
export AZURE_STORAGE_CONNECTION_STRING="DefaultEndpointsProtocol=..."
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 3. utils
|
|
103
|
+
|
|
104
|
+
Camada de utilidades **sem dependências internas**.
|
|
105
|
+
|
|
106
|
+
### 3.1 storage.BlobParquetClient
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from luxorasap.utils.storage import BlobParquetClient
|
|
110
|
+
client = BlobParquetClient(container="luxorasap")
|
|
111
|
+
|
|
112
|
+
# write
|
|
113
|
+
client.write_df(df, "bronze/parquet/mytable.parquet")
|
|
114
|
+
|
|
115
|
+
# read (tuple -> DataFrame, success_flag)
|
|
116
|
+
df, ok = client.read_df("bronze/parquet/mytable.parquet")
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 3.2 dataframe
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from luxorasap.utils.dataframe import prep_for_save, persist_column_formatting, read_bytes
|
|
123
|
+
|
|
124
|
+
df2 = prep_for_save(df, index=True, index_name="ID", normalize=True)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### 3.3 decorators & misc
|
|
128
|
+
|
|
129
|
+
`with_retry`, `chunkify`, `timer`… ficam em *utils.helpers* (caso precise).
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## 4. datareader
|
|
134
|
+
|
|
135
|
+
### 4.1 Visão rápida
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from luxorasap.datareader import LuxorQuery
|
|
139
|
+
lq = LuxorQuery()
|
|
140
|
+
|
|
141
|
+
# DataFrame completo
|
|
142
|
+
df = lq.get_table("assets")
|
|
143
|
+
|
|
144
|
+
# Série de preços diária
|
|
145
|
+
aapl = lq.get_prices("aapl us equity", start="2025-01-01", end="2025-03-31")
|
|
146
|
+
|
|
147
|
+
# Preço pontual
|
|
148
|
+
price = lq.get_price("aapl us equity", on="2025-03-31")
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
*Caching*: `@lru_cache(maxsize=32)` evita hits repetidos ao Blob.
|
|
152
|
+
|
|
153
|
+
### 4.2 Métodos-chave
|
|
154
|
+
|
|
155
|
+
| Método | Descrição |
|
|
156
|
+
| ----------------------------------------------- | ----------------------------- |
|
|
157
|
+
| `table_exists(name)` | checa metadados no ADLS |
|
|
158
|
+
| `get_table(name)` | DataFrame completo (cached) |
|
|
159
|
+
| `get_prices(asset, start, end, column="Price")` | `pd.Series` |
|
|
160
|
+
| `get_price(asset, on)` | preço pontual (float ou None) |
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## 5. ingest
|
|
165
|
+
|
|
166
|
+
### 5.1 ingest.cloud (novo)
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
from luxorasap.ingest import save_table, incremental_load
|
|
170
|
+
from luxorasap.datareader import LuxorQuery
|
|
171
|
+
|
|
172
|
+
save_table("trades", df)
|
|
173
|
+
|
|
174
|
+
lq = LuxorQuery()
|
|
175
|
+
incremental_load(lq, "prices_daily", df_new, increment_column="Date")
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### 5.2 ingest.legacy\_local
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
from luxorasap.ingest import DataLoader # Deprecado – ainda funcional
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
*Decoration*: ao importar `DataLoader` você verá `DeprecationWarning`.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## 6. btgapi
|
|
189
|
+
|
|
190
|
+
### 6.1 Autenticação
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
from luxorasap.btgapi import get_access_token
|
|
194
|
+
TOKEN = get_access_token(test_env=True)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### 6.2 Relatórios – Portfolio & Investor Transactions
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
from luxorasap.btgapi.reports import (
|
|
201
|
+
request_portfolio, await_report_ticket_result, process_zip_to_dfs,
|
|
202
|
+
request_investors_transactions_report,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
ticket = request_portfolio(TOKEN, "LUXOR FUND - CLASS A",
|
|
206
|
+
start=dt.date(2025,1,1), end=dt.date(2025,1,31))
|
|
207
|
+
zip_bytes = await_report_ticket_result(TOKEN, ticket)
|
|
208
|
+
carteiras = process_zip_to_dfs(zip_bytes)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### 6.3 Trades offshore
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
from luxorasap.btgapi.trades import (
|
|
215
|
+
submit_offshore_equity_trades,
|
|
216
|
+
await_transaction_ticket_result,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
ticket = submit_offshore_equity_trades(TOKEN, trades=[{...}], test_env=True)
|
|
220
|
+
status_df = await_transaction_ticket_result(TOKEN, ticket, test_env=True)
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### 6.4 Extras
|
|
224
|
+
|
|
225
|
+
* `BTGApiError` — exceção customizada para qualquer falha.
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## 7. Roadmap & Contribuições
|
|
230
|
+
|
|
231
|
+
* **Remover** `ingest.legacy_local` quando não houver mais dependências.
|
|
232
|
+
* Suporte a partições Parquet (delta‑like) na gravação.
|
|
233
|
+
* Adicionar `pydantic` para validar contratos BTG.
|
|
234
|
+
* Pull requests bem‑vindos! Rode `make lint && pytest -q` antes de enviar.
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
### Contatos
|
|
239
|
+
|
|
240
|
+
* Dados / Back‑Office – [backoffice@luxor.com.br](mailto:backoffice@luxor.com.br)
|
|
241
|
+
* Mantenedor principal – Sergio
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
luxorasap/__init__.py,sha256=Y5nnDcLGJ3_HtgLuOZcpM_FwSUlFBjrylPnr8xjAPIw,1355
|
|
2
|
+
luxorasap/btgapi/__init__.py,sha256=DISzvHp-J7oeNq_PhmCt-_ZRBCaUgkQ9k2wtJLm-kgs,563
|
|
3
|
+
luxorasap/btgapi/auth.py,sha256=2TLKZdVlJgfSItOvB7GoPnH6gndkWWqSIyE4VDSYRgo,1873
|
|
4
|
+
luxorasap/btgapi/reports.py,sha256=WSuJws9foki4i6URa9muKasOvzD6G11b-GVKvl9V5BA,6186
|
|
5
|
+
luxorasap/btgapi/trades.py,sha256=1Cn1RMjaHO073YHJFeN2XRxYElQH7A98GIfUVy0VmSg,5987
|
|
6
|
+
luxorasap/datareader/__init__.py,sha256=41RAvbrQ4R6oj67S32CrKqolx0CJ2W8cbOF6g5Cqm2g,120
|
|
7
|
+
luxorasap/datareader/core.py,sha256=LpXe5g4lZpfEqaz_gjjHizVA-vPEjBi5yJKg_7K0Nkw,153205
|
|
8
|
+
luxorasap/ingest/__init__.py,sha256=XhxDTN2ar-u6UCPhnxNU_to-nWiit-SpQ6cA_N9eMSs,795
|
|
9
|
+
luxorasap/ingest/cloud/__init__.py,sha256=MfnEYUav0AD6oxs92ghE8TtxdqcQkb_VB6z706m-2nw,1727
|
|
10
|
+
luxorasap/ingest/legacy_local/dataloader.py,sha256=zKPhuiBSFwkuWN6d8g2s60KkbVk1R_1cGMCtQM9j-0c,11908
|
|
11
|
+
luxorasap/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
luxorasap/utils/dataframe/__init__.py,sha256=dU_RwTTOi6F3mlhM-0MYWM_qexBN9BmmKc_yrDE1Lwc,207
|
|
13
|
+
luxorasap/utils/dataframe/reader.py,sha256=Vzjdw-AeS1lnWEHQ8RZNh0kK93NWTp0NWVi_B6mN5N0,616
|
|
14
|
+
luxorasap/utils/dataframe/transforms.py,sha256=Bm_cv9L9923QIXH82Fa_M4pM94f2AJRPu62Vv_i7tto,1684
|
|
15
|
+
luxorasap/utils/storage/__init__.py,sha256=U3XRq94yzRp3kgBSUcRzs2tQgJ4o8h8a1ZzwiscA5XM,67
|
|
16
|
+
luxorasap/utils/storage/blob.py,sha256=pcEixGxwXM9y5iPPpkX__ySWq0milghJGketYZlRL-0,3171
|
|
17
|
+
luxorasap-0.1.2.dist-info/METADATA,sha256=imZHJdmbV6ClMwMi29J3rsJTmfVZ7bip5S_aW9ew6Xk,6994
|
|
18
|
+
luxorasap-0.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
19
|
+
luxorasap-0.1.2.dist-info/entry_points.txt,sha256=XFh-dOwUhlya9DmGvgookMI0ezyUJjcOvTIHDEYS44g,52
|
|
20
|
+
luxorasap-0.1.2.dist-info/top_level.txt,sha256=9YOL6bUIpzY06XFBRkUW1e4rgB32Ds91fQPGwUEjxzU,10
|
|
21
|
+
luxorasap-0.1.2.dist-info/RECORD,,
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: luxorasap
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: Luxor’s unified toolbox for data ingestion, querying and analytics.
|
|
5
|
-
Author-email: Luxor Group <backoffice@luxor.com.br>
|
|
6
|
-
License: Proprietary – All rights reserved
|
|
7
|
-
Classifier: Programming Language :: Python :: 3
|
|
8
|
-
Classifier: License :: Other/Proprietary License
|
|
9
|
-
Classifier: Operating System :: OS Independent
|
|
10
|
-
Requires-Python: >=3.9
|
|
11
|
-
Description-Content-Type: text/markdown
|
|
12
|
-
Requires-Dist: pandas>=2.2
|
|
13
|
-
Requires-Dist: numpy>=1.25
|
|
14
|
-
Requires-Dist: loguru>=0.7
|
|
15
|
-
Requires-Dist: python-dotenv>=1.0
|
|
16
|
-
Requires-Dist: azure-storage-blob>=12.19
|
|
17
|
-
Requires-Dist: pyarrow>=15.0
|
|
18
|
-
Requires-Dist: requests>=2.32
|
|
19
|
-
Requires-Dist: pydantic>=2.7
|
|
20
|
-
Requires-Dist: scipy>=1.13
|
|
21
|
-
Provides-Extra: storage
|
|
22
|
-
Requires-Dist: azure-storage-blob>=12.19; extra == "storage"
|
|
23
|
-
Requires-Dist: pyarrow>=15.0; extra == "storage"
|
|
24
|
-
Provides-Extra: dataframe
|
|
25
|
-
Requires-Dist: pandas>=2.2; extra == "dataframe"
|
|
26
|
-
Provides-Extra: datareader
|
|
27
|
-
Requires-Dist: luxorasap[dataframe,storage]; extra == "datareader"
|
|
28
|
-
Requires-Dist: numpy>=1.25; extra == "datareader"
|
|
29
|
-
Requires-Dist: scipy>=1.13; extra == "datareader"
|
|
30
|
-
Provides-Extra: ingest
|
|
31
|
-
Requires-Dist: luxorasap[dataframe,storage]; extra == "ingest"
|
|
32
|
-
Requires-Dist: pandas>=2.2; extra == "ingest"
|
|
33
|
-
Provides-Extra: btgapi
|
|
34
|
-
Requires-Dist: requests>=2.32; extra == "btgapi"
|
|
35
|
-
Requires-Dist: pydantic>=2.7; extra == "btgapi"
|
|
36
|
-
Provides-Extra: dev
|
|
37
|
-
Requires-Dist: pytest>=8.2; extra == "dev"
|
|
38
|
-
Requires-Dist: black>=24.4.0; extra == "dev"
|
|
39
|
-
Requires-Dist: isort>=5.13; extra == "dev"
|
|
40
|
-
Requires-Dist: bumpver>=2024.3; extra == "dev"
|
|
41
|
-
Requires-Dist: pre-commit>=3.7; extra == "dev"
|
|
42
|
-
Requires-Dist: build>=1.2; extra == "dev"
|
|
43
|
-
|
|
44
|
-
# LuxorASAP
|
|
45
|
-
|
|
46
|
-
**LuxorASAP** é o pacote-guarda-chuva que concentra as ferramentas internas de dados da Luxor Group:
|
|
47
|
-
consulta estruturada ao data lake, cargas padronizadas para ADLS, wrappers de API, utilitários e muito mais.
|
|
48
|
-
|
|
49
|
-
[](https://pypi.org/project/luxorasap/)
|
|
50
|
-

|
|
51
|
-
|
|
52
|
-
---
|
|
53
|
-
|
|
54
|
-
## Instalação
|
|
55
|
-
|
|
56
|
-
```bash
|
|
57
|
-
# pacote base
|
|
58
|
-
pip install luxorasap
|
|
59
|
-
|
|
60
|
-
# com o submódulo datareader
|
|
61
|
-
pip install "luxorasap[datareader]"
|
|
62
|
-
```
|
|
63
|
-
## Uso rápido
|
|
64
|
-
```python
|
|
65
|
-
from luxorasap.datareader import LuxorQuery
|
|
66
|
-
|
|
67
|
-
lq = LuxorQuery(blob_directory="enriched/parquet")
|
|
68
|
-
prices = lq.get_prices("aapl us equity", "2024-01-01", "2024-12-31")
|
|
69
|
-
print(prices.head())
|
|
70
|
-
```
|
|
71
|
-
## Submódulos
|
|
72
|
-
| Módulo | Descrição rápida | Extras |
|
|
73
|
-
| ---------------------- | ---------------------------------------- | ------------------------------------- |
|
|
74
|
-
| `luxorasap.datareader` | Leitura de tabelas e séries no data lake | `pip install "luxorasap[datareader]"` |
|
|
75
|
-
| `luxorasap.ingest` | Funções de carga padronizada para ADLS | `"luxorasap[ingest]"` |
|
|
76
|
-
| `luxorasap.btgapi` | Wrapper REST para dados BTG | `"luxorasap[btgapi]"` |
|
|
77
|
-
|
|
78
|
-
© Luxor Group – uso interno. Todos os direitos reservados.
|
luxorasap-0.1.0.dist-info/RECORD
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
luxorasap/__init__.py,sha256=XLioUOz-0RizsKh06R0U3uYMG7EwTQ08p_GLVSQBXNY,1355
|
|
2
|
-
luxorasap/datareader/__init__.py,sha256=41RAvbrQ4R6oj67S32CrKqolx0CJ2W8cbOF6g5Cqm2g,120
|
|
3
|
-
luxorasap/datareader/core.py,sha256=LpXe5g4lZpfEqaz_gjjHizVA-vPEjBi5yJKg_7K0Nkw,153205
|
|
4
|
-
luxorasap/ingest/__init__.py,sha256=XhxDTN2ar-u6UCPhnxNU_to-nWiit-SpQ6cA_N9eMSs,795
|
|
5
|
-
luxorasap/ingest/cloud/__init__.py,sha256=V8cCNloP1RgPTEPsepHvWVL4m_t5geQuBORLm7x-OKQ,1729
|
|
6
|
-
luxorasap/ingest/legacy_local/dataloader.py,sha256=zKPhuiBSFwkuWN6d8g2s60KkbVk1R_1cGMCtQM9j-0c,11908
|
|
7
|
-
luxorasap/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
luxorasap/utils/dataframe/__init__.py,sha256=LXcMsv8kPFfIVzcwfA9lI-TExH6ty6a3NHBVBWyxfTA,161
|
|
9
|
-
luxorasap/utils/dataframe/transforms.py,sha256=O5VxJHMV2g6zKLJc2O7F84wyF9c_hqo6kJOVjixLeI4,1757
|
|
10
|
-
luxorasap/utils/storage/__init__.py,sha256=U3XRq94yzRp3kgBSUcRzs2tQgJ4o8h8a1ZzwiscA5XM,67
|
|
11
|
-
luxorasap/utils/storage/blob.py,sha256=lJPN5VoTVheijUVp1zhSY51GnqapiYYDkypFTVtAb10,3086
|
|
12
|
-
luxorasap-0.1.0.dist-info/METADATA,sha256=UD40ZCS8E2-jhopS3gTyy6jOaEOykQ71PnncCrVCjew,3018
|
|
13
|
-
luxorasap-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
-
luxorasap-0.1.0.dist-info/entry_points.txt,sha256=XFh-dOwUhlya9DmGvgookMI0ezyUJjcOvTIHDEYS44g,52
|
|
15
|
-
luxorasap-0.1.0.dist-info/top_level.txt,sha256=9YOL6bUIpzY06XFBRkUW1e4rgB32Ds91fQPGwUEjxzU,10
|
|
16
|
-
luxorasap-0.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|