Skip to content
This repository was archived by the owner on Nov 18, 2024. It is now read-only.

Commit 428e638

Browse files
committed
- Added a thetadata.client.ms_to_time method that converts a ms_of_day to a time object.
- Added a copy_from method for Quote, Trade, Open Interest, and OHLCVC, which can be used to create a safe copy of each respective object. - Added new exchange codes including a new OPRA participant, MEMX (Members Exchange). - Replaced uses of localhost with 127.0.0.1 to prevent a name table issue with certain machines.
1 parent 29a832d commit 428e638

File tree

2 files changed

+69
-28
lines changed

2 files changed

+69
-28
lines changed

thetadata/client.py

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""Module that contains Theta Client class."""
2-
import datetime
2+
from datetime import time
33
import threading
44
import time
55
import traceback
@@ -27,8 +27,9 @@
2727
from .terminal import check_download, launch_terminal
2828

2929
_NOT_CONNECTED_MSG = "You must establish a connection first."
30-
_VERSION = '0.9.2'
31-
URL_BASE = "http://localhost:25510/"
30+
_VERSION = '0.9.3'
31+
URL_BASE = "http://127.0.0.1:25510/"
32+
3233

3334
def _format_strike(strike: float) -> int:
3435
"""Round USD to the nearest tenth of a cent, acceptable by the terminal."""
@@ -40,6 +41,12 @@ def _format_date(dt: date) -> str:
4041
return dt.strftime("%Y%m%d")
4142

4243

44+
def ms_to_time(ms_of_day: int) -> datetime.time:
45+
return datetime(year=2000, month=1, day=1, hour=int((ms_of_day / (1000 * 60 * 60)) % 24),
46+
minute=int(ms_of_day / (1000 * 60)) % 60, second=int((ms_of_day / 1000) % 60),
47+
microsecond=(ms_of_day % 1000) * 1000).time()
48+
49+
4350
_pt_to_price_mul = [
4451
0,
4552
0.000000001,
@@ -89,6 +96,15 @@ def from_bytes(self, data: bytearray):
8996
date_raw = str(parse_int(view[28:32]))
9097
self.date = date(year=int(date_raw[0:4]), month=int(date_raw[4:6]), day=int(date_raw[6:8]))
9198

99+
def copy_from(self, other_trade):
100+
self.ms_of_day = other_trade.ms_of_day
101+
self.sequence = other_trade.sequence
102+
self.size = other_trade.size
103+
self.condition = other_trade.condition
104+
self.price = other_trade.price
105+
self.exchange = other_trade.exchange
106+
self.date = other_trade.date
107+
92108
def to_string(self) -> str:
93109
"""String representation of a trade."""
94110
return 'ms_of_day: ' + str(self.ms_of_day) + ' sequence: ' + str(self.sequence) + ' size: ' + str(self.size) + \
@@ -123,6 +139,16 @@ def from_bytes(self, data: bytearray):
123139
date_raw = str(parse_int(view[32:36]))
124140
self.date = date(year=int(date_raw[0:4]), month=int(date_raw[4:6]), day=int(date_raw[6:8]))
125141

142+
def copy_from(self, other_ohlcvc):
143+
self.ms_of_day = other_ohlcvc.ms_of_day
144+
self.open = other_ohlcvc.open
145+
self.high = other_ohlcvc.high
146+
self.low = other_ohlcvc.low
147+
self.close = other_ohlcvc.close
148+
self.volume = other_ohlcvc.volume
149+
self.count = other_ohlcvc.count
150+
self.date = other_ohlcvc.date
151+
126152
def to_string(self) -> str:
127153
"""String representation of a trade."""
128154
return 'ms_of_day: ' + str(self.ms_of_day) + ' open: ' + str(self.open) + ' high: ' + str(self.high) + \
@@ -162,6 +188,18 @@ def from_bytes(self, data: bytes):
162188
date_raw = str(parse_int(view[40:44]))
163189
self.date = date(year=int(date_raw[0:4]), month=int(date_raw[4:6]), day=int(date_raw[6:8]))
164190

191+
def copy_from(self, other_quote):
192+
self.ms_of_day = other_quote.ms_of_day
193+
self.bid_size = other_quote.bid_size
194+
self.bid_exchange = other_quote.bid_exchange
195+
self.bid_price = other_quote.bid_price
196+
self.bid_condition = other_quote.bid_condition
197+
self.ask_size = other_quote.ask_size
198+
self.ask_exchange = other_quote.ask_exchange
199+
self.ask_price = other_quote.ask_price
200+
self.ask_condition = other_quote.ask_condition
201+
self.date = other_quote.date
202+
165203
def to_string(self) -> str:
166204
"""String representation of a quote."""
167205
return 'ms_of_day: ' + str(self.ms_of_day) + ' bid_size: ' + str(self.bid_size) + ' bid_exchange: ' + \
@@ -186,6 +224,10 @@ def from_bytes(self, data: bytearray):
186224
date_raw = str(parse_int(view[4:8]))
187225
self.date = date(year=int(date_raw[0:4]), month=int(date_raw[4:6]), day=int(date_raw[6:8]))
188226

227+
def copy_from(self, other_open_interest):
228+
self.open_interest = other_open_interest.open_interest
229+
self.date = other_open_interest.date
230+
189231
def to_string(self) -> str:
190232
"""String representation of open interest."""
191233
return 'open_interest: ' + str(self.open_interest) + ' date: ' + str(self.date)
@@ -239,15 +281,14 @@ def __init__(self):
239281
self.contract = Contract()
240282
self.date = None
241283

242-
243284
class ThetaClient:
244285
"""A high-level, blocking client used to fetch market data. Instantiating this class
245286
runs a java background process, which is responsible for the heavy lifting of market
246287
data communication. Java 11 or higher is required to use this class."""
247288

248289
def __init__(self, port: int = 11000, timeout: Optional[float] = 60, launch: bool = True, jvm_mem: int = 0,
249290
username: str = "default", passwd: str = "default", auto_update: bool = True, use_bundle: bool = True,
250-
host: str = "localhost", streaming_port: int = 10000, stable: bool = True):
291+
host: str = "127.0.0.1", streaming_port: int = 10000, stable: bool = True):
251292
"""Construct a client instance to interface with market data. If no username and passwd fields are provided,
252293
the terminal will connect to thetadata servers with free data permissions.
253294
@@ -683,7 +724,7 @@ def get_hist_option_REST(
683724
end_fmt = _format_date(date_range.end)
684725
right_fmt = right.value
685726
use_rth_fmt = str(use_rth).lower()
686-
url = f"http://localhost:25510/hist/option/{req_fmt}"
727+
url = f"http://127.0.0.1:25510/hist/option/{req_fmt}"
687728
querystring = {"root": root, "start_date": start_fmt, "end_date": end_fmt,
688729
"strike": strike_fmt, "exp": exp_fmt, "right": right_fmt,
689730
"ivl": interval_size, "rth": use_rth_fmt}
@@ -774,7 +815,7 @@ def get_opt_at_time_REST(
774815
end_fmt = _format_date(date_range.end)
775816
right_fmt = right.value
776817

777-
url = f"http://localhost:25510/at_time/option/{req_fmt}"
818+
url = f"http://127.0.0.1:25510/at_time/option/{req_fmt}"
778819
querystring = {"root": root, "start_date": start_fmt, "end_date": end_fmt, "strike": strike_fmt,
779820
"exp": exp_fmt, "right": right_fmt, "ivl": ms_of_day}
780821
response = requests.get(url, params=querystring)
@@ -842,7 +883,7 @@ def get_stk_at_time_REST(
842883
start_fmt = _format_date(date_range.start)
843884
end_fmt = _format_date(date_range.end)
844885

845-
url = f"http://localhost:25510/at_time/stock/{req_fmt}"
886+
url = f"http://127.0.0.1:25510/at_time/stock/{req_fmt}"
846887
querystring = {"root": root_fmt, "start_date": start_fmt,
847888
"end_date": end_fmt, "ivl": ms_of_day}
848889
response = requests.get(url, params=querystring)
@@ -916,7 +957,7 @@ def get_hist_stock_REST(
916957
start_fmt = _format_date(date_range.start)
917958
end_fmt = _format_date(date_range.end)
918959
use_rth_fmt = str(use_rth).lower()
919-
url = f"http://localhost:25510/hist/stock/{req_fmt}"
960+
url = f"http://127.0.0.1:25510/hist/stock/{req_fmt}"
920961
params = {"root": root, "start_date": start_fmt, "end_date": end_fmt,
921962
"ivl": interval_size, "rth": use_rth_fmt}
922963
response = requests.get(url, params=params)
@@ -956,7 +997,7 @@ def get_dates_stk_REST(self, root: str, req: StockReqType) -> pd.Series:
956997
"""
957998
root_fmt = root.lower()
958999
req_fmt = req.name.lower()
959-
url = f"http://localhost:25510/list/dates/stock/{req_fmt}"
1000+
url = f"http://127.0.0.1:25510/list/dates/stock/{req_fmt}"
9601001
params = {'root': root_fmt}
9611002
response = requests.get(url, params=params)
9621003
series = parse_list_REST(response, dates=True)
@@ -1016,7 +1057,7 @@ def get_dates_opt_REST(
10161057
strike_fmt = _format_strike(strike)
10171058
right = right.value
10181059
sec = SecType.OPTION.value.lower()
1019-
url = f"http://localhost:25510/list/dates/{sec}/{req}"
1060+
url = f"http://127.0.0.1:25510/list/dates/{sec}/{req}"
10201061
params = {'root': root, 'exp': exp_fmt, 'strike': strike_fmt, 'right': right}
10211062
response = requests.get(url, params=params)
10221063
df = parse_list_REST(response, dates=True)
@@ -1067,7 +1108,7 @@ def get_dates_opt_bulk_REST(
10671108
req = req.name.lower()
10681109
exp_fmt = _format_date(exp)
10691110
sec = SecType.OPTION.value.lower()
1070-
url = f"http://localhost:25510/list/dates/{sec}/{req}"
1111+
url = f"http://127.0.0.1:25510/list/dates/{sec}/{req}"
10711112
params = {'root': root, 'exp': exp_fmt}
10721113
response = requests.get(url, params=params)
10731114
df = parse_list_REST(response, dates=True)
@@ -1100,7 +1141,7 @@ def get_expirations_REST(self, root: str) -> pd.Series:
11001141
:raises ResponseError: If the request failed.
11011142
:raises NoData: If there is no data available for the request.
11021143
"""
1103-
url = "http://localhost:25510/list/expirations"
1144+
url = "http://127.0.0.1:25510/list/expirations"
11041145
params = {"root": root}
11051146
response = requests.get(url, params=params)
11061147
df = parse_list_REST(response)
@@ -1164,7 +1205,7 @@ def get_strikes_REST(self, root: str, exp: date, date_range: DateRange = None,)
11641205
querystring = {"root": root_fmt, "exp": exp_fmt}
11651206
else:
11661207
querystring = {"root": root_fmt, "exp": exp_fmt}
1167-
url = "http://localhost:25510/list/strikes"
1208+
url = "http://127.0.0.1:25510/list/strikes"
11681209
response = requests.get(url, params=querystring)
11691210
ser = parse_list_REST(response)
11701211
ser = ser.divide(1000)
@@ -1197,7 +1238,7 @@ def get_roots_REST(self, sec: SecType) -> pd.Series:
11971238
:raises ResponseError: If the request failed.
11981239
:raises NoData: If there is no data available for the request.
11991240
"""
1200-
url = "http://localhost:25510/list/roots"
1241+
url = "http://127.0.0.1:25510/list/roots"
12011242
params = {'sec': sec.value}
12021243
response = requests.get(url, params=params)
12031244
df = parse_list_REST(response)
@@ -1269,7 +1310,7 @@ def get_last_option_REST(
12691310
strike_fmt = _format_strike(strike)
12701311
exp_fmt = _format_date(exp)
12711312

1272-
url = f"http://localhost:25510/snapshot/option/{req_fmt}"
1313+
url = f"http://127.0.0.1:25510/snapshot/option/{req_fmt}"
12731314
querystring = {"root": root_fmt, "strike": strike_fmt, "exp": exp_fmt, "right": right_fmt}
12741315
response = requests.get(url, params=querystring)
12751316
df = parse_flexible_REST(response)
@@ -1324,7 +1365,7 @@ def get_last_stock_REST(
13241365
root_fmt = root.lower()
13251366
req_fmt = req.name.lower()
13261367

1327-
url = f"http://localhost:25510/snapshot/option/{req_fmt}"
1368+
url = f"http://127.0.0.1:25510/snapshot/option/{req_fmt}"
13281369
querystring = {"root": root_fmt}
13291370
response = requests.get(url, params=querystring)
13301371
df = parse_flexible_REST(response)

thetadata/enums.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -477,17 +477,17 @@ class Exchange(enum.Enum):
477477
RUSL = (66, "", "Russell Indexes")
478478
CMEX = (67, "XIOM", "CME Indexes")
479479
IEX = (68, "IEXG", "Investors Exchange")
480-
TBA_69 = (69, "", "TBA Exchange 69")
481-
TBA_70 = (70, "", "TBA Exchange 70")
482-
TBA_71 = (71, "", "TBA Exchange 71")
483-
TBA_72 = (72, "", "TBA Exchange 72")
484-
TBA_73 = (73, "", "TBA Exchange 73")
485-
TBA_74 = (74, "", "TBA Exchange 74")
486-
TBA_75 = (75, "", "TBA Exchange 75")
487-
TBA_76 = (76, "", "TBA Exchange 76")
488-
TBA_77 = (77, "", "TBA Exchange 77")
489-
TBA_78 = (78, "", "TBA Exchange 78")
490-
TBA_79 = (79, "", "TBA Exchange 79")
480+
PERL = (69, "PERL", "Miami Pearl Options Exchange")
481+
LSE = (70, "LSE", "London Stock Exchange")
482+
NYSE_GIF = (71, "NYSE_GIF", "NYSE Global Index Feed")
483+
TSX_IDX = (72, "TSX_IDX", "TSX Indexes")
484+
MEMX = (73, "MEMX", "Members Exchange")
485+
TBA_74 = (74, "TBA_74", "TBA Exchange 74")
486+
LTSE = (75, "Long Term Stock Exchange")
487+
TBA_76 = (76, "TBA_76", "TBA Exchange 76")
488+
TBA_77 = (77, "TBA_77", "TBA Exchange 77")
489+
TBA_78 = (78, "TBA_78", "TBA Exchange 78")
490+
TBA_79 = (79, "TBA_79", "TBA Exchange 79")
491491

492492
@classmethod
493493
def from_code(cls, code: int) -> Exchange:

0 commit comments

Comments
 (0)