65 lines
2.9 KiB
Python
65 lines
2.9 KiB
Python
import asyncio
|
|
import dataclasses
|
|
from pathlib import Path
|
|
from unittest.mock import AsyncMock, Mock
|
|
|
|
import pytest
|
|
from ib_async import Stock
|
|
|
|
from ibkr_quant import cli
|
|
from ibkr_quant.broker import AccountState, Broker
|
|
from ibkr_quant.config import load_config
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"mode, error, price, expected, absent",
|
|
[
|
|
(3, (10197, "No market data during competing live session"), None,
|
|
"Log out of the competing live market-data session", "set ib.market_data_type = 3"),
|
|
(1, (10197, "No market data during competing live session"), None,
|
|
"changing ib.market_data_type cannot resolve this session conflict", "set ib.market_data_type = 3"),
|
|
(1, (354, "Requested market data is not subscribed"), None,
|
|
"set ib.market_data_type = 3", "competing live market-data session"),
|
|
(3, (354, "Requested market data is not subscribed"), None,
|
|
"Delayed data is already requested", "set ib.market_data_type = 3"),
|
|
(3, None, None, "no valid snapshot received", "set ib.market_data_type = 3"),
|
|
(3, None, 600.0, "SPY snapshot 600.00 (requested market_data_type=3)", "no SPY price"),
|
|
],
|
|
)
|
|
def test_check_explains_snapshot_result(monkeypatch, capsys, mode, error, price, expected, absent):
|
|
cfg = load_config(Path(__file__).resolve().parents[1] / "config.toml")
|
|
cfg = dataclasses.replace(cfg, ib=dataclasses.replace(cfg.ib, market_data_type=mode))
|
|
|
|
async def run():
|
|
broker = Broker(cfg.ib)
|
|
listeners = len(broker.ib.errorEvent)
|
|
contract = Stock("SPY", "SMART", "USD", conId=756733)
|
|
monkeypatch.setattr(Broker, "__aenter__", AsyncMock(return_value=broker))
|
|
monkeypatch.setattr(Broker, "__aexit__", AsyncMock(return_value=None))
|
|
monkeypatch.setattr(cli, "Broker", lambda _: broker)
|
|
monkeypatch.setattr(broker, "account_state", AsyncMock(return_value=AccountState("DU123", 1000, 1000, [])))
|
|
monkeypatch.setattr(broker, "open_trades", AsyncMock(return_value=[]))
|
|
monkeypatch.setattr(broker, "qualify_stocks", AsyncMock(return_value=({"SPY": contract}, [])))
|
|
monkeypatch.setattr(broker.ib.client, "getReqId", Mock(return_value=59))
|
|
|
|
def respond(req_id, *args):
|
|
# Exercise ib_async's real request completion and errorEvent path.
|
|
if error:
|
|
broker.ib.wrapper.error(req_id, *error, "")
|
|
else:
|
|
if price is not None:
|
|
broker.ib.wrapper.priceSizeTick(req_id, 4, price, 1)
|
|
broker.ib.wrapper.tickSnapshotEnd(req_id)
|
|
|
|
monkeypatch.setattr(broker.ib.client, "reqMktData", respond)
|
|
await cli.check(cfg)
|
|
assert len(broker.ib.errorEvent) == listeners
|
|
|
|
asyncio.run(run())
|
|
output = capsys.readouterr().out
|
|
assert "0 open orders" in output
|
|
assert expected in output
|
|
assert absent not in output
|
|
if error:
|
|
assert f"IBKR {error[0]}: {error[1]}" in output
|