btgsolutions-dataservices-python-client 3.2.10__tar.gz → 3.2.12__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.
- {btgsolutions_dataservices_python_client-3.2.10/btgsolutions_dataservices_python_client.egg-info → btgsolutions_dataservices_python_client-3.2.12}/PKG-INFO +44 -10
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/README.md +42 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices/__init__.py +1 -1
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices/rest/__init__.py +2 -0
- btgsolutions_dataservices_python_client-3.2.12/btgsolutions_dataservices/rest/book_scope.py +212 -0
- btgsolutions_dataservices_python_client-3.2.12/btgsolutions_dataservices/rest/broker_analytics.py +173 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices/rest/historical_candles.py +66 -6
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12/btgsolutions_dataservices_python_client.egg-info}/PKG-INFO +44 -10
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices_python_client.egg-info/SOURCES.txt +2 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/LICENSE +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/MANIFEST.in +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices/config.py +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices/exceptions.py +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices/rest/authenticator.py +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices/rest/broker_reference.py +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices/rest/bulk_data.py +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices/rest/company_data.py +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices/rest/corporate_events.py +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices/rest/hfn.py +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices/rest/historical_candles_crypto.py +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices/rest/intraday_candles.py +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices/rest/intraday_tick_data.py +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices/rest/public_sources.py +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices/rest/quotes.py +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices/rest/reference_data.py +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices/rest/stock_loan.py +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices/rest/ticker_last_event.py +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices/rest/ticker_last_event_polling.py +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices/websocket/__init__.py +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices/websocket/broker_analytics.py +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices/websocket/hfn_websocket_client.py +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices/websocket/market_data_feed.py +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices/websocket/market_data_websocket_client.py +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices/websocket/websocket_default_functions.py +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices_python_client.egg-info/dependency_links.txt +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices_python_client.egg-info/requires.txt +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/btgsolutions_dataservices_python_client.egg-info/top_level.txt +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/pyproject.toml +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/requirements.txt +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/setup.cfg +0 -0
- {btgsolutions_dataservices_python_client-3.2.10 → btgsolutions_dataservices_python_client-3.2.12}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
2
|
Name: btgsolutions_dataservices_python_client
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.12
|
|
4
4
|
Summary: Python package containing several classes and data for extracting and manipulating market and trading data.
|
|
5
5
|
Home-page: https://github.com/BTG-Pactual-Solutions/btgsolutions-dataservices-python-client
|
|
6
6
|
Author: BTG Solutions Data Services powered by BTG Pactual Solutions
|
|
@@ -12,14 +12,6 @@ Requires-Dist: websocket-client>=1.8.0
|
|
|
12
12
|
Requires-Dist: PyJWT>=2.8.0
|
|
13
13
|
Requires-Dist: requests>=2.32.3
|
|
14
14
|
Requires-Dist: pyarrow>=17.0.0
|
|
15
|
-
Dynamic: author
|
|
16
|
-
Dynamic: description
|
|
17
|
-
Dynamic: description-content-type
|
|
18
|
-
Dynamic: home-page
|
|
19
|
-
Dynamic: license-file
|
|
20
|
-
Dynamic: requires-dist
|
|
21
|
-
Dynamic: requires-python
|
|
22
|
-
Dynamic: summary
|
|
23
15
|
|
|
24
16
|
# BTG Solutions - Data Services
|
|
25
17
|
|
|
@@ -280,6 +272,14 @@ hist_candles = btg.HistoricalCandles(api_key='YOUR_API_KEY')
|
|
|
280
272
|
hist_candles.get_intraday_history_candles(ticker='PETR4', market_type='stocks', corporate_events_adj=True, date='2023-10-06', candle='1m', rmv_after_market=True, timezone='UTC', raw_data=False, round=True)
|
|
281
273
|
```
|
|
282
274
|
|
|
275
|
+
##### Interday Batch
|
|
276
|
+
|
|
277
|
+
```python
|
|
278
|
+
import btgsolutions_dataservices as btg
|
|
279
|
+
hist_candles = btg.HistoricalCandles(api_key='YOUR_API_KEY')
|
|
280
|
+
hist_candles.get_interday_history_candles_batch(market_type='stocks', tickers=['PETR4', 'VALE3'], start_date='2023-10-01', end_date='2023-10-13', corporate_events_adj=True, rmv_after_market=True, timezone='UTC', raw_data=False, round=True)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
283
|
##### Available Tickers
|
|
284
284
|
|
|
285
285
|
```python
|
|
@@ -503,6 +503,40 @@ broker_reference = btg.BrokerReference(api_key='YOUR_API_KEY')
|
|
|
503
503
|
broker_reference.get()
|
|
504
504
|
```
|
|
505
505
|
|
|
506
|
+
#### Book Scope
|
|
507
|
+
|
|
508
|
+
```python
|
|
509
|
+
import btgsolutions_dataservices as btg
|
|
510
|
+
book_scope = btg.BookScope(api_key='YOUR_API_KEY')
|
|
511
|
+
|
|
512
|
+
result = book_scope.get(
|
|
513
|
+
symbol='DOLM26',
|
|
514
|
+
market_type='derivatives',
|
|
515
|
+
start_time='2026-05-28T14:12:00Z',
|
|
516
|
+
end_time='2026-05-28T14:15:00Z',
|
|
517
|
+
select=['trades', 'book_snapshot', 'book_incremental'], # choose one, two, or all three
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
single_file_result = book_scope.get(
|
|
521
|
+
symbol='DOLM26',
|
|
522
|
+
market_type='derivatives',
|
|
523
|
+
start_time='2026-05-28T14:12:00Z',
|
|
524
|
+
end_time='2026-05-28T14:15:00Z',
|
|
525
|
+
select=['trades', 'book_snapshot', 'book_incremental'],
|
|
526
|
+
aggregate_info=True,
|
|
527
|
+
)
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
#### Broker Analytics
|
|
531
|
+
|
|
532
|
+
```python
|
|
533
|
+
import btgsolutions_dataservices as btg
|
|
534
|
+
broker_analytics = btg.BrokerAnalytics(api_key='YOUR_API_KEY', market_type='stocks')
|
|
535
|
+
summary = broker_analytics.get_summary(brokers=['85', '3'], tickers=['PETR4', 'ABCB4'])
|
|
536
|
+
top_brokers = broker_analytics.get_top_brokers(n=10)
|
|
537
|
+
top_tickers = broker_analytics.get_top_tickers(n=10, brokers=['85', '3'])
|
|
538
|
+
```
|
|
539
|
+
|
|
506
540
|
#### Ticker Reference Data
|
|
507
541
|
|
|
508
542
|
```python
|
|
@@ -257,6 +257,14 @@ hist_candles = btg.HistoricalCandles(api_key='YOUR_API_KEY')
|
|
|
257
257
|
hist_candles.get_intraday_history_candles(ticker='PETR4', market_type='stocks', corporate_events_adj=True, date='2023-10-06', candle='1m', rmv_after_market=True, timezone='UTC', raw_data=False, round=True)
|
|
258
258
|
```
|
|
259
259
|
|
|
260
|
+
##### Interday Batch
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
import btgsolutions_dataservices as btg
|
|
264
|
+
hist_candles = btg.HistoricalCandles(api_key='YOUR_API_KEY')
|
|
265
|
+
hist_candles.get_interday_history_candles_batch(market_type='stocks', tickers=['PETR4', 'VALE3'], start_date='2023-10-01', end_date='2023-10-13', corporate_events_adj=True, rmv_after_market=True, timezone='UTC', raw_data=False, round=True)
|
|
266
|
+
```
|
|
267
|
+
|
|
260
268
|
##### Available Tickers
|
|
261
269
|
|
|
262
270
|
```python
|
|
@@ -480,6 +488,40 @@ broker_reference = btg.BrokerReference(api_key='YOUR_API_KEY')
|
|
|
480
488
|
broker_reference.get()
|
|
481
489
|
```
|
|
482
490
|
|
|
491
|
+
#### Book Scope
|
|
492
|
+
|
|
493
|
+
```python
|
|
494
|
+
import btgsolutions_dataservices as btg
|
|
495
|
+
book_scope = btg.BookScope(api_key='YOUR_API_KEY')
|
|
496
|
+
|
|
497
|
+
result = book_scope.get(
|
|
498
|
+
symbol='DOLM26',
|
|
499
|
+
market_type='derivatives',
|
|
500
|
+
start_time='2026-05-28T14:12:00Z',
|
|
501
|
+
end_time='2026-05-28T14:15:00Z',
|
|
502
|
+
select=['trades', 'book_snapshot', 'book_incremental'], # choose one, two, or all three
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
single_file_result = book_scope.get(
|
|
506
|
+
symbol='DOLM26',
|
|
507
|
+
market_type='derivatives',
|
|
508
|
+
start_time='2026-05-28T14:12:00Z',
|
|
509
|
+
end_time='2026-05-28T14:15:00Z',
|
|
510
|
+
select=['trades', 'book_snapshot', 'book_incremental'],
|
|
511
|
+
aggregate_info=True,
|
|
512
|
+
)
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
#### Broker Analytics
|
|
516
|
+
|
|
517
|
+
```python
|
|
518
|
+
import btgsolutions_dataservices as btg
|
|
519
|
+
broker_analytics = btg.BrokerAnalytics(api_key='YOUR_API_KEY', market_type='stocks')
|
|
520
|
+
summary = broker_analytics.get_summary(brokers=['85', '3'], tickers=['PETR4', 'ABCB4'])
|
|
521
|
+
top_brokers = broker_analytics.get_top_brokers(n=10)
|
|
522
|
+
top_tickers = broker_analytics.get_top_tickers(n=10, brokers=['85', '3'])
|
|
523
|
+
```
|
|
524
|
+
|
|
483
525
|
#### Ticker Reference Data
|
|
484
526
|
|
|
485
527
|
```python
|
|
@@ -14,3 +14,5 @@ from .reference_data import ReferenceData
|
|
|
14
14
|
from .stock_loan import StockLoan
|
|
15
15
|
from .ticker_last_event_polling import TickerLastEventPolling
|
|
16
16
|
from .broker_reference import BrokerReference
|
|
17
|
+
from .book_scope import BookScope
|
|
18
|
+
from .broker_analytics import BrokerAnalytics
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
from datetime import datetime, timedelta, timezone
|
|
2
|
+
from typing import Optional, List
|
|
3
|
+
from ..exceptions import BadResponse
|
|
4
|
+
import requests
|
|
5
|
+
from ..config import url_api_v1
|
|
6
|
+
from .authenticator import Authenticator
|
|
7
|
+
from io import BytesIO
|
|
8
|
+
import pyarrow as pa
|
|
9
|
+
import pyarrow.parquet as pq
|
|
10
|
+
|
|
11
|
+
VALID_SELECT_OPTIONS = {"trades", "book_snapshot", "book_incremental"}
|
|
12
|
+
VALID_MARKET_TYPES = {"derivatives", "options", "stocks"}
|
|
13
|
+
MAX_MARKET_SCOPE_WINDOW = timedelta(minutes=10)
|
|
14
|
+
ROW_LIMIT = 10000
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BookScope:
|
|
18
|
+
"""
|
|
19
|
+
This class provides market scope data (trades, book snapshots, book incremental)
|
|
20
|
+
for a given symbol and time window.
|
|
21
|
+
|
|
22
|
+
* Main use case:
|
|
23
|
+
|
|
24
|
+
>>> from btgsolutions_dataservices import BookScope
|
|
25
|
+
>>> book_scope = BookScope(
|
|
26
|
+
>>> api_key='YOUR_API_KEY',
|
|
27
|
+
>>> )
|
|
28
|
+
>>> book_scope.get(
|
|
29
|
+
>>> symbol='DOLM26',
|
|
30
|
+
>>> market_type='derivatives',
|
|
31
|
+
>>> start_time='2026-05-28T14:12:00Z',
|
|
32
|
+
>>> end_time='2026-05-28T14:15:00Z',
|
|
33
|
+
>>> )
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------------
|
|
37
|
+
api_key: str
|
|
38
|
+
User identification key.
|
|
39
|
+
Field is required.
|
|
40
|
+
"""
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
api_key: Optional[str],
|
|
44
|
+
):
|
|
45
|
+
self.api_key = api_key
|
|
46
|
+
self.token = Authenticator(self.api_key).token
|
|
47
|
+
self.headers = {"authorization": f"authorization {self.token}"}
|
|
48
|
+
self.base_url = url_api_v1
|
|
49
|
+
|
|
50
|
+
def _fetch_all_pages(self, endpoint_url: str, params: dict) -> bytes:
|
|
51
|
+
"""Fetches all pages via rpt_seq cursor and returns a single combined parquet."""
|
|
52
|
+
tables = []
|
|
53
|
+
rpt_seq = None
|
|
54
|
+
|
|
55
|
+
while True:
|
|
56
|
+
page_params = {**params}
|
|
57
|
+
if rpt_seq is not None:
|
|
58
|
+
page_params["rpt_seq"] = rpt_seq
|
|
59
|
+
|
|
60
|
+
response = requests.get(endpoint_url, params=page_params, headers=self.headers, timeout=50)
|
|
61
|
+
if response.status_code != 200:
|
|
62
|
+
try:
|
|
63
|
+
error_body = response.json()
|
|
64
|
+
detail = error_body.get("detail", error_body.get("error", response.text))
|
|
65
|
+
except Exception:
|
|
66
|
+
detail = response.text
|
|
67
|
+
raise BadResponse(f"Error: {detail}")
|
|
68
|
+
|
|
69
|
+
table = pq.read_table(BytesIO(response.content))
|
|
70
|
+
tables.append(table)
|
|
71
|
+
|
|
72
|
+
if table.num_rows < ROW_LIMIT:
|
|
73
|
+
break
|
|
74
|
+
rpt_seq = table.column("rpt_seq")[-1].as_py()
|
|
75
|
+
|
|
76
|
+
combined = pa.concat_tables(tables)
|
|
77
|
+
buffer = BytesIO()
|
|
78
|
+
pq.write_table(combined, buffer)
|
|
79
|
+
return buffer.getvalue()
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def _combine_parquets(parquet_payloads: List[bytes]) -> bytes:
|
|
83
|
+
"""Combines multiple parquet payloads into a single parquet file."""
|
|
84
|
+
tables = [BookScope._normalize_timestamps(pq.read_table(BytesIO(payload))) for payload in parquet_payloads]
|
|
85
|
+
combined = pa.concat_tables(tables, promote_options="default")
|
|
86
|
+
buffer = BytesIO()
|
|
87
|
+
pq.write_table(combined, buffer)
|
|
88
|
+
return buffer.getvalue()
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def _normalize_timestamps(table: pa.Table) -> pa.Table:
|
|
92
|
+
"""Normalizes timestamp columns so tables can be concatenated safely."""
|
|
93
|
+
normalized = table
|
|
94
|
+
|
|
95
|
+
for field in table.schema:
|
|
96
|
+
if pa.types.is_timestamp(field.type):
|
|
97
|
+
target_type = pa.timestamp("us", tz=field.type.tz)
|
|
98
|
+
column_index = normalized.schema.get_field_index(field.name)
|
|
99
|
+
normalized = normalized.set_column(
|
|
100
|
+
column_index,
|
|
101
|
+
field.name,
|
|
102
|
+
normalized[field.name].cast(target_type),
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
return normalized
|
|
106
|
+
|
|
107
|
+
@staticmethod
|
|
108
|
+
def _parse_iso8601(value: str, field_name: str) -> datetime:
|
|
109
|
+
try:
|
|
110
|
+
return datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
111
|
+
except ValueError as exc:
|
|
112
|
+
raise ValueError(f"{field_name} must be a valid ISO-8601 datetime string.") from exc
|
|
113
|
+
|
|
114
|
+
@staticmethod
|
|
115
|
+
def _resolve_scope(end_time: datetime) -> str:
|
|
116
|
+
"""Determines whether the request is 'intraday' or 'historical' based on end_time."""
|
|
117
|
+
now = datetime.now(timezone.utc)
|
|
118
|
+
today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
119
|
+
if end_time >= today_start:
|
|
120
|
+
return "intraday"
|
|
121
|
+
return "historical"
|
|
122
|
+
|
|
123
|
+
def get(
|
|
124
|
+
self,
|
|
125
|
+
symbol: str,
|
|
126
|
+
market_type: str,
|
|
127
|
+
start_time: str,
|
|
128
|
+
end_time: str,
|
|
129
|
+
select: List[str] = ["trades", "book_snapshot", "book_incremental"],
|
|
130
|
+
aggregate_info: bool = False,
|
|
131
|
+
):
|
|
132
|
+
"""
|
|
133
|
+
Returns trades, book snapshot and book incremental data for the given symbol and time window.
|
|
134
|
+
|
|
135
|
+
Pagination is handled automatically: if the API returns the maximum number of rows
|
|
136
|
+
(10 000), additional pages are fetched using the rpt_seq cursor until all events
|
|
137
|
+
in the requested horizon are retrieved. The caller always receives a single,
|
|
138
|
+
complete DataFrame per dataset.
|
|
139
|
+
|
|
140
|
+
Parameters
|
|
141
|
+
----------------
|
|
142
|
+
symbol: str
|
|
143
|
+
The ticker symbol to query.
|
|
144
|
+
Field is required. Example: 'PETR4', 'DOLM26'.
|
|
145
|
+
market_type: str
|
|
146
|
+
Market type.
|
|
147
|
+
Field is required. Allowed values: 'derivatives', 'options', 'stocks'.
|
|
148
|
+
start_time: str
|
|
149
|
+
Start of the analysis window in ISO-8601 UTC format.
|
|
150
|
+
Field is required. Example: '2026-05-28T14:12:00Z'.
|
|
151
|
+
end_time: str
|
|
152
|
+
End of the analysis window in ISO-8601 UTC format.
|
|
153
|
+
Field is required. Example: '2026-05-28T14:15:00Z'.
|
|
154
|
+
select: list of str
|
|
155
|
+
Which datasets to return.
|
|
156
|
+
Field is optional. Default: ['trades', 'book_snapshot', 'book_incremental'].
|
|
157
|
+
Allowed values: 'trades', 'book_snapshot', 'book_incremental'.
|
|
158
|
+
aggregate_info: bool
|
|
159
|
+
When True, returns a single pandas.DataFrame containing the selected datasets.
|
|
160
|
+
Field is optional. Default: False.
|
|
161
|
+
|
|
162
|
+
Returns
|
|
163
|
+
----------------
|
|
164
|
+
dict or pandas.DataFrame
|
|
165
|
+
When aggregate_info is False (default): dictionary with the selected keys
|
|
166
|
+
('trades', 'book_snapshot', 'book_incremental'), each mapping directly to
|
|
167
|
+
a pandas.DataFrame with all rows for the requested time window.
|
|
168
|
+
When aggregate_info is True: returns a single pandas.DataFrame with all
|
|
169
|
+
selected datasets combined.
|
|
170
|
+
"""
|
|
171
|
+
normalized_market_type = market_type.lower()
|
|
172
|
+
if normalized_market_type not in VALID_MARKET_TYPES:
|
|
173
|
+
raise ValueError(
|
|
174
|
+
f"Invalid market_type: {market_type}. Valid options are: {VALID_MARKET_TYPES}"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
invalid = set(select) - VALID_SELECT_OPTIONS
|
|
178
|
+
if invalid:
|
|
179
|
+
raise ValueError(f"Invalid select options: {invalid}. Valid options are: {VALID_SELECT_OPTIONS}")
|
|
180
|
+
|
|
181
|
+
parsed_start_time = self._parse_iso8601(start_time, "start_time")
|
|
182
|
+
parsed_end_time = self._parse_iso8601(end_time, "end_time")
|
|
183
|
+
|
|
184
|
+
if parsed_start_time >= parsed_end_time:
|
|
185
|
+
raise ValueError("start_time must be earlier than end_time.")
|
|
186
|
+
|
|
187
|
+
if parsed_end_time - parsed_start_time > MAX_MARKET_SCOPE_WINDOW:
|
|
188
|
+
raise ValueError("The maximum allowed range between start_time and end_time is 10 minutes.")
|
|
189
|
+
|
|
190
|
+
base_params = dict(symbol=symbol, start_time=start_time, end_time=end_time, limit=ROW_LIMIT)
|
|
191
|
+
|
|
192
|
+
scope = self._resolve_scope(parsed_end_time)
|
|
193
|
+
|
|
194
|
+
endpoint_map = {
|
|
195
|
+
"trades": f"{self.base_url}/marketdata/br/b3/book-scope/{scope}/{normalized_market_type}/trades",
|
|
196
|
+
"book_snapshot": f"{self.base_url}/marketdata/br/b3/book-scope/{scope}/{normalized_market_type}/book-snapshot",
|
|
197
|
+
"book_incremental": f"{self.base_url}/marketdata/br/b3/book-scope/{scope}/{normalized_market_type}/book-incremental",
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
result = {}
|
|
201
|
+
parquet_payloads = []
|
|
202
|
+
for key in select:
|
|
203
|
+
data = self._fetch_all_pages(endpoint_map[key], base_params)
|
|
204
|
+
if aggregate_info:
|
|
205
|
+
parquet_payloads.append(data)
|
|
206
|
+
else:
|
|
207
|
+
result[key] = pq.read_table(BytesIO(data)).to_pandas()
|
|
208
|
+
|
|
209
|
+
if aggregate_info:
|
|
210
|
+
return pq.read_table(BytesIO(self._combine_parquets(parquet_payloads))).to_pandas()
|
|
211
|
+
|
|
212
|
+
return result
|
btgsolutions_dataservices_python_client-3.2.12/btgsolutions_dataservices/rest/broker_analytics.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
from typing import Optional, List
|
|
2
|
+
from ..exceptions import BadResponse
|
|
3
|
+
import requests
|
|
4
|
+
from ..config import url_api_v1
|
|
5
|
+
from .authenticator import Authenticator
|
|
6
|
+
|
|
7
|
+
VALID_MARKET_TYPES = {"derivatives", "stocks", "options"}
|
|
8
|
+
|
|
9
|
+
class BrokerAnalytics:
|
|
10
|
+
"""
|
|
11
|
+
This class provides broker analytics data (summary, top brokers, top tickers)
|
|
12
|
+
for a given market type.
|
|
13
|
+
|
|
14
|
+
* Main use case:
|
|
15
|
+
|
|
16
|
+
>>> from btgsolutions_dataservices import BrokerAnalytics
|
|
17
|
+
>>> broker_analytics = BrokerAnalytics(
|
|
18
|
+
>>> api_key='YOUR_API_KEY',
|
|
19
|
+
>>> market_type='stocks',
|
|
20
|
+
>>> )
|
|
21
|
+
>>> broker_analytics.get_summary(
|
|
22
|
+
>>> brokers=['85', '3'],
|
|
23
|
+
>>> tickers=['ABCB4', 'PETR4'],
|
|
24
|
+
>>> )
|
|
25
|
+
>>> broker_analytics.get_top_brokers(n=10, tickers=['ABCB4'])
|
|
26
|
+
>>> broker_analytics.get_top_tickers(n=10, brokers=['85', '3'])
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------------
|
|
30
|
+
api_key: str
|
|
31
|
+
User identification key.
|
|
32
|
+
Field is required.
|
|
33
|
+
market_type: str
|
|
34
|
+
Market type to query. One of: 'derivatives', 'stocks', 'options'.
|
|
35
|
+
Field is required.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
api_key: str,
|
|
41
|
+
market_type: str,
|
|
42
|
+
):
|
|
43
|
+
self.api_key = api_key
|
|
44
|
+
self.token = Authenticator(self.api_key).token
|
|
45
|
+
self.headers = {"authorization": f"authorization {self.token}"}
|
|
46
|
+
|
|
47
|
+
market_type = market_type.strip().lower()
|
|
48
|
+
if market_type not in VALID_MARKET_TYPES:
|
|
49
|
+
raise ValueError(
|
|
50
|
+
f"Invalid market_type '{market_type}'. Must be one of: {sorted(VALID_MARKET_TYPES)}"
|
|
51
|
+
)
|
|
52
|
+
self.market_type = market_type
|
|
53
|
+
|
|
54
|
+
def _build_url(self, suffix: str) -> str:
|
|
55
|
+
return f"{url_api_v1}/marketdata/br/b3/realtime/broker-analytics/{self.market_type}/{suffix}"
|
|
56
|
+
|
|
57
|
+
def get_summary(
|
|
58
|
+
self,
|
|
59
|
+
brokers: List[str],
|
|
60
|
+
tickers: List[str],
|
|
61
|
+
side: Optional[str] = None,
|
|
62
|
+
) -> dict:
|
|
63
|
+
"""
|
|
64
|
+
Get day analytics summary for specific brokers and tickers.
|
|
65
|
+
|
|
66
|
+
Parameters
|
|
67
|
+
----------
|
|
68
|
+
brokers : List[str]
|
|
69
|
+
List of broker names to filter. Required.
|
|
70
|
+
tickers : List[str]
|
|
71
|
+
List of ticker symbols to filter. Required.
|
|
72
|
+
side : str, optional
|
|
73
|
+
Filter by side ('buy' or 'sell'). If None, both sides are returned.
|
|
74
|
+
|
|
75
|
+
Returns
|
|
76
|
+
-------
|
|
77
|
+
dict
|
|
78
|
+
JSON response with broker analytics summary data.
|
|
79
|
+
"""
|
|
80
|
+
params = {
|
|
81
|
+
"brokers": ",".join(brokers),
|
|
82
|
+
"tickers": ",".join(tickers),
|
|
83
|
+
}
|
|
84
|
+
if side is not None: params["side"] = side
|
|
85
|
+
|
|
86
|
+
url = self._build_url("summary")
|
|
87
|
+
response = requests.get(url, params=params, headers=self.headers, timeout=30)
|
|
88
|
+
|
|
89
|
+
if response.status_code != 200:
|
|
90
|
+
self._raise_error(response)
|
|
91
|
+
|
|
92
|
+
return response.json()
|
|
93
|
+
|
|
94
|
+
def get_top_brokers(
|
|
95
|
+
self,
|
|
96
|
+
n: int,
|
|
97
|
+
tickers: Optional[List[str]] = None,
|
|
98
|
+
side: Optional[str] = None,
|
|
99
|
+
) -> dict:
|
|
100
|
+
"""
|
|
101
|
+
Get top N brokers ranked by financial volume, with ticker breakdown.
|
|
102
|
+
|
|
103
|
+
Parameters
|
|
104
|
+
----------
|
|
105
|
+
n : int
|
|
106
|
+
Number of top brokers to return. Required.
|
|
107
|
+
tickers : List[str], optional
|
|
108
|
+
List of ticker symbols to filter.
|
|
109
|
+
side : str, optional
|
|
110
|
+
Filter by side ('buy' or 'sell').
|
|
111
|
+
|
|
112
|
+
Returns
|
|
113
|
+
-------
|
|
114
|
+
dict
|
|
115
|
+
JSON response with top brokers and their top assets breakdown.
|
|
116
|
+
"""
|
|
117
|
+
params = {"n": str(n)}
|
|
118
|
+
|
|
119
|
+
if tickers: params["tickers"] = ",".join(tickers)
|
|
120
|
+
if side is not None: params["side"] = side
|
|
121
|
+
|
|
122
|
+
url = self._build_url("top-brokers")
|
|
123
|
+
response = requests.get(url, params=params, headers=self.headers, timeout=30)
|
|
124
|
+
|
|
125
|
+
if response.status_code != 200:
|
|
126
|
+
self._raise_error(response)
|
|
127
|
+
|
|
128
|
+
return response.json()
|
|
129
|
+
|
|
130
|
+
def get_top_tickers(
|
|
131
|
+
self,
|
|
132
|
+
n: int,
|
|
133
|
+
brokers: Optional[List[str]] = None,
|
|
134
|
+
side: Optional[str] = None,
|
|
135
|
+
) -> dict:
|
|
136
|
+
"""
|
|
137
|
+
Get top N tickers ranked by financial volume, with broker breakdown.
|
|
138
|
+
|
|
139
|
+
Parameters
|
|
140
|
+
----------
|
|
141
|
+
n : int
|
|
142
|
+
Number of top tickers to return. Required.
|
|
143
|
+
brokers : List[str], optional
|
|
144
|
+
List of broker names to filter.
|
|
145
|
+
side : str, optional
|
|
146
|
+
Filter by side ('buy' or 'sell').
|
|
147
|
+
|
|
148
|
+
Returns
|
|
149
|
+
-------
|
|
150
|
+
dict
|
|
151
|
+
JSON response with top tickers and their top brokers breakdown.
|
|
152
|
+
"""
|
|
153
|
+
params = {"n": str(n)}
|
|
154
|
+
|
|
155
|
+
if brokers: params["brokers"] = ",".join(brokers)
|
|
156
|
+
if side is not None: params["side"] = side
|
|
157
|
+
|
|
158
|
+
url = self._build_url("top-tickers")
|
|
159
|
+
response = requests.get(url, params=params, headers=self.headers, timeout=30)
|
|
160
|
+
|
|
161
|
+
if response.status_code != 200:
|
|
162
|
+
self._raise_error(response)
|
|
163
|
+
|
|
164
|
+
return response.json()
|
|
165
|
+
|
|
166
|
+
@staticmethod
|
|
167
|
+
def _raise_error(response):
|
|
168
|
+
try:
|
|
169
|
+
error_body = response.json()
|
|
170
|
+
detail = error_body.get("detail", error_body.get("error", response.text))
|
|
171
|
+
except Exception:
|
|
172
|
+
detail = response.text
|
|
173
|
+
raise BadResponse(f"Error {response.status_code}: {detail}")
|
|
@@ -74,7 +74,7 @@ class HistoricalCandles:
|
|
|
74
74
|
Parameters
|
|
75
75
|
----------------
|
|
76
76
|
market_type: str
|
|
77
|
-
Field is required. Allowed values: 'stocks' or '
|
|
77
|
+
Field is required. Allowed values: 'stocks', 'derivatives' or 'indices'.
|
|
78
78
|
ticker: str
|
|
79
79
|
Ticker that needs to be returned.
|
|
80
80
|
Field is required. Example: 'PETR4'.
|
|
@@ -103,8 +103,8 @@ class HistoricalCandles:
|
|
|
103
103
|
Field is not required. Default: True.
|
|
104
104
|
"""
|
|
105
105
|
|
|
106
|
-
if market_type not in ['stocks', 'derivatives']:
|
|
107
|
-
raise MarketTypeError(f'Allowed values: "stocks" or "
|
|
106
|
+
if market_type not in ['stocks', 'derivatives', 'indices']:
|
|
107
|
+
raise MarketTypeError(f'Allowed values: "stocks", "derivatives" or "indices". Input value: "{market_type}".')
|
|
108
108
|
|
|
109
109
|
url = f"{url_apis_v3}/marketdata/history/candles/intraday/{market_type}?ticker={ticker}&corporate_events_adj={corporate_events_adj}&rmv_after_market={rmv_after_market}&timezone={timezone}&date={date}&candle={candle}&round={round}"
|
|
110
110
|
response = requests.request("GET", url, headers=self.headers)
|
|
@@ -133,7 +133,7 @@ class HistoricalCandles:
|
|
|
133
133
|
Parameters
|
|
134
134
|
----------------
|
|
135
135
|
market_type: str
|
|
136
|
-
Field is required. Allowed values: 'stocks' or '
|
|
136
|
+
Field is required. Allowed values: 'stocks', 'derivatives' or 'indices'.
|
|
137
137
|
ticker: str
|
|
138
138
|
Ticker that needs to be returned.
|
|
139
139
|
Field is required. Example: 'PETR4'.
|
|
@@ -162,8 +162,8 @@ class HistoricalCandles:
|
|
|
162
162
|
Field is not required. Default: True.
|
|
163
163
|
"""
|
|
164
164
|
|
|
165
|
-
if market_type not in ['stocks', 'derivatives']:
|
|
166
|
-
raise MarketTypeError(f'Allowed values: "stocks" or "
|
|
165
|
+
if market_type not in ['stocks', 'derivatives', 'indices']:
|
|
166
|
+
raise MarketTypeError(f'Allowed values: "stocks", "derivatives" or "indices". Input value: "{market_type}".')
|
|
167
167
|
|
|
168
168
|
url = f"{url_apis_v3}/marketdata/history/candles/interday/{market_type}?ticker={ticker}&corporate_events_adj={corporate_events_adj}&rmv_after_market={rmv_after_market}&timezone={timezone}&start_date={start_date}&end_date={end_date}&round={round}"
|
|
169
169
|
response = requests.request("GET", url, headers=self.headers)
|
|
@@ -174,6 +174,66 @@ class HistoricalCandles:
|
|
|
174
174
|
response = json.loads(response.text)
|
|
175
175
|
raise BadResponse(f'Error: {response.get("ApiClientError", "")}.\n{response.get("SuggestedAction", "")}')
|
|
176
176
|
|
|
177
|
+
def get_interday_history_candles_batch(
|
|
178
|
+
self,
|
|
179
|
+
market_type:str,
|
|
180
|
+
tickers:list,
|
|
181
|
+
start_date:str,
|
|
182
|
+
end_date:str,
|
|
183
|
+
corporate_events_adj:bool,
|
|
184
|
+
rmv_after_market:bool,
|
|
185
|
+
timezone:str,
|
|
186
|
+
raw_data:bool=False,
|
|
187
|
+
round:bool=True
|
|
188
|
+
):
|
|
189
|
+
"""
|
|
190
|
+
This method provides historical candles for a batch of tickers in determined period.
|
|
191
|
+
|
|
192
|
+
Parameters
|
|
193
|
+
----------------
|
|
194
|
+
market_type: str
|
|
195
|
+
Field is required. Allowed values: 'stocks' or 'derivatives'.
|
|
196
|
+
tickers: list
|
|
197
|
+
Tickers that need to be returned.
|
|
198
|
+
Field is required. Example: ['PETR4', 'VALE3'].
|
|
199
|
+
start_date: string<date>
|
|
200
|
+
Start date of analysis. Format: "YYYY-MM-DD".
|
|
201
|
+
Field is required. Example: '2022-10-06'.
|
|
202
|
+
end_date: string<date>
|
|
203
|
+
End date of analysis. Format: "YYYY-MM-DD".
|
|
204
|
+
Field is required. Example: '2023-01-22'.
|
|
205
|
+
corporate_events_adj: bool
|
|
206
|
+
Corporate events adjustment.
|
|
207
|
+
Field is required. Allowed values: 'true' or 'false'.
|
|
208
|
+
rmv_after_market: bool
|
|
209
|
+
Remove trades after market close.
|
|
210
|
+
Field is required. Allowed values: 'true' or 'false'.
|
|
211
|
+
timezone: str
|
|
212
|
+
Timezone of the datetime.
|
|
213
|
+
Field is required. Allowed values: 'America/Sao_Paulo' or 'UTC'.
|
|
214
|
+
raw_data: bool
|
|
215
|
+
If false, returns data in a dataframe. If true, returns raw data.
|
|
216
|
+
Field is not required. Default: False.
|
|
217
|
+
round: bool
|
|
218
|
+
Apply rounding to prices.
|
|
219
|
+
Useful when corporate_events_adj=True, since price adjustment factors
|
|
220
|
+
may generate values with many decimal places.
|
|
221
|
+
Field is not required. Default: True.
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
if market_type not in ['stocks', 'derivatives']:
|
|
225
|
+
raise MarketTypeError(f'Allowed values: "stocks" or "derivatives". Input value: "{market_type}".')
|
|
226
|
+
|
|
227
|
+
url = f"{url_apis_v3}/marketdata/history/candles/interday/{market_type}/batch?corporate_events_adj={corporate_events_adj}&rmv_after_market={rmv_after_market}&timezone={timezone}&start_date={start_date}&end_date={end_date}&round={round}"
|
|
228
|
+
payload = {"tickers": tickers}
|
|
229
|
+
response = requests.request("POST", url, headers=self.headers, json=payload)
|
|
230
|
+
if response.status_code == 200:
|
|
231
|
+
response_data = json.loads(response.text)
|
|
232
|
+
return response_data if raw_data else pd.DataFrame(pd.concat([pd.DataFrame(v) for v in response_data.values()], ignore_index=True))
|
|
233
|
+
|
|
234
|
+
response = json.loads(response.text)
|
|
235
|
+
raise BadResponse(f'Error: {response.get("ApiClientError", "")}.\n{response.get("SuggestedAction", "")}')
|
|
236
|
+
|
|
177
237
|
def get_available_tickers(
|
|
178
238
|
self,
|
|
179
239
|
market_type:str,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
2
|
Name: btgsolutions_dataservices_python_client
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.12
|
|
4
4
|
Summary: Python package containing several classes and data for extracting and manipulating market and trading data.
|
|
5
5
|
Home-page: https://github.com/BTG-Pactual-Solutions/btgsolutions-dataservices-python-client
|
|
6
6
|
Author: BTG Solutions Data Services powered by BTG Pactual Solutions
|
|
@@ -12,14 +12,6 @@ Requires-Dist: websocket-client>=1.8.0
|
|
|
12
12
|
Requires-Dist: PyJWT>=2.8.0
|
|
13
13
|
Requires-Dist: requests>=2.32.3
|
|
14
14
|
Requires-Dist: pyarrow>=17.0.0
|
|
15
|
-
Dynamic: author
|
|
16
|
-
Dynamic: description
|
|
17
|
-
Dynamic: description-content-type
|
|
18
|
-
Dynamic: home-page
|
|
19
|
-
Dynamic: license-file
|
|
20
|
-
Dynamic: requires-dist
|
|
21
|
-
Dynamic: requires-python
|
|
22
|
-
Dynamic: summary
|
|
23
15
|
|
|
24
16
|
# BTG Solutions - Data Services
|
|
25
17
|
|
|
@@ -280,6 +272,14 @@ hist_candles = btg.HistoricalCandles(api_key='YOUR_API_KEY')
|
|
|
280
272
|
hist_candles.get_intraday_history_candles(ticker='PETR4', market_type='stocks', corporate_events_adj=True, date='2023-10-06', candle='1m', rmv_after_market=True, timezone='UTC', raw_data=False, round=True)
|
|
281
273
|
```
|
|
282
274
|
|
|
275
|
+
##### Interday Batch
|
|
276
|
+
|
|
277
|
+
```python
|
|
278
|
+
import btgsolutions_dataservices as btg
|
|
279
|
+
hist_candles = btg.HistoricalCandles(api_key='YOUR_API_KEY')
|
|
280
|
+
hist_candles.get_interday_history_candles_batch(market_type='stocks', tickers=['PETR4', 'VALE3'], start_date='2023-10-01', end_date='2023-10-13', corporate_events_adj=True, rmv_after_market=True, timezone='UTC', raw_data=False, round=True)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
283
|
##### Available Tickers
|
|
284
284
|
|
|
285
285
|
```python
|
|
@@ -503,6 +503,40 @@ broker_reference = btg.BrokerReference(api_key='YOUR_API_KEY')
|
|
|
503
503
|
broker_reference.get()
|
|
504
504
|
```
|
|
505
505
|
|
|
506
|
+
#### Book Scope
|
|
507
|
+
|
|
508
|
+
```python
|
|
509
|
+
import btgsolutions_dataservices as btg
|
|
510
|
+
book_scope = btg.BookScope(api_key='YOUR_API_KEY')
|
|
511
|
+
|
|
512
|
+
result = book_scope.get(
|
|
513
|
+
symbol='DOLM26',
|
|
514
|
+
market_type='derivatives',
|
|
515
|
+
start_time='2026-05-28T14:12:00Z',
|
|
516
|
+
end_time='2026-05-28T14:15:00Z',
|
|
517
|
+
select=['trades', 'book_snapshot', 'book_incremental'], # choose one, two, or all three
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
single_file_result = book_scope.get(
|
|
521
|
+
symbol='DOLM26',
|
|
522
|
+
market_type='derivatives',
|
|
523
|
+
start_time='2026-05-28T14:12:00Z',
|
|
524
|
+
end_time='2026-05-28T14:15:00Z',
|
|
525
|
+
select=['trades', 'book_snapshot', 'book_incremental'],
|
|
526
|
+
aggregate_info=True,
|
|
527
|
+
)
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
#### Broker Analytics
|
|
531
|
+
|
|
532
|
+
```python
|
|
533
|
+
import btgsolutions_dataservices as btg
|
|
534
|
+
broker_analytics = btg.BrokerAnalytics(api_key='YOUR_API_KEY', market_type='stocks')
|
|
535
|
+
summary = broker_analytics.get_summary(brokers=['85', '3'], tickers=['PETR4', 'ABCB4'])
|
|
536
|
+
top_brokers = broker_analytics.get_top_brokers(n=10)
|
|
537
|
+
top_tickers = broker_analytics.get_top_tickers(n=10, brokers=['85', '3'])
|
|
538
|
+
```
|
|
539
|
+
|
|
506
540
|
#### Ticker Reference Data
|
|
507
541
|
|
|
508
542
|
```python
|
|
@@ -9,6 +9,8 @@ btgsolutions_dataservices/config.py
|
|
|
9
9
|
btgsolutions_dataservices/exceptions.py
|
|
10
10
|
btgsolutions_dataservices/rest/__init__.py
|
|
11
11
|
btgsolutions_dataservices/rest/authenticator.py
|
|
12
|
+
btgsolutions_dataservices/rest/book_scope.py
|
|
13
|
+
btgsolutions_dataservices/rest/broker_analytics.py
|
|
12
14
|
btgsolutions_dataservices/rest/broker_reference.py
|
|
13
15
|
btgsolutions_dataservices/rest/bulk_data.py
|
|
14
16
|
btgsolutions_dataservices/rest/company_data.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|