From 5d12ae700c035a54f645996180f8f0bd7652adaf Mon Sep 17 00:00:00 2001 From: Chris Sellers Date: Mon, 1 Apr 2024 22:01:23 +1100 Subject: [PATCH] Refine mypy config and fix issues --- .pre-commit-config.yaml | 4 +- nautilus_trader/adapters/betfair/client.py | 10 ++-- nautilus_trader/backtest/__main__.py | 1 + nautilus_trader/core/nautilus_pyo3.pyi | 2 +- .../strategies/ema_cross_stop_entry.py | 11 +++- nautilus_trader/indicators/ta_lib/manager.py | 4 ++ nautilus_trader/test_kit/mocks/strategies.py | 60 ++++++++++++++----- nautilus_trader/test_kit/rust/events_pyo3.py | 2 +- nautilus_trader/test_kit/stubs/config.py | 2 +- pyproject.toml | 11 ++++ .../betfair/test_betfair_execution.py | 6 +- .../adapters/betfair/test_betfair_parsing.py | 4 +- .../adapters/betfair/test_kit.py | 2 +- .../adapters/bybit/schema/test_instruments.py | 8 +-- .../adapters/bybit/test_parsing.py | 30 +++++++--- .../adapters/bybit/test_providers.py | 2 +- tests/unit_tests/backtest/test_engine.py | 2 +- .../model/objects/test_money_pyo3.py | 4 +- 18 files changed, 118 insertions(+), 47 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 57a9a3751522..70686c716370 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -115,10 +115,8 @@ repos: hooks: - id: mypy args: [ - "--ignore-missing-imports", + "--config", "pyproject.toml", "--allow-incomplete-defs", - "--no-strict-optional", # Fixing in progress - "--warn-no-return", ] additional_dependencies: [ msgspec, diff --git a/nautilus_trader/adapters/betfair/client.py b/nautilus_trader/adapters/betfair/client.py index 7dbb3ec638a3..61e47098ef0e 100644 --- a/nautilus_trader/adapters/betfair/client.py +++ b/nautilus_trader/adapters/betfair/client.py @@ -75,7 +75,7 @@ def __init__( username: str, password: str, app_key: str, - ): + ) -> None: # Config self.username = username self.password = password @@ -124,7 +124,7 @@ def update_headers(self, login_resp: LoginResponse) -> None: }, ) - def reset_headers(self): + def reset_headers(self) -> None: self._headers = { "Accept": "application/json", "Content-Type": "application/x-www-form-urlencoded", @@ -132,7 +132,7 @@ def reset_headers(self): "X-Application": self.app_key, } - async def connect(self): + async def connect(self) -> None: if self.session_token is not None: self._log.warning("Session token exists (already connected), skipping") return @@ -145,12 +145,12 @@ async def connect(self): self._log.info("Login success", color=LogColor.GREEN) self.update_headers(login_resp=resp) - async def disconnect(self): + async def disconnect(self) -> None: self._log.info("Disconnecting...") self.reset_headers() self._log.info("Disconnected", color=LogColor.GREEN) - async def keep_alive(self): + async def keep_alive(self) -> None: """ Renew authentication. """ diff --git a/nautilus_trader/backtest/__main__.py b/nautilus_trader/backtest/__main__.py index d07d2d62e1e7..936771fe31db 100644 --- a/nautilus_trader/backtest/__main__.py +++ b/nautilus_trader/backtest/__main__.py @@ -36,6 +36,7 @@ def main( with fsspec.open(fsspec_url, "rb") as f: data = f.read().decode() else: + assert raw is not None # Type checking data = raw.encode() configs = msgspec.json.decode( diff --git a/nautilus_trader/core/nautilus_pyo3.pyi b/nautilus_trader/core/nautilus_pyo3.pyi index 1c3bd6207de4..f310bdf30f24 100644 --- a/nautilus_trader/core/nautilus_pyo3.pyi +++ b/nautilus_trader/core/nautilus_pyo3.pyi @@ -1126,7 +1126,7 @@ class AccountState: self, account_id: AccountId, account_type: AccountType, - base_currency: Currency, + base_currency: Currency | None, balances: list[AccountBalance], margins: list[MarginBalance], is_reported: bool, diff --git a/nautilus_trader/examples/strategies/ema_cross_stop_entry.py b/nautilus_trader/examples/strategies/ema_cross_stop_entry.py index d7544addbd4b..6304be9ec0c6 100644 --- a/nautilus_trader/examples/strategies/ema_cross_stop_entry.py +++ b/nautilus_trader/examples/strategies/ema_cross_stop_entry.py @@ -36,6 +36,7 @@ from nautilus_trader.model.events import OrderFilled from nautilus_trader.model.identifiers import InstrumentId from nautilus_trader.model.instruments import Instrument +from nautilus_trader.model.objects import Price from nautilus_trader.model.orders import MarketIfTouchedOrder from nautilus_trader.model.orders import TrailingStopMarketOrder from nautilus_trader.trading.strategy import Strategy @@ -145,7 +146,7 @@ def __init__(self, config: EMACrossStopEntryConfig) -> None: self.atr = AverageTrueRange(config.atr_period) self.instrument: Instrument | None = None # Initialized in `on_start()` - self.tick_size = None # Initialized in `on_start()` + self.tick_size: Price | None = None # Initialized in `on_start()` # Users order management variables self.entry = None @@ -266,6 +267,10 @@ def entry_buy(self, last_bar: Bar) -> None: self.log.error("No instrument loaded") return + if not self.tick_size: + self.log.error("No tick size loaded") + return + order: MarketIfTouchedOrder = self.order_factory.market_if_touched( instrument_id=self.instrument_id, order_side=OrderSide.BUY, @@ -301,6 +306,10 @@ def entry_sell(self, last_bar: Bar) -> None: self.log.error("No instrument loaded") return + if not self.tick_size: + self.log.error("No tick size loaded") + return + order: MarketIfTouchedOrder = self.order_factory.market_if_touched( instrument_id=self.instrument_id, order_side=OrderSide.SELL, diff --git a/nautilus_trader/indicators/ta_lib/manager.py b/nautilus_trader/indicators/ta_lib/manager.py index ac984f1f5419..3c8fda1cfd3b 100644 --- a/nautilus_trader/indicators/ta_lib/manager.py +++ b/nautilus_trader/indicators/ta_lib/manager.py @@ -392,6 +392,9 @@ def _update_ta_outputs(self, append: bool = True) -> None: """ self._log.debug("Calculating outputs.") + if self._input_deque is None: + return + combined_output = np.zeros(1, dtype=self._output_dtypes) combined_output["ts_event"] = self._input_deque[-1]["ts_event"].item() combined_output["ts_init"] = self._input_deque[-1]["ts_init"].item() @@ -402,6 +405,7 @@ def _update_ta_outputs(self, append: bool = True) -> None: combined_output["volume"] = self._input_deque[-1]["volume"].item() input_array = np.concatenate(self._input_deque) + assert self._indicators # Type checking for indicator in self._indicators: self._log.debug(f"Calculating {indicator.name} outputs.") inputs_dict = {name: input_array[name] for name in input_array.dtype.names} diff --git a/nautilus_trader/test_kit/mocks/strategies.py b/nautilus_trader/test_kit/mocks/strategies.py index a2a483d913ba..f70dc3ce4da1 100644 --- a/nautilus_trader/test_kit/mocks/strategies.py +++ b/nautilus_trader/test_kit/mocks/strategies.py @@ -47,28 +47,40 @@ def __init__(self, bar_type: BarType) -> None: self.calls: list[str] = [] def on_start(self) -> None: - self.calls.append(inspect.currentframe().f_code.co_name) + current_frame = inspect.currentframe() + assert current_frame # Type checking + self.calls.append(current_frame.f_code.co_name) self.register_indicator_for_bars(self.bar_type, self.ema1) self.register_indicator_for_bars(self.bar_type, self.ema2) def on_instrument(self, instrument) -> None: - self.calls.append(inspect.currentframe().f_code.co_name) + current_frame = inspect.currentframe() + assert current_frame # Type checking + self.calls.append(current_frame.f_code.co_name) self.store.append(instrument) def on_ticker(self, ticker): - self.calls.append(inspect.currentframe().f_code.co_name) + current_frame = inspect.currentframe() + assert current_frame # Type checking + self.calls.append(current_frame.f_code.co_name) self.store.append(ticker) def on_quote_tick(self, tick): - self.calls.append(inspect.currentframe().f_code.co_name) + current_frame = inspect.currentframe() + assert current_frame # Type checking + self.calls.append(current_frame.f_code.co_name) self.store.append(tick) def on_trade_tick(self, tick) -> None: - self.calls.append(inspect.currentframe().f_code.co_name) + current_frame = inspect.currentframe() + assert current_frame # Type checking + self.calls.append(current_frame.f_code.co_name) self.store.append(tick) def on_bar(self, bar) -> None: - self.calls.append(inspect.currentframe().f_code.co_name) + current_frame = inspect.currentframe() + assert current_frame # Type checking + self.calls.append(current_frame.f_code.co_name) self.store.append(bar) if bar.bar_type != self.bar_type: @@ -94,36 +106,54 @@ def on_bar(self, bar) -> None: self.position_id = sell_order.client_order_id def on_data(self, data) -> None: - self.calls.append(inspect.currentframe().f_code.co_name) + current_frame = inspect.currentframe() + assert current_frame # Type checking + self.calls.append(current_frame.f_code.co_name) self.store.append(data) def on_strategy_data(self, data) -> None: - self.calls.append(inspect.currentframe().f_code.co_name) + current_frame = inspect.currentframe() + assert current_frame # Type checking + self.calls.append(current_frame.f_code.co_name) self.store.append(data) def on_event(self, event) -> None: - self.calls.append(inspect.currentframe().f_code.co_name) + current_frame = inspect.currentframe() + assert current_frame # Type checking + self.calls.append(current_frame.f_code.co_name) self.store.append(event) def on_stop(self) -> None: - self.calls.append(inspect.currentframe().f_code.co_name) + current_frame = inspect.currentframe() + assert current_frame # Type checking + self.calls.append(current_frame.f_code.co_name) def on_resume(self) -> None: - self.calls.append(inspect.currentframe().f_code.co_name) + current_frame = inspect.currentframe() + assert current_frame # Type checking + self.calls.append(current_frame.f_code.co_name) def on_reset(self) -> None: - self.calls.append(inspect.currentframe().f_code.co_name) + current_frame = inspect.currentframe() + assert current_frame # Type checking + self.calls.append(current_frame.f_code.co_name) def on_save(self) -> dict[str, bytes]: - self.calls.append(inspect.currentframe().f_code.co_name) + current_frame = inspect.currentframe() + assert current_frame # Type checking + self.calls.append(current_frame.f_code.co_name) return {"UserState": b"1"} def on_load(self, state: dict[str, bytes]) -> None: - self.calls.append(inspect.currentframe().f_code.co_name) + current_frame = inspect.currentframe() + assert current_frame # Type checking + self.calls.append(current_frame.f_code.co_name) self.store.append(state) def on_dispose(self) -> None: - self.calls.append(inspect.currentframe().f_code.co_name) + current_frame = inspect.currentframe() + assert current_frame # Type checking + self.calls.append(current_frame.f_code.co_name) class KaboomStrategy(Strategy): diff --git a/nautilus_trader/test_kit/rust/events_pyo3.py b/nautilus_trader/test_kit/rust/events_pyo3.py index ac1bdfaa57b6..3faec3fdfc16 100644 --- a/nautilus_trader/test_kit/rust/events_pyo3.py +++ b/nautilus_trader/test_kit/rust/events_pyo3.py @@ -483,7 +483,7 @@ def order_filled( order_side=order.side, order_type=order.order_type, last_qty=last_qty, - last_px=last_px or order.price, + last_px=last_px or order.price or Price.from_str("1.00000"), currency=instrument.quote_currency, commission=commission, liquidity_side=liquidity_side, diff --git a/nautilus_trader/test_kit/stubs/config.py b/nautilus_trader/test_kit/stubs/config.py index 87d915304c3c..bbd37f230fc1 100644 --- a/nautilus_trader/test_kit/stubs/config.py +++ b/nautilus_trader/test_kit/stubs/config.py @@ -95,12 +95,12 @@ def strategies_config() -> list[ImportableStrategyConfig]: @staticmethod def backtest_engine_config( + catalog: ParquetDataCatalog, log_level="INFO", bypass_logging: bool = True, bypass_risk: bool = False, allow_cash_position: bool = True, persist: bool = False, - catalog: ParquetDataCatalog | None = None, strategies: list[ImportableStrategyConfig] | None = None, ) -> BacktestEngineConfig: if persist: diff --git a/pyproject.toml b/pyproject.toml index 5c134ed1067d..a9598b70ddb7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -259,10 +259,21 @@ disallow_incomplete_defs = true explicit_package_bases = true ignore_missing_imports = true namespace_packages = true +no_strict_optional = false warn_no_return = true warn_unused_configs = true warn_unused_ignores = true +[[tool.mypy.overrides]] +no_strict_optional = true +module = [ + "examples/*", + "nautilus_trader/adapters/betfair/*", + "nautilus_trader/adapters/binance/*", + "nautilus_trader/adapters/interactive_brokers/*", + "nautilus_trader/indicators/ta_lib/*", +] + [tool.pytest.ini_options] testpaths = ["tests"] addopts = "-ra --new-first --failed-first --doctest-modules --doctest-glob=\"*.pyx\"" diff --git a/tests/integration_tests/adapters/betfair/test_betfair_execution.py b/tests/integration_tests/adapters/betfair/test_betfair_execution.py index 9df6e5ba26db..78038eb83fda 100644 --- a/tests/integration_tests/adapters/betfair/test_betfair_execution.py +++ b/tests/integration_tests/adapters/betfair/test_betfair_execution.py @@ -845,13 +845,14 @@ async def test_generate_order_status_report_client_id( instrument_provider.add(instrument) # Act - report: OrderStatusReport = await exec_client.generate_order_status_report( + report: OrderStatusReport | None = await exec_client.generate_order_status_report( instrument_id=instrument.id, venue_order_id=VenueOrderId("1"), client_order_id=None, ) # Assert + assert report assert report.order_status == OrderStatus.ACCEPTED assert report.price == Price(5.0, BETFAIR_PRICE_PRECISION) assert report.quantity == Quantity(10.0, BETFAIR_QUANTITY_PRECISION) @@ -874,13 +875,14 @@ async def test_generate_order_status_report_venue_order_id( venue_order_id = VenueOrderId("323427122115") # Act - report: OrderStatusReport = await exec_client.generate_order_status_report( + report: OrderStatusReport | None = await exec_client.generate_order_status_report( instrument_id=instrument.id, venue_order_id=venue_order_id, client_order_id=client_order_id, ) # Assert + assert report assert report.order_status == OrderStatus.ACCEPTED assert report.price == Price(5.0, BETFAIR_PRICE_PRECISION) assert report.quantity == Quantity(10.0, BETFAIR_QUANTITY_PRECISION) diff --git a/tests/integration_tests/adapters/betfair/test_betfair_parsing.py b/tests/integration_tests/adapters/betfair/test_betfair_parsing.py index 27444a9d2ccf..6f803dedb4bd 100644 --- a/tests/integration_tests/adapters/betfair/test_betfair_parsing.py +++ b/tests/integration_tests/adapters/betfair/test_betfair_parsing.py @@ -306,7 +306,7 @@ def test_order_book_integrity(self, filename, book_count) -> None: result = [book.count for book in books.values()] assert result == book_count - def test_betfair_trade_sizes(self): # noqa: C901 + def test_betfair_trade_sizes(self) -> None: # noqa: C901 mcms = BetfairDataProvider.read_mcm("1.206064380.bz2") trade_ticks: dict[InstrumentId, list[TradeTick]] = defaultdict(list) betfair_tv: dict[int, dict[float, float]] = {} @@ -338,7 +338,7 @@ def test_betfair_trade_sizes(self): # noqa: C901 class TestBetfairParsing: - def setup(self): + def setup(self) -> None: # Fixture Setup self.loop = asyncio.new_event_loop() self.clock = LiveClock() diff --git a/tests/integration_tests/adapters/betfair/test_kit.py b/tests/integration_tests/adapters/betfair/test_kit.py index 6ef4e319e2d2..786f546d53fa 100644 --- a/tests/integration_tests/adapters/betfair/test_kit.py +++ b/tests/integration_tests/adapters/betfair/test_kit.py @@ -250,7 +250,7 @@ def betfair_backtest_run_config( ), ] if add_strategy - else None + else [] ), ) run_config = BacktestRunConfig( # typing: ignore diff --git a/tests/integration_tests/adapters/bybit/schema/test_instruments.py b/tests/integration_tests/adapters/bybit/schema/test_instruments.py index 11d6cc27bca4..7335348327bd 100644 --- a/tests/integration_tests/adapters/bybit/schema/test_instruments.py +++ b/tests/integration_tests/adapters/bybit/schema/test_instruments.py @@ -34,12 +34,12 @@ class TestBybitInstruments: - def setup(self): + def setup(self) -> None: # linear linear_data: BybitInstrumentsLinearResponse = msgspec.json.Decoder( BybitInstrumentsLinearResponse, ).decode( - pkgutil.get_data( + pkgutil.get_data( # type: ignore [arg-type] "tests.integration_tests.adapters.bybit.resources.http_responses.linear", "instruments.json", ), @@ -49,7 +49,7 @@ def setup(self): spot_data: BybitInstrumentsSpotResponse = msgspec.json.Decoder( BybitInstrumentsSpotResponse, ).decode( - pkgutil.get_data( + pkgutil.get_data( # type: ignore [arg-type] "tests.integration_tests.adapters.bybit.resources.http_responses.spot", "instruments.json", ), @@ -59,7 +59,7 @@ def setup(self): option_data: BybitInstrumentsOptionResponse = msgspec.json.Decoder( BybitInstrumentsOptionResponse, ).decode( - pkgutil.get_data( + pkgutil.get_data( # type: ignore [arg-type] "tests.integration_tests.adapters.bybit.resources.http_responses.option", "instruments.json", ), diff --git a/tests/integration_tests/adapters/bybit/test_parsing.py b/tests/integration_tests/adapters/bybit/test_parsing.py index 613cf2caab78..7c0a7113920c 100644 --- a/tests/integration_tests/adapters/bybit/test_parsing.py +++ b/tests/integration_tests/adapters/bybit/test_parsing.py @@ -28,7 +28,7 @@ class TestBybitParsing: - def setup(self): + def setup(self) -> None: self._enum_parser = BybitEnumParser() self.instrument: str = "ETHUSDT.BINANCE" @@ -50,12 +50,12 @@ def setup(self): ["ETHUSDT.BYBIT-1-MONTH-LAST-EXTERNAL", "M"], ], ) - def test_parse_bybit_kline_correct(self, bar_type, bybit_kline_interval): + def test_parse_bybit_kline_correct(self, bar_type: str, bybit_kline_interval: str) -> None: bar_type = BarType.from_str(bar_type) result = self._enum_parser.parse_bybit_kline(bar_type) assert result.value == bybit_kline_interval - def test_parse_bybit_kline_incorrect(self): + def test_parse_bybit_kline_incorrect(self) -> None: # MINUTE with pytest.raises(ValueError): self._enum_parser.parse_bybit_kline( @@ -90,7 +90,11 @@ def test_parse_bybit_kline_incorrect(self): [BybitOrderSide.SELL, OrderSide.SELL], ], ) - def test_parse_bybit_order_side(self, bybit_order_side, order_side): + def test_parse_bybit_order_side( + self, + bybit_order_side: BybitOrderSide, + order_side: OrderSide, + ) -> None: result = self._enum_parser.parse_bybit_order_side(bybit_order_side) assert result == order_side @@ -101,7 +105,11 @@ def test_parse_bybit_order_side(self, bybit_order_side, order_side): [OrderSide.SELL, BybitOrderSide.SELL], ], ) - def test_parse_nautilus_order_side(self, order_side, bybit_order_side): + def test_parse_nautilus_order_side( + self, + order_side: OrderSide, + bybit_order_side: BybitOrderSide, + ) -> None: result = self._enum_parser.parse_nautilus_order_side(order_side) assert result == bybit_order_side @@ -114,7 +122,11 @@ def test_parse_nautilus_order_side(self, order_side, bybit_order_side): [BybitOrderStatus.CANCELED, OrderStatus.CANCELED], ], ) - def test_parse_bybit_order_status(self, bybit_order_status, order_status): + def test_parse_bybit_order_status( + self, + bybit_order_status: BybitOrderStatus, + order_status: OrderStatus, + ) -> None: result = self._enum_parser.parse_bybit_order_status(bybit_order_status) assert result == order_status @@ -127,6 +139,10 @@ def test_parse_bybit_order_status(self, bybit_order_status, order_status): [OrderStatus.CANCELED, BybitOrderStatus.CANCELED], ], ) - def test_parse_nautilus_order_status(self, order_status, bybit_order_status): + def test_parse_nautilus_order_status( + self, + order_status: OrderStatus, + bybit_order_status: BybitOrderStatus, + ) -> None: result = self._enum_parser.parse_nautilus_order_status(order_status) assert result == bybit_order_status diff --git a/tests/integration_tests/adapters/bybit/test_providers.py b/tests/integration_tests/adapters/bybit/test_providers.py index 1ecb556711b0..13dc76cb66f6 100644 --- a/tests/integration_tests/adapters/bybit/test_providers.py +++ b/tests/integration_tests/adapters/bybit/test_providers.py @@ -30,7 +30,7 @@ class TestBybitInstrumentProvider: - def setup(self): + def setup(self) -> None: self.clock = LiveClock() self.http_client: BybitHttpClient = BybitHttpClient( clock=self.clock, diff --git a/tests/unit_tests/backtest/test_engine.py b/tests/unit_tests/backtest/test_engine.py index 2ff6b9417fd7..31cb9d50131e 100644 --- a/tests/unit_tests/backtest/test_engine.py +++ b/tests/unit_tests/backtest/test_engine.py @@ -221,7 +221,7 @@ def test_persistence_files_cleaned_up(self, tmp_path: Path) -> None: path=tmp_path, fs_protocol="file", ) - config = TestConfigStubs.backtest_engine_config(persist=True, catalog=catalog) + config = TestConfigStubs.backtest_engine_config(catalog=catalog, persist=True) engine = TestComponentStubs.backtest_engine( config=config, instrument=self.usdjpy, diff --git a/tests/unit_tests/model/objects/test_money_pyo3.py b/tests/unit_tests/model/objects/test_money_pyo3.py index d04fbfb8fe28..5863c4d51ca0 100644 --- a/tests/unit_tests/model/objects/test_money_pyo3.py +++ b/tests/unit_tests/model/objects/test_money_pyo3.py @@ -37,12 +37,12 @@ def test_instantiate_with_nan_raises_value_error(self): def test_instantiate_with_none_value_raises_type_error(self) -> None: # Arrange, Act, Assert with pytest.raises(TypeError): - Money(None, currency=USD) + Money(None, currency=USD) # type: ignore def test_instantiate_with_none_currency_raises_type_error(self) -> None: # Arrange, Act, Assert with pytest.raises(TypeError): - Money(1.0, None) + Money(1.0, None) # type: ignore def test_instantiate_with_value_exceeding_positive_limit_raises_value_error(self) -> None: # Arrange, Act, Assert