btgsolutions-dataservices-python-client 2.11.4__py3-none-any.whl → 2.17.1__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.
- btgsolutions_dataservices/config.py +8 -1
- btgsolutions_dataservices/rest/__init__.py +3 -1
- btgsolutions_dataservices/rest/intraday_candles.py +10 -1
- btgsolutions_dataservices/rest/stock_loan.py +110 -0
- btgsolutions_dataservices/rest/ticker_last_event.py +96 -11
- btgsolutions_dataservices/rest/ticker_last_event_polling.py +149 -0
- btgsolutions_dataservices/websocket/market_data_feed.py +111 -14
- btgsolutions_dataservices/websocket/market_data_websocket_client.py +15 -8
- {btgsolutions_dataservices_python_client-2.11.4.dist-info → btgsolutions_dataservices_python_client-2.17.1.dist-info}/METADATA +54 -4
- {btgsolutions_dataservices_python_client-2.11.4.dist-info → btgsolutions_dataservices_python_client-2.17.1.dist-info}/RECORD +13 -11
- {btgsolutions_dataservices_python_client-2.11.4.dist-info → btgsolutions_dataservices_python_client-2.17.1.dist-info}/WHEEL +0 -0
- {btgsolutions_dataservices_python_client-2.11.4.dist-info → btgsolutions_dataservices_python_client-2.17.1.dist-info}/licenses/LICENSE +0 -0
- {btgsolutions_dataservices_python_client-2.11.4.dist-info → btgsolutions_dataservices_python_client-2.17.1.dist-info}/top_level.txt +0 -0
|
@@ -26,6 +26,7 @@ SECURITIES = 'securities'
|
|
|
26
26
|
TRADES = 'trades'
|
|
27
27
|
PROCESSEDTRADES = 'processed-trades'
|
|
28
28
|
INSTRUMENTSTATUS = 'instrument_status'
|
|
29
|
+
SETTLEMENTPRICES = 'settlement-price'
|
|
29
30
|
BOOKS = 'books'
|
|
30
31
|
INDICES = 'indices'
|
|
31
32
|
CANDLES1S = 'candles-1S'
|
|
@@ -49,7 +50,8 @@ VALID_MARKET_DATA_TYPES = [
|
|
|
49
50
|
CANDLES1S,
|
|
50
51
|
CANDLES1M,
|
|
51
52
|
STOPLOSS,
|
|
52
|
-
INSTRUMENTSTATUS
|
|
53
|
+
INSTRUMENTSTATUS,
|
|
54
|
+
SETTLEMENTPRICES
|
|
53
55
|
]
|
|
54
56
|
VALID_MARKET_DATA_SUBTYPES = [ALL, STOCKS, OPTIONS, DERIVATIVES]
|
|
55
57
|
|
|
@@ -134,6 +136,11 @@ market_data_socket_urls = {
|
|
|
134
136
|
DERIVATIVES: f"{url_ws}v2/marketdata/{INSTRUMENTSTATUS}/{DERIVATIVES}",
|
|
135
137
|
OPTIONS: f"{url_ws}v2/marketdata/{INSTRUMENTSTATUS}/{OPTIONS}",
|
|
136
138
|
}
|
|
139
|
+
},
|
|
140
|
+
SETTLEMENTPRICES: {
|
|
141
|
+
REALTIME: {
|
|
142
|
+
ALL: f"{url_ws}v2/marketdata/{SETTLEMENTPRICES}",
|
|
143
|
+
}
|
|
137
144
|
}
|
|
138
145
|
},
|
|
139
146
|
BMV: {
|
|
@@ -9,4 +9,6 @@ from .ticker_last_event import TickerLastEvent
|
|
|
9
9
|
from .corporate_events import CorporateEvents
|
|
10
10
|
from .company_data import CompanyData
|
|
11
11
|
from .public_sources import PublicSources
|
|
12
|
-
from .reference_data import ReferenceData
|
|
12
|
+
from .reference_data import ReferenceData
|
|
13
|
+
from .stock_loan import StockLoan
|
|
14
|
+
from .ticker_last_event_polling import TickerLastEventPolling
|
|
@@ -58,7 +58,8 @@ class IntradayCandles:
|
|
|
58
58
|
start:int=0,
|
|
59
59
|
end:int=0,
|
|
60
60
|
mode:str='absolute',
|
|
61
|
-
raw_data:bool=False
|
|
61
|
+
raw_data:bool=False,
|
|
62
|
+
cross_filter:str='',
|
|
62
63
|
):
|
|
63
64
|
"""
|
|
64
65
|
This method provides realtime intraday candles for a given ticker.
|
|
@@ -93,6 +94,10 @@ class IntradayCandles:
|
|
|
93
94
|
Candle mode.
|
|
94
95
|
Example: 'absolute', 'relative' or 'spark'.
|
|
95
96
|
Default: absolute.
|
|
97
|
+
cross_filter: str
|
|
98
|
+
Filter trades by cross status. Available only when market_type is derivatives.
|
|
99
|
+
Options: 'all', 'only_cross' or 'without_cross'.
|
|
100
|
+
Default: 'all'.
|
|
96
101
|
raw_data: bool
|
|
97
102
|
If false, returns data in a dict of dataframes. If true, returns raw data.
|
|
98
103
|
Default: False.
|
|
@@ -101,6 +106,8 @@ class IntradayCandles:
|
|
|
101
106
|
if market_type not in ['stocks', 'derivatives', 'options', 'indices']: raise MarketTypeError(f"Must provide a valid 'market_type' parameter. Input: '{market_type}'. Accepted values: 'stocks', 'derivatives', 'options' or 'indices'.")
|
|
102
107
|
|
|
103
108
|
if delay not in ['delayed', 'realtime']: raise DelayError(f"Must provide a valid 'delay' parameter. Input: '{delay}'. Accepted values: 'delayed' or 'realtime'.")
|
|
109
|
+
|
|
110
|
+
if cross_filter and market_type != 'derivatives': raise DelayError(f"The 'cross_filter' flag is only valid for market_type 'derivatives'.")
|
|
104
111
|
|
|
105
112
|
tickers = ','.join(tickers) if type(tickers) is list else tickers
|
|
106
113
|
|
|
@@ -109,6 +116,8 @@ class IntradayCandles:
|
|
|
109
116
|
if start: url += f'&start={start}'
|
|
110
117
|
|
|
111
118
|
if end: url += f'&end={end}'
|
|
119
|
+
|
|
120
|
+
if cross_filter: url += f'&cross_filter={cross_filter}'
|
|
112
121
|
|
|
113
122
|
response = requests.request("GET", url, headers=self.headers)
|
|
114
123
|
if response.status_code == 200:
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from ..exceptions import BadResponse
|
|
4
|
+
import requests
|
|
5
|
+
from ..config import url_api_v1
|
|
6
|
+
import json
|
|
7
|
+
from .authenticator import Authenticator
|
|
8
|
+
import pandas as pd
|
|
9
|
+
|
|
10
|
+
class StockLoan:
|
|
11
|
+
"""
|
|
12
|
+
This class provides trades related to stock loan operations.
|
|
13
|
+
|
|
14
|
+
* Main use case:
|
|
15
|
+
|
|
16
|
+
>>> from btgsolutions_dataservices import StockLoan
|
|
17
|
+
>>> stock_loan = StockLoan(
|
|
18
|
+
>>> api_key='YOUR_API_KEY',
|
|
19
|
+
>>> )
|
|
20
|
+
>>> trades = stock_loan.get_trades(
|
|
21
|
+
>>> ticker = 'PETR4'
|
|
22
|
+
>>> )
|
|
23
|
+
>>> trades = stock_loan.get_paginated_trades(
|
|
24
|
+
>>> page = 1,
|
|
25
|
+
>>> limit = 1000
|
|
26
|
+
>>> )
|
|
27
|
+
>>> stock_loan.get_available_tickers()
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------------
|
|
31
|
+
api_key: str
|
|
32
|
+
User identification key.
|
|
33
|
+
Field is required.
|
|
34
|
+
"""
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
api_key: Optional[str]
|
|
38
|
+
):
|
|
39
|
+
self.api_key = api_key
|
|
40
|
+
self.token = Authenticator(self.api_key).token
|
|
41
|
+
self.headers = {"authorization": f"authorization {self.token}"}
|
|
42
|
+
|
|
43
|
+
def get_trades(
|
|
44
|
+
self,
|
|
45
|
+
ticker:Optional[str]='',
|
|
46
|
+
):
|
|
47
|
+
"""
|
|
48
|
+
Returns trades related to stock loan operations.
|
|
49
|
+
|
|
50
|
+
Parameters
|
|
51
|
+
----------------
|
|
52
|
+
ticker : str, optional
|
|
53
|
+
The ticker symbol to be returned.
|
|
54
|
+
Example: 'PETR4'
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
page = 1
|
|
58
|
+
df = pd.DataFrame()
|
|
59
|
+
while True:
|
|
60
|
+
url = f"{url_api_v1}/marketdata/stock-loan/daily-trades?page={page}&limit=20000"
|
|
61
|
+
if ticker: url += f'&ticker={ticker}'
|
|
62
|
+
response = requests.request("GET", url, headers=self.headers)
|
|
63
|
+
if response.status_code != 200: raise BadResponse(response.json())
|
|
64
|
+
response = response.json()
|
|
65
|
+
df = pd.concat([df, pd.DataFrame(response['data'])])
|
|
66
|
+
if response['totalPages'] <= page: break
|
|
67
|
+
page += 1
|
|
68
|
+
|
|
69
|
+
return df.reset_index(drop=True)
|
|
70
|
+
|
|
71
|
+
def get_paginated_trades(
|
|
72
|
+
self,
|
|
73
|
+
page:int,
|
|
74
|
+
limit:int,
|
|
75
|
+
ticker:Optional[str]='',
|
|
76
|
+
):
|
|
77
|
+
"""
|
|
78
|
+
Returns paginated trades related to stock loan operations.
|
|
79
|
+
|
|
80
|
+
Parameters
|
|
81
|
+
----------------
|
|
82
|
+
ticker : str, optional
|
|
83
|
+
The ticker symbol to be returned.
|
|
84
|
+
Example: 'PETR4'
|
|
85
|
+
|
|
86
|
+
page : int
|
|
87
|
+
Page number for paginated results.
|
|
88
|
+
Example: 1
|
|
89
|
+
|
|
90
|
+
limit : int
|
|
91
|
+
Maximum number of items to return per page.
|
|
92
|
+
Example: 1000
|
|
93
|
+
|
|
94
|
+
"""
|
|
95
|
+
url = f"{url_api_v1}/marketdata/stock-loan/daily-trades?page={page}&limit={limit}"
|
|
96
|
+
if ticker: url += f'&ticker={ticker}'
|
|
97
|
+
response = requests.request("GET", url, headers=self.headers)
|
|
98
|
+
if response.status_code == 200: return json.loads(response.text)
|
|
99
|
+
raise BadResponse(response.json())
|
|
100
|
+
|
|
101
|
+
def get_available_tickers(
|
|
102
|
+
self,
|
|
103
|
+
):
|
|
104
|
+
"""
|
|
105
|
+
This method provides all tickers available for query.
|
|
106
|
+
"""
|
|
107
|
+
url = f"{url_api_v1}/marketdata/stock-loan/available-tickers"
|
|
108
|
+
response = requests.request("GET", url, headers=self.headers)
|
|
109
|
+
if response.status_code == 200: return json.loads(response.text)
|
|
110
|
+
raise BadResponse(response.json())
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Optional
|
|
1
|
+
from typing import Optional, List
|
|
2
2
|
from ..exceptions import BadResponse
|
|
3
3
|
import requests
|
|
4
4
|
from ..config import url_api_v1
|
|
@@ -18,10 +18,16 @@ class TickerLastEvent:
|
|
|
18
18
|
>>> )
|
|
19
19
|
|
|
20
20
|
>>> last_event.get_trades(
|
|
21
|
+
>>> data_type = 'equities',
|
|
21
22
|
>>> ticker = 'PETR4',
|
|
22
23
|
>>> raw_data = False
|
|
23
24
|
>>> )
|
|
24
25
|
|
|
26
|
+
>>> last_event.get_tob(
|
|
27
|
+
>>> data_type = 'stocks',
|
|
28
|
+
>>> raw_data = False
|
|
29
|
+
>>> )
|
|
30
|
+
|
|
25
31
|
Parameters
|
|
26
32
|
----------------
|
|
27
33
|
api_key: str
|
|
@@ -33,10 +39,12 @@ class TickerLastEvent:
|
|
|
33
39
|
api_key:Optional[str]
|
|
34
40
|
):
|
|
35
41
|
self.api_key = api_key
|
|
36
|
-
self.
|
|
37
|
-
self.headers = {"authorization": f"authorization {self.token}"}
|
|
42
|
+
self.authenticator = Authenticator(self.api_key)
|
|
38
43
|
|
|
39
|
-
self.available_data_types =
|
|
44
|
+
self.available_data_types = {
|
|
45
|
+
"trades": ['equities', 'derivatives'],
|
|
46
|
+
"books": ['stocks', 'derivatives', 'options']
|
|
47
|
+
}
|
|
40
48
|
|
|
41
49
|
def get_trades(self, data_type:str, ticker:str, raw_data:bool=False):
|
|
42
50
|
|
|
@@ -56,12 +64,12 @@ class TickerLastEvent:
|
|
|
56
64
|
If false, returns data in a dataframe. If true, returns raw data.
|
|
57
65
|
Field is not required. Default: False.
|
|
58
66
|
"""
|
|
59
|
-
if data_type not in self.available_data_types:
|
|
60
|
-
raise Exception("Must provide a valid data_type. Valid data types are: {self.available_data_types}")
|
|
67
|
+
if data_type not in self.available_data_types["trades"]:
|
|
68
|
+
raise Exception(f"Must provide a valid data_type. Valid data types are: {self.available_data_types['trades']}")
|
|
61
69
|
|
|
62
70
|
url = f"{url_api_v1}/marketdata/last-event/trades/{data_type}?ticker={ticker}"
|
|
63
71
|
|
|
64
|
-
response = requests.request("GET", url, headers=self.
|
|
72
|
+
response = requests.request("GET", url, headers={"authorization": f"Bearer {self.authenticator.token}"})
|
|
65
73
|
if response.status_code == 200:
|
|
66
74
|
if raw_data:
|
|
67
75
|
return response.json()
|
|
@@ -70,23 +78,100 @@ class TickerLastEvent:
|
|
|
70
78
|
else:
|
|
71
79
|
response = json.loads(response.text)
|
|
72
80
|
raise BadResponse(f'Error: {response.get("error", "")}')
|
|
81
|
+
|
|
82
|
+
def get_tobs(self, data_type:str, raw_data:bool=False):
|
|
83
|
+
|
|
84
|
+
"""
|
|
85
|
+
This method provides the last available top of book for all tickers of the given market type.
|
|
86
|
+
|
|
87
|
+
Parameters
|
|
88
|
+
----------------
|
|
89
|
+
data_type: str
|
|
90
|
+
Market Data Type.
|
|
91
|
+
Field is required.
|
|
92
|
+
Example: 'stocks', 'derivatives', 'options'.
|
|
93
|
+
raw_data: bool
|
|
94
|
+
If false, returns data in a dataframe. If true, returns raw data.
|
|
95
|
+
Field is not required. Default: False.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
if data_type not in self.available_data_types["books"]:
|
|
99
|
+
raise Exception(f"Must provide a valid data_type. Valid data types are: {self.available_data_types}")
|
|
100
|
+
|
|
101
|
+
url = f"{url_api_v1}/marketdata/last-event/books/top/{data_type}/batch"
|
|
102
|
+
|
|
103
|
+
response = requests.request("GET", url, headers={"authorization": f"Bearer {self.authenticator.token}"})
|
|
104
|
+
|
|
105
|
+
if response.status_code == 200:
|
|
106
|
+
if raw_data:
|
|
107
|
+
return response.json()
|
|
108
|
+
else:
|
|
109
|
+
return pd.DataFrame(response.json())
|
|
110
|
+
else:
|
|
111
|
+
|
|
112
|
+
response = json.loads(response.text)
|
|
113
|
+
raise BadResponse(f'Error: {response.get("error", "")}')
|
|
73
114
|
|
|
74
|
-
def
|
|
115
|
+
def get_status(self, tickers:List[str]=None, raw_data:bool=False):
|
|
116
|
+
|
|
117
|
+
"""
|
|
118
|
+
This method provides the current ticker trading status information.
|
|
119
|
+
|
|
120
|
+
Parameters
|
|
121
|
+
----------------
|
|
122
|
+
tickers: List[str]
|
|
123
|
+
Ticker symbol.
|
|
124
|
+
Field is not required. Default: None
|
|
125
|
+
Example: ['PETR4', 'VALE3', 'DOLM25'].
|
|
126
|
+
If no ticker is provided, it returns trading status information for all available tickers.
|
|
127
|
+
raw_data: bool
|
|
128
|
+
If false, returns data in a dataframe. If true, returns raw data.
|
|
129
|
+
Field is not required. Default: False.
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
if tickers:
|
|
133
|
+
tickers = ','.join(tickers)
|
|
134
|
+
url = f"{url_api_v1}/marketdata/last-event/status/all/batch?tickers={tickers}"
|
|
135
|
+
else:
|
|
136
|
+
url = f"{url_api_v1}/marketdata/last-event/status/all/batch"
|
|
137
|
+
|
|
138
|
+
response = requests.request("GET", url, headers={"authorization": f"Bearer {self.authenticator.token}"})
|
|
139
|
+
if response.status_code == 200:
|
|
140
|
+
if raw_data:
|
|
141
|
+
return response.json()
|
|
142
|
+
else:
|
|
143
|
+
return pd.DataFrame(response.json())
|
|
144
|
+
else:
|
|
145
|
+
response = json.loads(response.text)
|
|
146
|
+
raise BadResponse(f'Error: {response}')
|
|
147
|
+
|
|
148
|
+
def get_available_tickers(self,type:str, data_type:str):
|
|
75
149
|
|
|
76
150
|
"""
|
|
77
151
|
This method provides all the available tickers for the specific data type.
|
|
78
152
|
|
|
79
153
|
Parameters
|
|
80
154
|
----------------
|
|
155
|
+
type: str
|
|
156
|
+
Data Type.
|
|
157
|
+
Field is required.
|
|
158
|
+
Example: 'trades', 'books'
|
|
81
159
|
data_type: str
|
|
82
160
|
Market Data Type.
|
|
83
161
|
Field is required.
|
|
84
|
-
Example: 'equities', 'derivatives'.
|
|
162
|
+
Example: 'equities', 'derivatives', 'options', 'stocks'.
|
|
85
163
|
"""
|
|
86
164
|
|
|
87
|
-
|
|
165
|
+
if type not in self.available_data_types:
|
|
166
|
+
raise Exception(f"Must provide a valid type. Valid data types are: {list(self.available_data_types.keys())}")
|
|
167
|
+
|
|
168
|
+
if data_type not in self.available_data_types[type]:
|
|
169
|
+
raise Exception(f"Must provide a valid data_type. Valid data types are: {self.available_data_types['books']}")
|
|
170
|
+
|
|
171
|
+
url = f"{url_api_v1}/marketdata/last-event/trades/{data_type}/available-tickers" if type == "trades" else \
|
|
172
|
+
f"{url_api_v1}/marketdata/last-event/books/{data_type}/availables"
|
|
88
173
|
|
|
89
|
-
response = requests.request("GET", url, headers=self.
|
|
174
|
+
response = requests.request("GET", url, headers={"authorization": f"Bearer {self.authenticator.token}"})
|
|
90
175
|
if response.status_code == 200:
|
|
91
176
|
return response.json()
|
|
92
177
|
else:
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import time
|
|
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
|
+
import pandas as pd
|
|
8
|
+
import json
|
|
9
|
+
from datetime import datetime, timezone, timedelta
|
|
10
|
+
import threading
|
|
11
|
+
|
|
12
|
+
class TickerLastEventPolling:
|
|
13
|
+
"""
|
|
14
|
+
This class continuously polls and caches the latest ticker market data in the background for quick and up-to-date access.
|
|
15
|
+
|
|
16
|
+
* Main use case:
|
|
17
|
+
|
|
18
|
+
>>> from btgsolutions_dataservices import TickerLastEventPolling
|
|
19
|
+
>>> last_event = TickerLastEventPolling(
|
|
20
|
+
>>> api_key='YOUR_API_KEY',
|
|
21
|
+
>>> data_type='top-of-books',
|
|
22
|
+
>>> data_subtype='stocks',
|
|
23
|
+
>>> )
|
|
24
|
+
|
|
25
|
+
>>> last_event.get(
|
|
26
|
+
>>> raw_data=False
|
|
27
|
+
>>> )
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------------
|
|
31
|
+
api_key: str
|
|
32
|
+
User identification key.
|
|
33
|
+
Field is required.
|
|
34
|
+
data_type: str
|
|
35
|
+
Market Data type.
|
|
36
|
+
Options: 'top-of-books'
|
|
37
|
+
Field is required.
|
|
38
|
+
data_subtype: str
|
|
39
|
+
Market Data subtype.
|
|
40
|
+
Options: 'stocks', 'options', 'derivatives'.
|
|
41
|
+
Field is required.
|
|
42
|
+
"""
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
api_key:Optional[str],
|
|
46
|
+
data_type: str,
|
|
47
|
+
data_subtype: str,
|
|
48
|
+
interval_seconds: float = 1.0
|
|
49
|
+
):
|
|
50
|
+
self.api_key = api_key
|
|
51
|
+
self.authenticator = Authenticator(self.api_key)
|
|
52
|
+
self.interval_seconds = interval_seconds
|
|
53
|
+
self.data_type = data_type
|
|
54
|
+
|
|
55
|
+
self._last_request_datetime = None
|
|
56
|
+
self._cache = {}
|
|
57
|
+
|
|
58
|
+
self._available_data_types = {
|
|
59
|
+
"top-of-books": ['stocks', 'derivatives', 'options']
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
self._available_url = {
|
|
63
|
+
"top-of-books": {
|
|
64
|
+
"data": f"{url_api_v1}/marketdata/last-event/books/top/{data_subtype}/batch",
|
|
65
|
+
"available": f"{url_api_v1}/marketdata/last-event/books/{data_subtype}/availables"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if data_type not in self._available_data_types:
|
|
70
|
+
raise Exception(f"Must provide a valid data_type. Valid data types are: {self._available_data_types}")
|
|
71
|
+
|
|
72
|
+
if data_subtype not in self._available_data_types[data_type]:
|
|
73
|
+
raise Exception(f"Must provide a valid data_subtype. Valid data subtypes are: {self._available_data_types[data_type]}")
|
|
74
|
+
|
|
75
|
+
self.url = self._available_url[self.data_type]["data"]
|
|
76
|
+
|
|
77
|
+
self._update_data()
|
|
78
|
+
|
|
79
|
+
threading.Thread(target=self._polling_loop, daemon=True).start()
|
|
80
|
+
|
|
81
|
+
def _polling_loop(self):
|
|
82
|
+
|
|
83
|
+
while True:
|
|
84
|
+
try:
|
|
85
|
+
self._update_data()
|
|
86
|
+
except Exception as e:
|
|
87
|
+
print("error on updating data:", e)
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
time.sleep(self.interval_seconds)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _update_data(self):
|
|
94
|
+
url = self.url + (f"?dt={(self._last_request_datetime - timedelta(seconds=60)).strftime('%Y-%m-%dT%H:%M:%S.000Z')}" if self._last_request_datetime else "")
|
|
95
|
+
|
|
96
|
+
request_datetime = datetime.now(timezone.utc)
|
|
97
|
+
|
|
98
|
+
response = requests.request("GET", url, headers={"authorization": f"Bearer {self.authenticator.token}"})
|
|
99
|
+
|
|
100
|
+
if response.status_code != 200:
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
self._last_request_datetime = request_datetime
|
|
105
|
+
|
|
106
|
+
new_data = { tob["sb"]: tob for tob in response.json() if tob.get("sb")}
|
|
107
|
+
|
|
108
|
+
self._cache.update(new_data)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def get(self, force_update: bool=False, raw_data:bool=False):
|
|
112
|
+
|
|
113
|
+
"""
|
|
114
|
+
This method provides the last events for all tickers of the given data type and data subtype.
|
|
115
|
+
|
|
116
|
+
Parameters
|
|
117
|
+
----------------
|
|
118
|
+
force_update: bool
|
|
119
|
+
If true, forces an update before returning the data. If false, returns the data.
|
|
120
|
+
Field is not required. Default: False.
|
|
121
|
+
raw_data: bool
|
|
122
|
+
If false, returns data in a dataframe. If true, returns raw data.
|
|
123
|
+
Field is not required. Default: False.
|
|
124
|
+
"""
|
|
125
|
+
if force_update:
|
|
126
|
+
self._update_data()
|
|
127
|
+
|
|
128
|
+
if raw_data:
|
|
129
|
+
return list(self._cache.values())
|
|
130
|
+
else:
|
|
131
|
+
return pd.DataFrame(self._cache.values())
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def get_available_tickers(self):
|
|
136
|
+
|
|
137
|
+
"""
|
|
138
|
+
This method provides all the available tickers for the specific data type and data subtype.
|
|
139
|
+
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
url = self._available_url[self.data_type]["available"]
|
|
143
|
+
|
|
144
|
+
response = requests.request("GET", url, headers={"authorization": f"Bearer {self.authenticator.token}"})
|
|
145
|
+
if response.status_code == 200:
|
|
146
|
+
return response.json()
|
|
147
|
+
else:
|
|
148
|
+
response = json.loads(response.text)
|
|
149
|
+
raise BadResponse(f'Error: {response.get("error", "")}')
|
|
@@ -1,19 +1,30 @@
|
|
|
1
1
|
from typing import Optional, Callable, List
|
|
2
2
|
import websocket
|
|
3
3
|
import time
|
|
4
|
-
from datetime import date
|
|
4
|
+
from datetime import date, datetime
|
|
5
5
|
import multiprocessing
|
|
6
6
|
import logging
|
|
7
7
|
from logging.handlers import QueueHandler, QueueListener
|
|
8
8
|
import json
|
|
9
9
|
import ssl
|
|
10
10
|
import threading
|
|
11
|
+
import uuid
|
|
11
12
|
from ..rest import Authenticator
|
|
12
13
|
from ..config import market_data_socket_urls, market_data_feedb_socket_urls, REALTIME, B3, TRADES, BOOKS, FEED_A, FEED_B, MAX_WS_RECONNECT_RETRIES
|
|
13
14
|
from .websocket_default_functions import _on_open, _on_message_already_serialized, _on_error, _on_close
|
|
14
15
|
|
|
15
16
|
multiprocessing.set_start_method("spawn", force=True)
|
|
16
17
|
|
|
18
|
+
class LogConstFilter(logging.Filter):
|
|
19
|
+
def __init__(self, consts):
|
|
20
|
+
super().__init__()
|
|
21
|
+
self.consts = consts
|
|
22
|
+
|
|
23
|
+
def filter(self, record):
|
|
24
|
+
for key, value in self.consts.items():
|
|
25
|
+
setattr(record, key, value)
|
|
26
|
+
return True
|
|
27
|
+
|
|
17
28
|
class MarketDataFeed:
|
|
18
29
|
"""
|
|
19
30
|
WebSocket client that connects with BTG Solutions Data Services WebSocket servers. The servers streams realtime and delayed market data, such as trades and book events.
|
|
@@ -50,7 +61,7 @@ class MarketDataFeed:
|
|
|
50
61
|
Field is not required. Default: 'b3'.
|
|
51
62
|
data_type: str
|
|
52
63
|
Market Data type.
|
|
53
|
-
Options: 'trades', 'processed-trades', 'books', 'indices', 'securities', 'stoploss', 'candles-1S', 'candles-1M', 'instrument_status'.
|
|
64
|
+
Options: 'trades', 'processed-trades', 'books', 'indices', 'securities', 'stoploss', 'candles-1S', 'candles-1M', 'instrument_status', 'settlement-price'.
|
|
54
65
|
Field is not required. Default: 'trades'.
|
|
55
66
|
data_subtype: str
|
|
56
67
|
Market Data subtype (when applicable).
|
|
@@ -136,6 +147,9 @@ class MarketDataFeed:
|
|
|
136
147
|
self.reconnect = reconnect
|
|
137
148
|
self.__nro_reconnect_retries = 0
|
|
138
149
|
|
|
150
|
+
client_feed = f'feed_{exchange}_{data_type}_{stream_type}_{data_subtype}'
|
|
151
|
+
client_id = str(uuid.uuid4())
|
|
152
|
+
|
|
139
153
|
self.server_message_queue = multiprocessing.Queue()
|
|
140
154
|
self.client_message_queue = multiprocessing.Queue()
|
|
141
155
|
self.log_queue = multiprocessing.Queue()
|
|
@@ -143,8 +157,10 @@ class MarketDataFeed:
|
|
|
143
157
|
log_level = getattr(logging, log_level)
|
|
144
158
|
self.log_level = log_level
|
|
145
159
|
|
|
160
|
+
log_constants = {'client_feed': client_feed, 'client_id': client_id}
|
|
146
161
|
log_handler = logging.FileHandler(filename=f"MarketDataFeed_{date.today().isoformat()}.log")
|
|
147
|
-
log_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
|
|
162
|
+
log_handler.setFormatter(logging.Formatter('%(asctime)s - %(client_feed)s - %(client_id)s - %(levelname)s - %(message)s'))
|
|
163
|
+
log_handler.addFilter(LogConstFilter(log_constants))
|
|
148
164
|
|
|
149
165
|
log_queue_listener = QueueListener(self.log_queue, log_handler)
|
|
150
166
|
log_queue_listener.start()
|
|
@@ -156,6 +172,14 @@ class MarketDataFeed:
|
|
|
156
172
|
self.process = None
|
|
157
173
|
self.running = False
|
|
158
174
|
|
|
175
|
+
self.head_message_count = 0
|
|
176
|
+
self.head_avg_latency = 0
|
|
177
|
+
|
|
178
|
+
self.instruments = []
|
|
179
|
+
|
|
180
|
+
def set_instruments(self, instruments: List[str]):
|
|
181
|
+
self.instruments = instruments
|
|
182
|
+
|
|
159
183
|
def _ws_client_process(self, server_message_queue: multiprocessing.Queue, client_message_queue: multiprocessing.Queue, log_queue: multiprocessing.Queue, log_level: int):
|
|
160
184
|
|
|
161
185
|
logger = logging.getLogger("client")
|
|
@@ -163,7 +187,27 @@ class MarketDataFeed:
|
|
|
163
187
|
logger.addHandler(QueueHandler(log_queue))
|
|
164
188
|
|
|
165
189
|
def on_message(ws, message):
|
|
166
|
-
|
|
190
|
+
message = json.loads(message)
|
|
191
|
+
server_message_queue.put(message)
|
|
192
|
+
|
|
193
|
+
if self.log_level != logging.DEBUG:
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
msg_datetime = None
|
|
197
|
+
if self.data_type == BOOKS:
|
|
198
|
+
bid = message.get("bid")
|
|
199
|
+
offer = message.get("offer")
|
|
200
|
+
if bid:
|
|
201
|
+
msg_datetime = bid[0]["datetime"]
|
|
202
|
+
elif offer:
|
|
203
|
+
msg_datetime = offer[0]["datetime"]
|
|
204
|
+
else:
|
|
205
|
+
msg_datetime = message.get("tTime")
|
|
206
|
+
if msg_datetime:
|
|
207
|
+
msg_datetime = datetime.strptime(msg_datetime, "%Y-%m-%dT%H:%M:%S.%fZ")
|
|
208
|
+
latency = (time.time() - msg_datetime.timestamp()) * 1000
|
|
209
|
+
self.head_message_count += 1
|
|
210
|
+
self.head_avg_latency += (latency - self.head_avg_latency) / self.head_message_count
|
|
167
211
|
|
|
168
212
|
def on_error(ws, error):
|
|
169
213
|
logger.error(f"On Error | {error}")
|
|
@@ -182,6 +226,9 @@ class MarketDataFeed:
|
|
|
182
226
|
|
|
183
227
|
def on_open(ws):
|
|
184
228
|
logger.info(f"On Open | Connection open")
|
|
229
|
+
if self.instruments:
|
|
230
|
+
logger.info(f"On Open | Subscribing to {len(self.instruments)} instruments")
|
|
231
|
+
self.subscribe(self.instruments)
|
|
185
232
|
self.on_open()
|
|
186
233
|
self.__nro_reconnect_retries = 0
|
|
187
234
|
|
|
@@ -208,15 +255,28 @@ class MarketDataFeed:
|
|
|
208
255
|
break
|
|
209
256
|
pass
|
|
210
257
|
|
|
258
|
+
log_metrics_interval = 5
|
|
259
|
+
log_timer = time.time()
|
|
211
260
|
while True:
|
|
212
261
|
try:
|
|
213
262
|
if not client_message_queue.empty():
|
|
214
263
|
msg = client_message_queue.get()
|
|
215
264
|
ws.send(json.dumps(msg))
|
|
216
|
-
|
|
217
|
-
|
|
265
|
+
|
|
266
|
+
if self.log_level != logging.DEBUG:
|
|
267
|
+
continue
|
|
268
|
+
|
|
269
|
+
if time.time() - log_timer >= log_metrics_interval:
|
|
270
|
+
if self.head_avg_latency == 0:
|
|
271
|
+
logger.debug(f"HEAD - (ServerQ) Relative Latency: N/A; Throughput: N/A")
|
|
272
|
+
else:
|
|
273
|
+
logger.debug(f"HEAD - (ServerQ) Relative Latency: {round(self.head_avg_latency, 1)} ms; Throughput: {round(self.head_message_count/log_metrics_interval, 1)} msg/s")
|
|
274
|
+
self.head_message_count = 0
|
|
275
|
+
self.head_avg_latency = 0
|
|
276
|
+
log_timer = time.time()
|
|
277
|
+
|
|
218
278
|
except Exception as e:
|
|
219
|
-
time.sleep(0.
|
|
279
|
+
time.sleep(0.01)
|
|
220
280
|
|
|
221
281
|
run_forever_new_thread()
|
|
222
282
|
|
|
@@ -230,18 +290,47 @@ class MarketDataFeed:
|
|
|
230
290
|
|
|
231
291
|
def run_on_new_thread(*args):
|
|
232
292
|
log_timer = time.time()
|
|
293
|
+
log_metrics_interval = 5
|
|
294
|
+
|
|
295
|
+
message_count = 0
|
|
296
|
+
latency_message_count = 0
|
|
297
|
+
latency_average = 0
|
|
233
298
|
while self.running:
|
|
234
299
|
if not self.server_message_queue.empty():
|
|
235
300
|
msg = self.server_message_queue.get()
|
|
236
301
|
self.on_message(msg)
|
|
237
|
-
else:
|
|
238
|
-
time.sleep(0.01)
|
|
239
302
|
|
|
240
|
-
|
|
303
|
+
if self.log_level != logging.DEBUG:
|
|
304
|
+
continue
|
|
305
|
+
|
|
306
|
+
message_count += 1
|
|
307
|
+
msg_datetime = None
|
|
308
|
+
if self.data_type == BOOKS:
|
|
309
|
+
bid = msg.get("bid")
|
|
310
|
+
offer = msg.get("offer")
|
|
311
|
+
if bid:
|
|
312
|
+
msg_datetime = bid[0]["datetime"]
|
|
313
|
+
elif offer:
|
|
314
|
+
msg_datetime = offer[0]["datetime"]
|
|
315
|
+
else:
|
|
316
|
+
msg_datetime = msg.get("tTime")
|
|
317
|
+
if msg_datetime:
|
|
318
|
+
msg_datetime = datetime.strptime(msg_datetime, "%Y-%m-%dT%H:%M:%S.%fZ")
|
|
319
|
+
latency = (time.time() - msg_datetime.timestamp()) * 1000
|
|
320
|
+
latency_message_count += 1
|
|
321
|
+
latency_average += (latency - latency_average) / latency_message_count
|
|
322
|
+
|
|
323
|
+
if time.time() - log_timer >= log_metrics_interval:
|
|
241
324
|
server_queue_size = self.server_message_queue.qsize()
|
|
242
325
|
client_queue_size = self.client_message_queue.qsize()
|
|
243
|
-
|
|
326
|
+
if message_count == 0:
|
|
327
|
+
self.logger.debug(f"TAIL - (ServerQ) Relative Latency: N/A; Throughput: N/A; Size: {server_queue_size} | (ClientQ) Size: {client_queue_size}")
|
|
328
|
+
else:
|
|
329
|
+
self.logger.debug(f"TAIL - (ServerQ) Relative Latency: {round(latency_average, 1)} ms; Throughput: {round(message_count/log_metrics_interval, 1)} msg/s; Size: {server_queue_size} | (ClientQ) Size: {client_queue_size}")
|
|
244
330
|
log_timer = time.time()
|
|
331
|
+
message_count = 0
|
|
332
|
+
latency_message_count = 0
|
|
333
|
+
latency_average = 0
|
|
245
334
|
|
|
246
335
|
threading.Thread(target=run_on_new_thread).start()
|
|
247
336
|
|
|
@@ -262,7 +351,7 @@ class MarketDataFeed:
|
|
|
262
351
|
message = json.dumps(message)
|
|
263
352
|
self.client_message_queue.put(json.loads(message))
|
|
264
353
|
|
|
265
|
-
def subscribe(self, list_instruments: List[str], n=None):
|
|
354
|
+
def subscribe(self, list_instruments: List[str], n=None, initial_snapshot: bool=False):
|
|
266
355
|
"""
|
|
267
356
|
Subscribes a list of instruments.
|
|
268
357
|
|
|
@@ -274,9 +363,17 @@ class MarketDataFeed:
|
|
|
274
363
|
Field is not required.
|
|
275
364
|
**For books data_type only.**
|
|
276
365
|
Maximum book level. It must be between 1 and 10.
|
|
366
|
+
initial_snapshot: float
|
|
367
|
+
If True, client receives ticker last event (snapshot) of the provided ticker.
|
|
368
|
+
Field is not required.
|
|
369
|
+
Default: False
|
|
277
370
|
"""
|
|
278
|
-
|
|
279
|
-
|
|
371
|
+
|
|
372
|
+
if initial_snapshot or n is not None:
|
|
373
|
+
message = {'action': 'subscribe', 'params': {"tickers": list_instruments, "initial_snapshot": initial_snapshot}}
|
|
374
|
+
if n is not None:
|
|
375
|
+
message['params']['n'] = n
|
|
376
|
+
self._send(message)
|
|
280
377
|
else:
|
|
281
378
|
self._send({'action': 'subscribe', 'params': list_instruments})
|
|
282
379
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
from typing import Optional, List
|
|
3
3
|
from ..exceptions import WSTypeError, DelayedError, FeedError
|
|
4
4
|
from ..rest import Authenticator
|
|
5
|
-
from ..config import market_data_socket_urls, market_data_feedb_socket_urls, MAX_WS_RECONNECT_RETRIES, VALID_STREAM_TYPES, VALID_EXCHANGES, VALID_MARKET_DATA_TYPES, VALID_MARKET_DATA_SUBTYPES, REALTIME, B3, TRADES, INDICES, ALL, STOCKS, BOOKS, FEED_A, FEED_B
|
|
5
|
+
from ..config import market_data_socket_urls, market_data_feedb_socket_urls, MAX_WS_RECONNECT_RETRIES, VALID_STREAM_TYPES, VALID_EXCHANGES, VALID_MARKET_DATA_TYPES, VALID_MARKET_DATA_SUBTYPES, REALTIME, B3, TRADES, INDICES, ALL, STOCKS, BOOKS, FEED_A, FEED_B, SETTLEMENTPRICES
|
|
6
6
|
from .websocket_default_functions import _on_open, _on_message, _on_error, _on_close
|
|
7
7
|
import websocket
|
|
8
8
|
import json
|
|
@@ -49,7 +49,7 @@ class MarketDataWebSocketClient:
|
|
|
49
49
|
|
|
50
50
|
data_type: str
|
|
51
51
|
Market Data type.
|
|
52
|
-
Options: 'trades', 'processed-trades', 'books', 'indices', 'securities', 'stoploss', 'candles-1S', 'candles-1M', 'instrument_status'.
|
|
52
|
+
Options: 'trades', 'processed-trades', 'books', 'indices', 'securities', 'stoploss', 'candles-1S', 'candles-1M', 'instrument_status', 'settlement-price'.
|
|
53
53
|
Field is not required. Default: 'trades'.
|
|
54
54
|
|
|
55
55
|
data_subtype: str
|
|
@@ -93,7 +93,7 @@ class MarketDataWebSocketClient:
|
|
|
93
93
|
self.__nro_reconnect_retries = 0
|
|
94
94
|
|
|
95
95
|
if data_subtype is None:
|
|
96
|
-
if exchange is B3 and data_type
|
|
96
|
+
if exchange is B3 and not data_type in [INDICES, SETTLEMENTPRICES]:
|
|
97
97
|
data_subtype = STOCKS
|
|
98
98
|
else:
|
|
99
99
|
data_subtype = ALL
|
|
@@ -267,7 +267,7 @@ class MarketDataWebSocketClient:
|
|
|
267
267
|
"""
|
|
268
268
|
self.ws.close()
|
|
269
269
|
|
|
270
|
-
def subscribe(self, list_instruments, n=None):
|
|
270
|
+
def subscribe(self, list_instruments, n=None, initial_snapshot: bool=False):
|
|
271
271
|
"""
|
|
272
272
|
Subscribes a list of instruments.
|
|
273
273
|
|
|
@@ -278,12 +278,19 @@ class MarketDataWebSocketClient:
|
|
|
278
278
|
n: int
|
|
279
279
|
Field is not required.
|
|
280
280
|
**For books data_type only.**
|
|
281
|
-
Maximum book level. It must be between 1 and 10.
|
|
281
|
+
Maximum book level. It must be between 1 and 10.
|
|
282
|
+
initial_snapshot: float
|
|
283
|
+
If True, client receives ticker last event (snapshot) of the provided ticker.
|
|
284
|
+
Field is not required.
|
|
285
|
+
Default: False
|
|
282
286
|
"""
|
|
283
287
|
|
|
284
|
-
if
|
|
285
|
-
|
|
286
|
-
|
|
288
|
+
if initial_snapshot or n is not None:
|
|
289
|
+
message = {'action': 'subscribe', 'params': {"tickers": list_instruments, "initial_snapshot": initial_snapshot}}
|
|
290
|
+
if n is not None:
|
|
291
|
+
message['params']['n'] = n
|
|
292
|
+
self.__send(message)
|
|
293
|
+
self.__print(f'Socket subscribed the following instrument(s) with n = {n}: {list_instruments} and initial_snapshot = {initial_snapshot}')
|
|
287
294
|
else:
|
|
288
295
|
self.__send({'action': 'subscribe', 'params': list_instruments})
|
|
289
296
|
self.__print(f'Socket subscribed the following instrument(s): {list_instruments}')
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: btgsolutions-dataservices-python-client
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.17.1
|
|
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
|
|
7
|
-
Requires-Python: >=3.9,<3.
|
|
7
|
+
Requires-Python: >=3.9,<3.14
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
License-File: LICENSE
|
|
10
10
|
Requires-Dist: pandas>=2.2.2
|
|
@@ -165,11 +165,26 @@ ws.run(on_message=lambda message: print(message))
|
|
|
165
165
|
|
|
166
166
|
```python
|
|
167
167
|
import btgsolutions_dataservices as btg
|
|
168
|
-
ws = btg.MarketDataWebSocketClient(api_key='YOUR_API_KEY', data_type='
|
|
168
|
+
ws = btg.MarketDataWebSocketClient(api_key='YOUR_API_KEY', data_type='instrument_status', data_subtype='stocks')
|
|
169
169
|
ws.run(on_message=lambda message: print(message))
|
|
170
170
|
ws.instrument_status('PETR4')
|
|
171
171
|
ws.instrument_status_history('PETR4')
|
|
172
172
|
|
|
173
|
+
## The following is optional to keep the program running in a .py file:
|
|
174
|
+
# from time import sleep
|
|
175
|
+
# while True:
|
|
176
|
+
# sleep(1)
|
|
177
|
+
```
|
|
178
|
+
##### Settlement Price
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
import btgsolutions_dataservices as btg
|
|
182
|
+
ws = btg.MarketDataWebSocketClient(api_key='YOUR_API_KEY', data_type='settlement-price', instruments=['ABEVOU25', 'WINV25'])
|
|
183
|
+
ws.run(on_message=lambda message: print(message))
|
|
184
|
+
|
|
185
|
+
## Getting the last event (settlement-price) of ABEVOU25:
|
|
186
|
+
# ws.get_last_event(['ABEVOU25'])
|
|
187
|
+
|
|
173
188
|
## The following is optional to keep the program running in a .py file:
|
|
174
189
|
# from time import sleep
|
|
175
190
|
# while True:
|
|
@@ -214,7 +229,7 @@ quotes = btg.Quotes(api_key='YOUR_API_KEY')
|
|
|
214
229
|
quotes.get_quote(market_type = 'stocks', tickers = ['PETR4', 'VALE3'])
|
|
215
230
|
```
|
|
216
231
|
|
|
217
|
-
#### Ticker Last
|
|
232
|
+
#### Ticker Last Trade
|
|
218
233
|
|
|
219
234
|
```python
|
|
220
235
|
import btgsolutions_dataservices as btg
|
|
@@ -222,6 +237,31 @@ last_event = btg.TickerLastEvent(api_key='YOUR_API_KEY')
|
|
|
222
237
|
last_event.get_trades(data_type='equities', ticker='VALE3')
|
|
223
238
|
```
|
|
224
239
|
|
|
240
|
+
#### Ticker Last Top of Book
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
import btgsolutions_dataservices as btg
|
|
244
|
+
last_event = btg.TickerLastEvent(api_key='YOUR_API_KEY')
|
|
245
|
+
last_event.get_tobs(data_type='stocks')
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
#### Ticker Last Trading Status
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
import btgsolutions_dataservices as btg
|
|
252
|
+
last_event = btg.TickerLastEvent(api_key='YOUR_API_KEY')
|
|
253
|
+
last_event.get_status(tickers=['PETR4','VALE3'])
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
#### Ticker Last Polling - Top of Books
|
|
257
|
+
|
|
258
|
+
```python
|
|
259
|
+
import btgsolutions_dataservices as btg
|
|
260
|
+
last_event = btg.TickerLastEventPolling(api_key='YOUR_API_KEY', data_type='top-of-books', data_subtype='stocks')
|
|
261
|
+
last_event.get()
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
|
|
225
265
|
### Historical Data
|
|
226
266
|
|
|
227
267
|
#### Historical Candles
|
|
@@ -336,6 +376,16 @@ public_sources = btg.PublicSources(api_key='YOUR_API_KEY')
|
|
|
336
376
|
public_sources.get_opas(start_date='2022-10-01', end_date='2024-10-01')
|
|
337
377
|
```
|
|
338
378
|
|
|
379
|
+
#### STOCK LOAN
|
|
380
|
+
|
|
381
|
+
```python
|
|
382
|
+
import btgsolutions_dataservices as btg
|
|
383
|
+
stock_loan = btg.StockLoan(api_key='YOUR_API_KEY')
|
|
384
|
+
stock_loan.get_trades()
|
|
385
|
+
stock_loan.get_paginated_trades(page=1, limit=1000, ticker ='PETR4')
|
|
386
|
+
stock_loan.get_available_tickers()
|
|
387
|
+
```
|
|
388
|
+
|
|
339
389
|
#### Company Fundamentals
|
|
340
390
|
|
|
341
391
|
##### Company General Information
|
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
btgsolutions_dataservices/__init__.py,sha256=reSSb9MBIp5WQ5o872fX__moJYXx0sQgkhUf3QmfEjI,93
|
|
2
|
-
btgsolutions_dataservices/config.py,sha256=
|
|
2
|
+
btgsolutions_dataservices/config.py,sha256=9L9bIGL1j7XSAkpYMxLS8oqIu-F4CQ2EyAywIT496p8,5285
|
|
3
3
|
btgsolutions_dataservices/exceptions.py,sha256=QlwQTDEohtVsTER0lHbQrDgAGvjiZBmoyDuqLkUcvh8,599
|
|
4
|
-
btgsolutions_dataservices/rest/__init__.py,sha256=
|
|
4
|
+
btgsolutions_dataservices/rest/__init__.py,sha256=NEar641a2Mosioj-D3HrcdrJqgjaFRCwos3rftnUhq0,591
|
|
5
5
|
btgsolutions_dataservices/rest/authenticator.py,sha256=uBLARz5lCRhIDc_PZaC4a2rSQtrFJajmAC4Bdg9BnX0,1363
|
|
6
6
|
btgsolutions_dataservices/rest/bulk_data.py,sha256=26CrFH-IVMUbpPLx0HaN-BePs5R9A1ZUINSir7qtfhE,9032
|
|
7
7
|
btgsolutions_dataservices/rest/company_data.py,sha256=yhsgR-P7g5e6TDdpoMZoyiyqI85roBCXJGyob4g1P_8,7318
|
|
8
8
|
btgsolutions_dataservices/rest/corporate_events.py,sha256=04DdoUnBaX1rSAWplCLzNm-YPrKks80txOR26xubEZ0,2399
|
|
9
9
|
btgsolutions_dataservices/rest/hfn.py,sha256=0E-oNw797M8XgbDfwbhmMaPaf9z-WN4rhMpMR_2dkos,11205
|
|
10
10
|
btgsolutions_dataservices/rest/historical_candles.py,sha256=0iu3clwbJUtwyHotxT5pb3xsqCJvpd6kRUsrYBVNU80,7359
|
|
11
|
-
btgsolutions_dataservices/rest/intraday_candles.py,sha256=
|
|
11
|
+
btgsolutions_dataservices/rest/intraday_candles.py,sha256=WWujmvW_qoREPjYadhMoJyiVMX8Ts7zFo21KacqKhjU,5687
|
|
12
12
|
btgsolutions_dataservices/rest/intraday_tick_data.py,sha256=VZZ-yPoQn9dZCJ9KnPYGwaEFq2FZi0oSnLDIIENYCNo,2469
|
|
13
13
|
btgsolutions_dataservices/rest/public_sources.py,sha256=CMXsvHe8RKuEmJJ_baeVgsj28TrH2AYxE3TEfGSCdOQ,2459
|
|
14
14
|
btgsolutions_dataservices/rest/quotes.py,sha256=sirlG1-_aCtE7auwYM7lTZkpMcW3UYisjtCaBWzPWuY,7508
|
|
15
15
|
btgsolutions_dataservices/rest/reference_data.py,sha256=ntcHEMapKULGPHJvmzAnccUo8ZTGokt1M9bDBceV5Tk,2112
|
|
16
|
-
btgsolutions_dataservices/rest/
|
|
16
|
+
btgsolutions_dataservices/rest/stock_loan.py,sha256=_EYxywO-qA-zC7CeAo8a1omUb327g13P1R1B-plPKtI,3207
|
|
17
|
+
btgsolutions_dataservices/rest/ticker_last_event.py,sha256=1DR4fVsareRDr1iGya7E8308kDcciK5XtlS2SI56c1o,6328
|
|
18
|
+
btgsolutions_dataservices/rest/ticker_last_event_polling.py,sha256=wCDplal8k5xHXI4JD69D5j-djYkL16SiNiso7Of3UCI,4656
|
|
17
19
|
btgsolutions_dataservices/websocket/__init__.py,sha256=Bifok6zQFenYNIA-NE8hYSooZVIQ2MCeTCjIOYnfHzI,165
|
|
18
20
|
btgsolutions_dataservices/websocket/hfn_websocket_client.py,sha256=wzyPXtXFsv31cVFcTWrTA5uhFBebgM2IpIxXQvPSP54,6253
|
|
19
|
-
btgsolutions_dataservices/websocket/market_data_feed.py,sha256=
|
|
20
|
-
btgsolutions_dataservices/websocket/market_data_websocket_client.py,sha256=
|
|
21
|
+
btgsolutions_dataservices/websocket/market_data_feed.py,sha256=HAVA4thvYyvmcVrHm4_exGaq4y9RCGjwl98yuXR8KVc,16209
|
|
22
|
+
btgsolutions_dataservices/websocket/market_data_websocket_client.py,sha256=ix6EH6yHJcRc69RWuvUjHxr0H17zRSM_o9YjsO6OZIw,13812
|
|
21
23
|
btgsolutions_dataservices/websocket/websocket_default_functions.py,sha256=JWbOR4uX14BI9JB3zXMbwqVsF8AtfDbEC83fgs8O5PY,335
|
|
22
|
-
btgsolutions_dataservices_python_client-2.
|
|
23
|
-
btgsolutions_dataservices_python_client-2.
|
|
24
|
-
btgsolutions_dataservices_python_client-2.
|
|
25
|
-
btgsolutions_dataservices_python_client-2.
|
|
26
|
-
btgsolutions_dataservices_python_client-2.
|
|
24
|
+
btgsolutions_dataservices_python_client-2.17.1.dist-info/licenses/LICENSE,sha256=vePSp4Jry-f15vZ48xge2vljuhNgkMZD6wU0XmuFBgA,1057
|
|
25
|
+
btgsolutions_dataservices_python_client-2.17.1.dist-info/METADATA,sha256=cDuPuZOmolVyzvEqaHub2x0wvWAwv2kuAqeGSYPNYVg,13169
|
|
26
|
+
btgsolutions_dataservices_python_client-2.17.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
27
|
+
btgsolutions_dataservices_python_client-2.17.1.dist-info/top_level.txt,sha256=OaseSN41lI9j_Z-kHIE_XVo_m1hmGdAHE3EK1h5_Ce0,26
|
|
28
|
+
btgsolutions_dataservices_python_client-2.17.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|