lumibot 4.2.7__py3-none-any.whl → 4.2.9__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.

Potentially problematic release.


This version of lumibot might be problematic. Click here for more details.

@@ -26,6 +26,7 @@ CONNECTION_RETRY_SLEEP = 1.0
26
26
  CONNECTION_MAX_RETRIES = 60
27
27
  BOOT_GRACE_PERIOD = 5.0
28
28
  MAX_RESTART_ATTEMPTS = 3
29
+ MAX_TERMINAL_RESTART_CYCLES = 3
29
30
 
30
31
 
31
32
  def _resolve_asset_folder(asset_obj: Asset) -> str:
@@ -43,6 +44,12 @@ THETA_DATA_PROCESS = None
43
44
  THETA_DATA_PID = None
44
45
  THETA_DATA_LOG_HANDLE = None
45
46
 
47
+
48
+ class ThetaDataConnectionError(RuntimeError):
49
+ """Raised when ThetaTerminal cannot reconnect to Theta Data after multiple restarts."""
50
+
51
+ pass
52
+
46
53
  def reset_connection_diagnostics():
47
54
  """Reset ThetaData connection counters (useful for tests)."""
48
55
  CONNECTION_DIAGNOSTICS.update({
@@ -50,6 +57,7 @@ def reset_connection_diagnostics():
50
57
  "start_terminal_calls": 0,
51
58
  "network_requests": 0,
52
59
  "placeholder_writes": 0,
60
+ "terminal_restarts": 0,
53
61
  })
54
62
 
55
63
 
@@ -198,6 +206,7 @@ CONNECTION_DIAGNOSTICS = {
198
206
  "start_terminal_calls": 0,
199
207
  "network_requests": 0,
200
208
  "placeholder_writes": 0,
209
+ "terminal_restarts": 0,
201
210
  }
202
211
 
203
212
 
@@ -1395,7 +1404,6 @@ def check_connection(username: str, password: str, wait_for_connection: bool = F
1395
1404
 
1396
1405
  max_retries = CONNECTION_MAX_RETRIES
1397
1406
  sleep_interval = CONNECTION_RETRY_SLEEP
1398
- restart_attempts = 0
1399
1407
  client = None
1400
1408
 
1401
1409
  def probe_status() -> Optional[str]:
@@ -1435,47 +1443,72 @@ def check_connection(username: str, password: str, wait_for_connection: bool = F
1435
1443
  logger.debug("ThetaTerminal running but not yet CONNECTED; waiting for status.")
1436
1444
  return check_connection(username=username, password=password, wait_for_connection=True)
1437
1445
 
1438
- counter = 0
1439
- connected = False
1440
-
1441
- while counter < max_retries:
1442
- status_text = probe_status()
1443
- if status_text == "CONNECTED":
1444
- if counter:
1445
- logger.info("ThetaTerminal connected after %s attempt(s).", counter + 1)
1446
- connected = True
1447
- break
1448
- elif status_text == "DISCONNECTED":
1449
- logger.debug("ThetaTerminal reports DISCONNECTED; will retry.")
1450
- elif status_text is not None:
1451
- logger.debug(f"ThetaTerminal returned unexpected status: {status_text}")
1452
-
1453
- if not is_process_alive():
1454
- if restart_attempts >= MAX_RESTART_ATTEMPTS:
1455
- logger.error("ThetaTerminal not running after %s restart attempts.", restart_attempts)
1456
- break
1457
- restart_attempts += 1
1458
- logger.warning("ThetaTerminal process is not running (restart #%s).", restart_attempts)
1459
- client = start_theta_data_client(username=username, password=password)
1460
- time.sleep(max(BOOT_GRACE_PERIOD, sleep_interval))
1461
- counter = 0
1462
- continue
1446
+ total_restart_cycles = 0
1463
1447
 
1464
- counter += 1
1465
- if counter % 10 == 0:
1466
- logger.info("Waiting for ThetaTerminal connection (attempt %s/%s).", counter, max_retries)
1467
- time.sleep(sleep_interval)
1448
+ while True:
1449
+ counter = 0
1450
+ restart_attempts = 0
1451
+
1452
+ while counter < max_retries:
1453
+ status_text = probe_status()
1454
+ if status_text == "CONNECTED":
1455
+ if counter or total_restart_cycles:
1456
+ logger.info(
1457
+ "ThetaTerminal connected after %s attempt(s) (restart cycles=%s).",
1458
+ counter + 1,
1459
+ total_restart_cycles,
1460
+ )
1461
+ return client, True
1462
+ elif status_text == "DISCONNECTED":
1463
+ logger.debug("ThetaTerminal reports DISCONNECTED; will retry.")
1464
+ elif status_text is not None:
1465
+ logger.debug(f"ThetaTerminal returned unexpected status: {status_text}")
1466
+
1467
+ if not is_process_alive():
1468
+ if restart_attempts >= MAX_RESTART_ATTEMPTS:
1469
+ logger.error("ThetaTerminal not running after %s restart attempts.", restart_attempts)
1470
+ break
1471
+ restart_attempts += 1
1472
+ logger.warning("ThetaTerminal process is not running (restart #%s).", restart_attempts)
1473
+ client = start_theta_data_client(username=username, password=password)
1474
+ CONNECTION_DIAGNOSTICS["terminal_restarts"] = CONNECTION_DIAGNOSTICS.get("terminal_restarts", 0) + 1
1475
+ time.sleep(max(BOOT_GRACE_PERIOD, sleep_interval))
1476
+ counter = 0
1477
+ continue
1468
1478
 
1469
- if not connected and counter >= max_retries:
1470
- logger.error("Cannot connect to Theta Data after %s attempts.", counter)
1479
+ counter += 1
1480
+ if counter % 10 == 0:
1481
+ logger.info("Waiting for ThetaTerminal connection (attempt %s/%s).", counter, max_retries)
1482
+ time.sleep(sleep_interval)
1483
+
1484
+ total_restart_cycles += 1
1485
+ if total_restart_cycles > MAX_TERMINAL_RESTART_CYCLES:
1486
+ logger.error(
1487
+ "Unable to connect to Theta Data after %s restart cycle(s) (%s attempts each).",
1488
+ MAX_TERMINAL_RESTART_CYCLES,
1489
+ max_retries,
1490
+ )
1491
+ raise ThetaDataConnectionError(
1492
+ f"Unable to connect to Theta Data after {MAX_TERMINAL_RESTART_CYCLES} restart cycle(s)."
1493
+ )
1471
1494
 
1472
- return client, connected
1495
+ logger.warning(
1496
+ "ThetaTerminal still disconnected after %s attempts; restarting (cycle %s/%s).",
1497
+ max_retries,
1498
+ total_restart_cycles,
1499
+ MAX_TERMINAL_RESTART_CYCLES,
1500
+ )
1501
+ client = start_theta_data_client(username=username, password=password)
1502
+ CONNECTION_DIAGNOSTICS["terminal_restarts"] = CONNECTION_DIAGNOSTICS.get("terminal_restarts", 0) + 1
1503
+ time.sleep(max(BOOT_GRACE_PERIOD, sleep_interval))
1473
1504
 
1474
1505
 
1475
1506
  def get_request(url: str, headers: dict, querystring: dict, username: str, password: str):
1476
1507
  all_responses = []
1477
1508
  next_page_url = None
1478
1509
  page_count = 0
1510
+ consecutive_disconnects = 0
1511
+ restart_budget = 3
1479
1512
 
1480
1513
  # Lightweight liveness probe before issuing the request
1481
1514
  check_connection(username=username, password=password, wait_for_connection=False)
@@ -1498,25 +1531,52 @@ def get_request(url: str, headers: dict, querystring: dict, username: str, passw
1498
1531
  )
1499
1532
 
1500
1533
  response = requests.get(request_url, headers=headers, params=request_params)
1534
+ status_code = response.status_code
1501
1535
  # Status code 472 means "No data" - this is valid, return None
1502
- if response.status_code == 472:
1536
+ if status_code == 472:
1503
1537
  logger.warning(f"No data available for request: {response.text[:200]}")
1504
1538
  # DEBUG-LOG: API response - no data
1505
1539
  logger.debug(
1506
1540
  "[THETA][DEBUG][API][RESPONSE] status=472 result=NO_DATA"
1507
1541
  )
1542
+ consecutive_disconnects = 0
1508
1543
  return None
1544
+ elif status_code == 474:
1545
+ consecutive_disconnects += 1
1546
+ logger.warning("Received 474 from Theta Data (attempt %s): %s", counter + 1, response.text[:200])
1547
+ if consecutive_disconnects >= 2:
1548
+ if restart_budget <= 0:
1549
+ logger.error("Restart budget exhausted after repeated 474 responses.")
1550
+ raise ValueError("Cannot connect to Theta Data!")
1551
+ logger.warning(
1552
+ "Restarting ThetaTerminal after %s consecutive 474 responses (restart budget remaining %s).",
1553
+ consecutive_disconnects,
1554
+ restart_budget - 1,
1555
+ )
1556
+ restart_budget -= 1
1557
+ start_theta_data_client(username=username, password=password)
1558
+ CONNECTION_DIAGNOSTICS["terminal_restarts"] = CONNECTION_DIAGNOSTICS.get("terminal_restarts", 0) + 1
1559
+ check_connection(username=username, password=password, wait_for_connection=True)
1560
+ time.sleep(max(BOOT_GRACE_PERIOD, CONNECTION_RETRY_SLEEP))
1561
+ consecutive_disconnects = 0
1562
+ counter = 0
1563
+ else:
1564
+ check_connection(username=username, password=password, wait_for_connection=True)
1565
+ time.sleep(CONNECTION_RETRY_SLEEP)
1566
+ continue
1509
1567
  # If status code is not 200, then we are not connected
1510
- elif response.status_code != 200:
1511
- logger.warning(f"Non-200 status code {response.status_code}: {response.text[:200]}")
1568
+ elif status_code != 200:
1569
+ logger.warning(f"Non-200 status code {status_code}: {response.text[:200]}")
1512
1570
  # DEBUG-LOG: API response - error
1513
1571
  logger.debug(
1514
1572
  "[THETA][DEBUG][API][RESPONSE] status=%d result=ERROR",
1515
- response.status_code
1573
+ status_code
1516
1574
  )
1517
1575
  check_connection(username=username, password=password, wait_for_connection=True)
1576
+ consecutive_disconnects = 0
1518
1577
  else:
1519
1578
  json_resp = response.json()
1579
+ consecutive_disconnects = 0
1520
1580
 
1521
1581
  # DEBUG-LOG: API response - success
1522
1582
  response_rows = len(json_resp.get("response", [])) if isinstance(json_resp.get("response"), list) else 0
@@ -1542,6 +1602,9 @@ def get_request(url: str, headers: dict, querystring: dict, username: str, passw
1542
1602
  else:
1543
1603
  break
1544
1604
 
1605
+ except ThetaDataConnectionError as exc:
1606
+ logger.error("Theta Data connection failed after supervised restarts: %s", exc)
1607
+ raise
1545
1608
  except Exception as e:
1546
1609
  logger.warning(f"Exception during request (attempt {counter + 1}): {e}")
1547
1610
  check_connection(username=username, password=password, wait_for_connection=True)
@@ -1551,7 +1614,7 @@ def get_request(url: str, headers: dict, querystring: dict, username: str, passw
1551
1614
 
1552
1615
  counter += 1
1553
1616
  if counter > 1:
1554
- raise ValueError("Cannot connect to Theta Data!")
1617
+ raise ThetaDataConnectionError("Unable to connect to Theta Data after repeated retries.")
1555
1618
 
1556
1619
  # Store this page's response data
1557
1620
  page_count += 1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lumibot
3
- Version: 4.2.7
3
+ Version: 4.2.9
4
4
  Summary: Backtesting and Trading Library, Made by Lumiwealth
5
5
  Home-page: https://github.com/Lumiwealth/lumibot
6
6
  Author: Robert Grzesik
@@ -132,7 +132,7 @@ lumibot/tools/polygon_helper_async.py,sha256=YHDXa9kmkkn8jh7hToY6GP5etyXS9Tj-uky
132
132
  lumibot/tools/polygon_helper_polars_optimized.py,sha256=NaIZ-5Av-G2McPEKHyJ-x65W72W_Agnz4lRgvXfQp8c,30415
133
133
  lumibot/tools/projectx_helpers.py,sha256=EIemLfbG923T_RBV_i6s6A9xgs7dt0et0oCnhFwdWfA,58299
134
134
  lumibot/tools/schwab_helper.py,sha256=CXnYhgsXOIb5MgmIYOp86aLxsBF9oeVrMGrjwl_GEv0,11768
135
- lumibot/tools/thetadata_helper.py,sha256=QXuFzfHi22L9sqzwL_mXZGhU2m9ortbE9U1-Kt7jSew,86743
135
+ lumibot/tools/thetadata_helper.py,sha256=F5CdaWZkfJYFrParvN8KhowRWc5cRmPL4TnI9SH-VpI,90111
136
136
  lumibot/tools/types.py,sha256=x-aQBeC6ZTN2-pUyxyo69Q0j5e0c_swdfe06kfrWSVc,1978
137
137
  lumibot/tools/yahoo_helper.py,sha256=htcKKkuktatIckVKfLc_ms0X75mXColysQhrZW244z8,19497
138
138
  lumibot/tools/yahoo_helper_polars_optimized.py,sha256=g9xBN-ReHSW4Aj9EMU_OncBXVS1HpfL8LTHit9ZxFY4,7417
@@ -142,7 +142,7 @@ lumibot/traders/trader.py,sha256=KMif3WoZtnSxA0BzoK3kvkTITNELrDFIortx1BYBv8s,967
142
142
  lumibot/trading_builtins/__init__.py,sha256=vH2QL5zLjL3slfEV1YW-BvQHtEYLCFkIWTZDfh3y8LE,87
143
143
  lumibot/trading_builtins/custom_stream.py,sha256=8_XiPT0JzyXrgnXCXoovGGUrWEfnG4ohIYMPfB_Nook,5264
144
144
  lumibot/trading_builtins/safe_list.py,sha256=IIjZOHSiZYK25A4WBts0oJaZNOJDsjZL65MOSHhE3Ig,1975
145
- lumibot-4.2.7.dist-info/licenses/LICENSE,sha256=fYhGIyxjyNXACgpNQS3xxpxDOaVOWRVxZMCRbsDv8k0,35130
145
+ lumibot-4.2.9.dist-info/licenses/LICENSE,sha256=fYhGIyxjyNXACgpNQS3xxpxDOaVOWRVxZMCRbsDv8k0,35130
146
146
  tests/__init__.py,sha256=3-VoT-nAuqMfwufd4ceN6fXaHl_zCfDCSXJOTp1ywYQ,393
147
147
  tests/conftest.py,sha256=UBw_2fx7r6TZPKus2b1Qxrzmd4bg8EEBnX1vCHUuSVA,3311
148
148
  tests/fixtures.py,sha256=wOHQsh1SGHnXe_PGi6kDWI30CS_Righi7Ig7vwSEKT4,9082
@@ -236,7 +236,7 @@ tests/test_session_manager.py,sha256=1qygN3aQ2Xe2uh4BMPm0E3V8KXLFNGq5qdL8KkZjef4
236
236
  tests/test_strategy_methods.py,sha256=j9Mhr6nnG1fkiVQXnx7gLjzGbeQmwt0UbJr_4plD36o,12539
237
237
  tests/test_strategy_price_guard.py,sha256=3GJdlfROwx6-adsSi8ZBrWaLOy9e-0N6V1eqpikj8e4,1540
238
238
  tests/test_thetadata_backwards_compat.py,sha256=RzNLhNZNJZ2hPkEDyG-T_4mRRXh5XqavK6r-OjfRASQ,3306
239
- tests/test_thetadata_helper.py,sha256=RQ-q7OfKc_2aO7-dInaVRTUrrI5iTh9oteYsdmfn4Lg,63735
239
+ tests/test_thetadata_helper.py,sha256=4EdFHoCTTKELU216ZNYGjfo6Uvq-29kK-TVpFLPWT3M,67263
240
240
  tests/test_thetadata_pandas_verification.py,sha256=MWUecqBY6FGFslWLRo_C5blGbom_unmXCZikAfZXLks,6553
241
241
  tests/test_tradier.py,sha256=iCEM2FTxJSzJ2oLNaRqSx05XaX_DCiMzLx1aEYPANko,33280
242
242
  tests/test_tradier_data.py,sha256=1jTxDzQtzaC42CQJVXMRMElBwExy1mVci3NFfKjjVH0,13363
@@ -282,7 +282,7 @@ tests/backtest/test_thetadata.py,sha256=xWYfC9C4EhbMDb29qyZWHO3sSWaLIPzzvcMbHCt5
282
282
  tests/backtest/test_thetadata_comprehensive.py,sha256=-gN3xLJcJtlB-k4vlaK82DCZDGDmr0LNZZDzn-aN3l4,26120
283
283
  tests/backtest/test_thetadata_vs_polygon.py,sha256=dZqsrOx3u3cz-1onIO6o5BDRjI1ey7U9vIkZupfXoig,22831
284
284
  tests/backtest/test_yahoo.py,sha256=2FguUTUMC9_A20eqxnZ17rN3tT9n6hyvJHaL98QKpqY,3443
285
- lumibot-4.2.7.dist-info/METADATA,sha256=cCEnxjhYBqtO5Uvd2pEJ90ULy2OlCiX1FHSaJPEpXAI,12093
286
- lumibot-4.2.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
287
- lumibot-4.2.7.dist-info/top_level.txt,sha256=otUnUjDFVASauEDiTiAzNgMyqQ1B6jjS3QqqP-WSx38,14
288
- lumibot-4.2.7.dist-info/RECORD,,
285
+ lumibot-4.2.9.dist-info/METADATA,sha256=Bis7WU6RY3Wp7YXHsNa7-xklQzQcXRi5zcJzZhC2j0w,12093
286
+ lumibot-4.2.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
287
+ lumibot-4.2.9.dist-info/top_level.txt,sha256=otUnUjDFVASauEDiTiAzNgMyqQ1B6jjS3QqqP-WSx38,14
288
+ lumibot-4.2.9.dist-info/RECORD,,
@@ -1426,8 +1426,10 @@ class TestThetaDataChainsCaching:
1426
1426
 
1427
1427
  monkeypatch.setattr(thetadata_helper, "build_historical_chain", lambda **kwargs: None)
1428
1428
  monkeypatch.setattr(thetadata_helper, "LUMIBOT_CACHE_FOLDER", str(tmp_path))
1429
+ monkeypatch.delenv("BACKTESTING_QUIET_LOGS", raising=False)
1430
+ caplog.set_level(logging.WARNING, logger="lumibot.tools.thetadata_helper")
1429
1431
 
1430
- with caplog.at_level(logging.WARNING):
1432
+ with caplog.at_level(logging.WARNING, logger="lumibot.tools.thetadata_helper"):
1431
1433
  result = thetadata_helper.get_chains_cached("user", "pass", asset, test_date)
1432
1434
 
1433
1435
  cache_folder = Path(tmp_path) / "thetadata" / "stock" / "option_chains"
@@ -1508,8 +1510,10 @@ def test_build_historical_chain_returns_none_when_no_dates(monkeypatch, caplog):
1508
1510
  raise AssertionError(f"Unexpected URL {url}")
1509
1511
 
1510
1512
  monkeypatch.setattr(thetadata_helper, "get_request", fake_get_request)
1513
+ monkeypatch.delenv("BACKTESTING_QUIET_LOGS", raising=False)
1514
+ caplog.set_level(logging.WARNING, logger="lumibot.tools.thetadata_helper")
1511
1515
 
1512
- with caplog.at_level(logging.WARNING):
1516
+ with caplog.at_level(logging.WARNING, logger="lumibot.tools.thetadata_helper"):
1513
1517
  result = thetadata_helper.build_historical_chain("user", "pass", asset, as_of_date)
1514
1518
 
1515
1519
  assert result is None
@@ -1525,14 +1529,83 @@ def test_build_historical_chain_empty_response(monkeypatch, caplog):
1525
1529
  raise AssertionError("Unexpected call after empty expirations")
1526
1530
 
1527
1531
  monkeypatch.setattr(thetadata_helper, "get_request", fake_get_request)
1532
+ monkeypatch.delenv("BACKTESTING_QUIET_LOGS", raising=False)
1533
+ caplog.set_level(logging.WARNING, logger="lumibot.tools.thetadata_helper")
1528
1534
 
1529
- with caplog.at_level(logging.WARNING):
1535
+ with caplog.at_level(logging.WARNING, logger="lumibot.tools.thetadata_helper"):
1530
1536
  result = thetadata_helper.build_historical_chain("user", "pass", asset, as_of_date)
1531
1537
 
1532
1538
  assert result is None
1533
1539
  assert "returned no expirations" in caplog.text
1534
1540
 
1535
1541
 
1542
+ class TestThetaDataConnectionSupervision:
1543
+
1544
+ def setup_method(self):
1545
+ thetadata_helper.reset_connection_diagnostics()
1546
+
1547
+ def test_check_connection_recovers_after_restart(self, monkeypatch):
1548
+ statuses = iter(["DISCONNECTED", "DISCONNECTED", "CONNECTED"])
1549
+
1550
+ class FakeResponse:
1551
+ def __init__(self, text):
1552
+ self.text = text
1553
+
1554
+ def fake_get(url, timeout):
1555
+ try:
1556
+ text = next(statuses)
1557
+ except StopIteration:
1558
+ text = "CONNECTED"
1559
+ return FakeResponse(text)
1560
+
1561
+ start_calls = []
1562
+
1563
+ def fake_start(username, password):
1564
+ start_calls.append((username, password))
1565
+ return object()
1566
+
1567
+ monkeypatch.setattr(thetadata_helper.requests, "get", fake_get)
1568
+ monkeypatch.setattr(thetadata_helper, "start_theta_data_client", fake_start)
1569
+ monkeypatch.setattr(thetadata_helper, "is_process_alive", lambda: True)
1570
+ monkeypatch.setattr(thetadata_helper, "CONNECTION_MAX_RETRIES", 2, raising=False)
1571
+ monkeypatch.setattr(thetadata_helper, "MAX_TERMINAL_RESTART_CYCLES", 2, raising=False)
1572
+ monkeypatch.setattr(thetadata_helper, "BOOT_GRACE_PERIOD", 0, raising=False)
1573
+ monkeypatch.setattr(thetadata_helper, "CONNECTION_RETRY_SLEEP", 0, raising=False)
1574
+ monkeypatch.setattr(thetadata_helper.time, "sleep", lambda *args, **kwargs: None)
1575
+
1576
+ client, connected = thetadata_helper.check_connection("user", "pass", wait_for_connection=True)
1577
+
1578
+ assert connected is True
1579
+ assert len(start_calls) == 1
1580
+ assert thetadata_helper.CONNECTION_DIAGNOSTICS["terminal_restarts"] >= 1
1581
+
1582
+ def test_check_connection_raises_after_restart_cycles(self, monkeypatch):
1583
+ statuses = iter(["DISCONNECTED"] * 10)
1584
+
1585
+ class FakeResponse:
1586
+ def __init__(self, text):
1587
+ self.text = text
1588
+
1589
+ def fake_get(url, timeout):
1590
+ try:
1591
+ text = next(statuses)
1592
+ except StopIteration:
1593
+ text = "DISCONNECTED"
1594
+ return FakeResponse(text)
1595
+
1596
+ monkeypatch.setattr(thetadata_helper.requests, "get", fake_get)
1597
+ monkeypatch.setattr(thetadata_helper, "start_theta_data_client", lambda *args, **kwargs: object())
1598
+ monkeypatch.setattr(thetadata_helper, "is_process_alive", lambda: True)
1599
+ monkeypatch.setattr(thetadata_helper, "CONNECTION_MAX_RETRIES", 1, raising=False)
1600
+ monkeypatch.setattr(thetadata_helper, "MAX_TERMINAL_RESTART_CYCLES", 1, raising=False)
1601
+ monkeypatch.setattr(thetadata_helper, "BOOT_GRACE_PERIOD", 0, raising=False)
1602
+ monkeypatch.setattr(thetadata_helper, "CONNECTION_RETRY_SLEEP", 0, raising=False)
1603
+ monkeypatch.setattr(thetadata_helper.time, "sleep", lambda *args, **kwargs: None)
1604
+
1605
+ with pytest.raises(thetadata_helper.ThetaDataConnectionError):
1606
+ thetadata_helper.check_connection("user", "pass", wait_for_connection=True)
1607
+
1608
+
1536
1609
  def test_finalize_day_frame_handles_dst_fallback():
1537
1610
  tz = pytz.timezone("America/New_York")
1538
1611
  utc = pytz.UTC