Compare commits

..

12 Commits

Author SHA1 Message Date
Gud Boi 4ff6cd9afa Drop `Flume.feed`, it's unused yet causes import cycles.. 2026-02-24 15:50:49 -05:00
Gud Boi 201a9307d3 Just warn on single-bar nulls instead of bping
Replace the debug breakpoint with a warning-log when a single-bar
null-segment is detected in `get_null_segs()`. This lets the gap
analysis continue while still alerting about the anomaly.

Deats,
- extract the 3-bar window (before, null, after) and calculate
  a `gap: pendulum.Interval` for the warning msg.
- comment-out the old breakpoint block for optional debugging as needed.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-02-24 15:50:49 -05:00
Gud Boi b01f60e895 Lul, drop long unused poetry lock file 2026-02-24 15:50:49 -05:00
Gud Boi 081ca4e3fc Pin `pg` at latest official `0.14.0` release
Keep in masked GH sources lines for easy hackin against upstream
`master` branch when needed as well!
2026-02-24 15:50:49 -05:00
Gud Boi bff692b399 .ui._editors: log multiline styling and re-leveling 2026-02-24 15:50:49 -05:00
Gud Boi 0d3ac34189 .ui._lines: drop unused graphics-item import 2026-02-24 15:50:49 -05:00
Gud Boi 2653529af0 Add batch-submit API for gap annotations
Introduce `AnnotCtl.add_batch()` and `serve_rc_annots()` batch
handler to submit 1000s of gaps in single IPC msg instead of
per-annot round-trips. Server builds `GapAnnotations` from specs
and handles vectorized timestamp-to-index lookups.

Deats,
- add `'cmd': 'batch'` handler in `serve_rc_annots()`
- vectorized timestamp lookup via `np.searchsorted()` + masking
- build `gap_specs: list[dict]` from rect+arrow specs client-side
- create single `GapAnnotations` item for all gaps server-side
- handle `GapAnnotations.reposition()` in redraw handler
- add profiling to batch path for perf measurement
- support optional individual arrows for A/B comparison

Also,
- refactor `markup_gaps()` to collect specs + single batch call
- add `no_qt_updates()` context mgr for batch render ops
- add profiling to annotation teardown path
- add `GapAnnotations` case to `rm_annot()` match block

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-02-24 15:50:49 -05:00
Gud Boi 0ab0fd9f7f Add a `GapAnnotations` path-renderer
For a ~1000x perf gain says ol' claudy, our boi who wrote this entire
patch! Bo

Introduce `GapAnnotations` in `.ui._annotate` for batch-rendering gap
rects/arrows instead of individual `QGraphicsItem` instances. Uses
upstream's `pyqtgraph.Qt.internals.PrimitiveArray` for rects and
a `QPainterPath` for arrows. This API-replicates our prior annotator's
in view shape-graphics but now using (what we're dubbing)
"single-array-multiple-graphics" tech much like our `.ui._curve`
extensions to `pg` B)

Impl deats,
- batch draw ~1000 gaps in single paint call vs 1000 items
- arrows render in scene coords to maintain pixel size on zoom
- add vectorized timestamp-to-index lookup for repositioning
- cache bounding rect, rebuild on `reposition()` calls
- match `SelectRect` + `ArrowItem` visual style/colors
- skip reposition when timeframe doesn't match gap's period

Other,
- fix typo in `LevelMarker` docstring: "graphich" -> "graphic"
- reflow docstring in `qgo_draw_markers()` to 67 char limit

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-02-24 15:50:49 -05:00
Gud Boi b1cf102cc5 Add info log for shm processing in `ldshm` CLI cmd
Log shm file name and detected period before null segment
processing to aid debugging.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-02-24 15:50:49 -05:00
Gud Boi 8591f1b4a2 Bump to latest official `pyqtgraph` release 2026-02-24 15:50:49 -05:00
Gud Boi b1cb67d1bd Port `.ib` backend from `ib_insync` to `ib_async`
Migrate the IB broker backend to use `ib_async` (the actively maintained
fork) instead of the now stale, original `ib_insync` lib.

Deats,
- update `pyproject.toml` dep: drop `ib-insync` pin, add
  `ib-async>=2.1.0`.
- update lock file with `ib-async` and its new `aeventkit` dep (which
  i guess replaces `eventkit`).
- obvi, change all `ib_insync` imports to `ib_async` across `.ib.*`.
- update docs and select internal comments referencing the original lib.

Also,
- drop unused `ledger_dict` init in `_flex_reports.load_flex_trades()`.
- fix union type annot style: `dict | None` -> `dict|None`.
- strip `.tzinfo` from `lastTimeStamp` in `normalize()` to avoid
  IPC codec issues with `ib_async`'s `timezone.utc` injection.
- pop `'defaults'` from ticker data dict in `normalize()` to avoid
  non-serializable `timezone` objects and warning-log in such
  cases.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-02-24 15:13:28 -05:00
Gud Boi 196422433c Capture `cons` in `Client.get_fute()`
That is to be able to (eventually) introspect "ambiguous" contract sets
once we move to `ib_async` and its `returnAll: bool` now offered by
`IB.qualifyContractsAsync()`,

https://github.com/ib-api-reloaded/ib_async/blob/main/ib_async/ib.py#L2115

Also, tweak some type type annots to multline style in sibling mods.
2026-02-24 12:31:43 -05:00
15 changed files with 115 additions and 1340 deletions

View File

@ -2,7 +2,7 @@
-------------- --------------
more or less the "everything broker" for traditional and international more or less the "everything broker" for traditional and international
markets. they are the "go to" provider for automatic retail trading markets. they are the "go to" provider for automatic retail trading
and we interface to their APIs using the `ib_insync` project. and we interface to their APIs using the `ib_async` project.
status status
****** ******

View File

@ -22,7 +22,7 @@ Sub-modules within break into the core functionalities:
- ``broker.py`` part for orders / trading endpoints - ``broker.py`` part for orders / trading endpoints
- ``feed.py`` for real-time data feed endpoints - ``feed.py`` for real-time data feed endpoints
- ``api.py`` for the core API machinery which is ``trio``-ized - ``api.py`` for the core API machinery which is ``trio``-ized
wrapping around ``ib_insync``. wrapping around `ib_async`.
""" """
from .api import ( from .api import (

View File

@ -111,7 +111,7 @@ def load_flex_trades(
) -> dict[str, Any]: ) -> dict[str, Any]:
from ib_insync import flexreport, util from ib_async import flexreport, util
conf = get_config() conf = get_config()
@ -154,8 +154,7 @@ def load_flex_trades(
trade_entries, trade_entries,
) )
ledger_dict: dict | None = None ledger_dict: dict|None
for acctid in trades_by_account: for acctid in trades_by_account:
trades_by_id = trades_by_account[acctid] trades_by_id = trades_by_account[acctid]

View File

@ -15,7 +15,8 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
''' '''
Core API client machinery; mostly sane/useful wrapping around `ib_insync`.. Core API client machinery; mostly sane/useful wrapping around
`ib_async`..
''' '''
from __future__ import annotations from __future__ import annotations
@ -57,7 +58,7 @@ from pendulum import (
Interval, Interval,
) )
from eventkit import Event from eventkit import Event
from ib_insync import ( from ib_async import (
client as ib_client, client as ib_client,
IB, IB,
Contract, Contract,
@ -143,7 +144,7 @@ _bar_sizes = {
_show_wap_in_history: bool = False _show_wap_in_history: bool = False
# overrides to sidestep pretty questionable design decisions in # overrides to sidestep pretty questionable design decisions in
# ``ib_insync``: # ``ib_async``:
class NonShittyWrapper(Wrapper): class NonShittyWrapper(Wrapper):
def tcpDataArrived(self): def tcpDataArrived(self):
"""Override time stamps to be floats for now. """Override time stamps to be floats for now.
@ -183,7 +184,7 @@ class NonShittyIB(IB):
''' '''
def __init__(self): def __init__(self):
# override `ib_insync` internal loggers so we can see wtf # override `ib_async` internal loggers so we can see wtf
# it's doing.. # it's doing..
self._logger = get_logger( self._logger = get_logger(
name=__name__, name=__name__,
@ -194,7 +195,7 @@ class NonShittyIB(IB):
self.wrapper = NonShittyWrapper(self) self.wrapper = NonShittyWrapper(self)
self.client = ib_client.Client(self.wrapper) self.client = ib_client.Client(self.wrapper)
self.client._logger = get_logger( self.client._logger = get_logger(
name='ib_insync.client', name='ib_async.client',
) )
# self.errorEvent += self._onError # self.errorEvent += self._onError
@ -779,13 +780,14 @@ class Client:
ContFuture(symbol, exchange=exchange) ContFuture(symbol, exchange=exchange)
))[0] ))[0]
else: else:
con = (await self.ib.qualifyContractsAsync( cons = (await self.ib.qualifyContractsAsync(
Future( Future(
symbol, symbol,
exchange=exchange, exchange=exchange,
lastTradeDateOrContractMonth=expiry, lastTradeDateOrContractMonth=expiry,
) )
))[0] ))
con = cons[0]
return con return con
@ -878,7 +880,7 @@ class Client:
currency='USD', currency='USD',
exchange='PAXOS', exchange='PAXOS',
) )
# XXX, on `ib_insync` when first tried this, # XXX, on `ib_async` when first tried this,
# > Error 10299, reqId 141: Expected what to show is # > Error 10299, reqId 141: Expected what to show is
# > AGGTRADES, please use that instead of TRADES., # > AGGTRADES, please use that instead of TRADES.,
# > contract: Crypto(conId=479624278, symbol='BTC', # > contract: Crypto(conId=479624278, symbol='BTC',
@ -992,7 +994,6 @@ class Client:
async def get_sym_details( async def get_sym_details(
self, self,
fqme: str, fqme: str,
) -> tuple[ ) -> tuple[
Contract, Contract,
ContractDetails, ContractDetails,
@ -1092,7 +1093,7 @@ class Client:
size: int, size: int,
account: str, # if blank the "default" tws account is used account: str, # if blank the "default" tws account is used
# XXX: by default 0 tells ``ib_insync`` methods that there is no # XXX: by default 0 tells ``ib_async`` methods that there is no
# existing order so ask the client to create a new one (which it # existing order so ask the client to create a new one (which it
# seems to do by allocating an int counter - collision prone..) # seems to do by allocating an int counter - collision prone..)
reqid: int = None, reqid: int = None,
@ -1281,7 +1282,7 @@ async def load_aio_clients(
port: int = None, port: int = None,
client_id: int = 6116, client_id: int = 6116,
# the API TCP in `ib_insync` connection can be flaky af so instead # the API TCP in `ib_async` connection can be flaky af so instead
# retry a few times to get the client going.. # retry a few times to get the client going..
connect_retries: int = 3, connect_retries: int = 3,
connect_timeout: float = 30, # in case a remote-host connect_timeout: float = 30, # in case a remote-host
@ -1289,7 +1290,7 @@ async def load_aio_clients(
) -> dict[str, Client]: ) -> dict[str, Client]:
''' '''
Return an ``ib_insync.IB`` instance wrapped in our client API. Return an ``ib_async.IB`` instance wrapped in our client API.
Client instances are cached for later use. Client instances are cached for later use.
@ -1631,6 +1632,7 @@ async def open_aio_client_method_relay(
) -> None: ) -> None:
# with tractor.devx.maybe_open_crash_handler() as _bxerr:
# sync with `open_client_proxy()` caller # sync with `open_client_proxy()` caller
chan.started_nowait(client) chan.started_nowait(client)
@ -1640,7 +1642,11 @@ async def open_aio_client_method_relay(
# relay all method requests to ``asyncio``-side client and deliver # relay all method requests to ``asyncio``-side client and deliver
# back results # back results
while not chan._to_trio._closed: # <- TODO, better check like `._web_bs`? while not chan._to_trio._closed: # <- TODO, better check like `._web_bs`?
msg: tuple[str, dict]|dict|None = await chan.get() msg: (
None
|tuple[str, dict]
|dict
) = await chan.get()
match msg: match msg:
case None: # termination sentinel case None: # termination sentinel
log.info('asyncio `Client` method-proxy SHUTDOWN!') log.info('asyncio `Client` method-proxy SHUTDOWN!')
@ -1742,7 +1748,7 @@ async def get_client(
) -> Client: ) -> Client:
''' '''
Init the ``ib_insync`` client in another actor and return Init the ``ib_async`` client in another actor and return
a method proxy to it. a method proxy to it.
''' '''

View File

@ -35,14 +35,14 @@ from trio_typing import TaskStatus
import tractor import tractor
from tractor.to_asyncio import LinkedTaskChannel from tractor.to_asyncio import LinkedTaskChannel
from tractor import trionics from tractor import trionics
from ib_insync.contract import ( from ib_async.contract import (
Contract, Contract,
) )
from ib_insync.order import ( from ib_async.order import (
Trade, Trade,
OrderStatus, OrderStatus,
) )
from ib_insync.objects import ( from ib_async.objects import (
Fill, Fill,
Execution, Execution,
CommissionReport, CommissionReport,
@ -181,7 +181,7 @@ async def handle_order_requests(
# validate # validate
order = BrokerdOrder(**request_msg) order = BrokerdOrder(**request_msg)
# XXX: by default 0 tells ``ib_insync`` methods that # XXX: by default 0 tells ``ib_async`` methods that
# there is no existing order so ask the client to create # there is no existing order so ask the client to create
# a new one (which it seems to do by allocating an int # a new one (which it seems to do by allocating an int
# counter - collision prone..) # counter - collision prone..)
@ -237,7 +237,7 @@ async def recv_trade_updates(
) -> None: ) -> None:
''' '''
Receive and relay order control and positioning related events Receive and relay order control and positioning related events
from `ib_insync`, pack as tuples and push over mem-chan to our from `ib_async`, pack as tuples and push over mem-chan to our
trio relay task for processing and relay to EMS. trio relay task for processing and relay to EMS.
''' '''
@ -303,7 +303,7 @@ async def recv_trade_updates(
# much more then a few more pnl fields.. # much more then a few more pnl fields..
# 'updatePortfolioEvent', # 'updatePortfolioEvent',
# XXX: these all seem to be weird ib_insync internal # XXX: these all seem to be weird ib_async internal
# events that we probably don't care that much about # events that we probably don't care that much about
# given the internal design is wonky af.. # given the internal design is wonky af..
# 'newOrderEvent', # 'newOrderEvent',
@ -499,7 +499,7 @@ async def open_trade_event_stream(
] = trio.TASK_STATUS_IGNORED, ] = trio.TASK_STATUS_IGNORED,
): ):
''' '''
Proxy wrapper for starting trade event stream from ib_insync Proxy wrapper for starting trade event stream from ib_async
which spawns an asyncio task that registers an internal closure which spawns an asyncio task that registers an internal closure
(`push_tradies()`) which in turn relays trading events through (`push_tradies()`) which in turn relays trading events through
a `tractor.to_asyncio.LinkedTaskChannel` which the parent a `tractor.to_asyncio.LinkedTaskChannel` which the parent

View File

@ -36,7 +36,7 @@ from typing import (
) )
from async_generator import aclosing from async_generator import aclosing
import ib_insync as ibis import ib_async as ibis
import numpy as np import numpy as np
from pendulum import ( from pendulum import (
now, now,
@ -100,7 +100,7 @@ tick_types = {
5: 'size', 5: 'size',
8: 'volume', 8: 'volume',
# ``ib_insync`` already packs these into # `ib_async` already packs these into
# quotes under the following fields. # quotes under the following fields.
55: 'trades_per_min', # `'tradeRate'` 55: 'trades_per_min', # `'tradeRate'`
56: 'vlm_per_min', # `'volumeRate'` 56: 'vlm_per_min', # `'volumeRate'`
@ -304,7 +304,7 @@ async def open_history_client(
# TODO: it seems like we can do async queries for ohlc # TODO: it seems like we can do async queries for ohlc
# but getting the order right still isn't working and I'm not # but getting the order right still isn't working and I'm not
# quite sure why.. needs some tinkering and probably # quite sure why.. needs some tinkering and probably
# a lookthrough of the `ib_insync` machinery, for eg. maybe # a lookthrough of the `ib_async` machinery, for eg. maybe
# we have to do the batch queries on the `asyncio` side? # we have to do the batch queries on the `asyncio` side?
yield ( yield (
get_hist, get_hist,
@ -1051,6 +1051,21 @@ def normalize(
# ticker.rtTime.timestamp) / 1000. # ticker.rtTime.timestamp) / 1000.
data.pop('rtTime') data.pop('rtTime')
# XXX, `ib_async` seems to set a
# `'timezone': datetime.timezone.utc` in this `dict`
# which is NOT IPC serializeable sin codec!
#
# pretty sure we don't need any of this field for now anyway?
data.pop('defaults')
if lts := data.get('lastTimeStamp'):
lts.replace(tzinfo=None)
log.warning(
f'Stripping `.tzinfo` from datetime\n'
f'{lts}\n'
)
# breakpoint()
return data return data
@ -1227,7 +1242,7 @@ async def stream_quotes(
): ):
# ?TODO? can we rm this - particularly for `ib_async`? # ?TODO? can we rm this - particularly for `ib_async`?
# ugh, clear ticks since we've consumed them # ugh, clear ticks since we've consumed them
# (ahem, ib_insync is stateful trash) # (ahem, ib_async is stateful trash)
# first_ticker.ticks = [] # first_ticker.ticks = []
# only on first entry at feed boot up # only on first entry at feed boot up

View File

@ -36,7 +36,7 @@ from pendulum import (
parse, parse,
from_timestamp, from_timestamp,
) )
from ib_insync import ( from ib_async import (
Contract, Contract,
Commodity, Commodity,
Fill, Fill,

View File

@ -30,7 +30,7 @@ from typing import (
) )
from rapidfuzz import process as fuzzy from rapidfuzz import process as fuzzy
import ib_insync as ibis import ib_async as ibis
import tractor import tractor
import trio import trio
@ -522,7 +522,11 @@ async def get_mkt_info(
if atype == 'commodity': if atype == 'commodity':
venue: str = 'cmdty' venue: str = 'cmdty'
else: else:
venue = con.primaryExchange or con.exchange venue: str = (
con.primaryExchange
or
con.exchange
)
price_tick: Decimal = Decimal(str(details.minTick)) price_tick: Decimal = Decimal(str(details.minTick))
ib_min_tick_gt_2: Decimal = Decimal('0.01') ib_min_tick_gt_2: Decimal = Decimal('0.01')

View File

@ -41,7 +41,7 @@ from pendulum import (
) )
if TYPE_CHECKING: if TYPE_CHECKING:
from ib_insync import ( from ib_async import (
TradingSession, TradingSession,
ContractDetails, ContractDetails,
) )
@ -236,7 +236,7 @@ def is_venue_closure(
# #
# NOTE, this was generated by @guille from a gpt5 prompt # NOTE, this was generated by @guille from a gpt5 prompt
# and was originally thot to be needed before learning about # and was originally thot to be needed before learning about
# `ib_insync.contract.ContractDetails._parseSessions()` and # `ib_async.contract.ContractDetails._parseSessions()` and
# it's downstream meths.. # it's downstream meths..
# #
# This is still likely useful to keep for now to parse the # This is still likely useful to keep for now to parse the

View File

@ -973,9 +973,6 @@ async def open_feed(
# assert flume.mkt.fqme == fqme # assert flume.mkt.fqme == fqme
feed.flumes[fqme] = flume feed.flumes[fqme] = flume
# TODO: do we need this?
flume.feed = feed
# attach and cache shm handles # attach and cache shm handles
rt_shm = flume.rt_shm rt_shm = flume.rt_shm
assert rt_shm assert rt_shm

View File

@ -22,9 +22,6 @@ real-time data processing data-structures.
""" """
from __future__ import annotations from __future__ import annotations
from typing import (
TYPE_CHECKING,
)
import tractor import tractor
import pendulum import pendulum
@ -38,9 +35,6 @@ from ._sharedmem import (
) )
from piker.accounting import MktPair from piker.accounting import MktPair
if TYPE_CHECKING:
from piker.data.feed import Feed
class Flume(Struct): class Flume(Struct):
''' '''
@ -80,10 +74,6 @@ class Flume(Struct):
izero_rt: int = 0 izero_rt: int = 0
throttle_rate: int | None = None throttle_rate: int | None = None
# TODO: do we need this really if we can pull the `Portal` from
# ``tractor``'s internals?
feed: Feed|None = None
@property @property
def rt_shm(self) -> ShmArray: def rt_shm(self) -> ShmArray:
@ -156,7 +146,6 @@ class Flume(Struct):
# will get instead some kind of msg-compat version # will get instead some kind of msg-compat version
# that it can load. # that it can load.
msg.pop('stream') msg.pop('stream')
msg.pop('feed')
msg.pop('_rt_shm') msg.pop('_rt_shm')
msg.pop('_hist_shm') msg.pop('_hist_shm')

View File

@ -276,14 +276,41 @@ def get_null_segs(
absi_zdiff: np.ndarray = np.diff(absi_zeros) absi_zdiff: np.ndarray = np.diff(absi_zeros)
if zero_t.size < 2: if zero_t.size < 2:
try: idx: int = zero_t['index'][0]
breakpoint() idx_before: int = idx - 1
except RuntimeError: idx_after: int = idx + 1
# XXX, if greenback not active from index = frame['index']
# piker store ldshm cmd.. before_cond = idx_before <= index
log.exception( after_cond = index <= idx_after
"Can't debug single-sample null!\n" bars: np.ndarray = frame[
) before_cond
&
after_cond
]
time: np.ndarray = bars['time']
from pendulum import (
from_timestamp,
Interval,
)
gap: Interval = (
from_timestamp(time[-1])
-
from_timestamp(time[0])
)
log.warning(
f'Single OHLCV-bar null-segment detected??\n'
f'gap -> {gap}\n'
)
# ^^XXX, if you want to debug the above bar-gap^^
# try:
# breakpoint()
# except RuntimeError:
# # XXX, if greenback not active from
# # piker store ldshm cmd..
# log.exception(
# "Can't debug single-sample null!\n"
# )
return None return None

1263
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -52,7 +52,6 @@ dependencies = [
"bidict >=0.23.1", "bidict >=0.23.1",
"colorama >=0.4.6, <0.5.0", "colorama >=0.4.6, <0.5.0",
"colorlog >=6.7.0, <7.0.0", "colorlog >=6.7.0, <7.0.0",
"ib-insync >=0.9.86, <0.10.0",
"numpy>=2.0", "numpy>=2.0",
"polars >=0.20.6", "polars >=0.20.6",
"polars-fuzzy-match>=0.1.5", "polars-fuzzy-match>=0.1.5",
@ -76,6 +75,7 @@ dependencies = [
"numba>=0.61.0", "numba>=0.61.0",
"pyvnc", "pyvnc",
"exchange-calendars>=4.13.1", "exchange-calendars>=4.13.1",
"ib-async>=2.1.0",
] ]
# ------ dependencies ------ # ------ dependencies ------
# NOTE, by default we ship only a "headless" deps set bc # NOTE, by default we ship only a "headless" deps set bc

39
uv.lock
View File

@ -10,6 +10,18 @@ resolution-markers = [
"python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
] ]
[[package]]
name = "aeventkit"
version = "2.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5c/8c/c08db1a1910f8d04ec6a524de522edd0bac181bdf94dbb01183f7685cd77/aeventkit-2.1.0.tar.gz", hash = "sha256:4e7d81bb0a67227121da50a23e19e5bbf13eded541a9f4857eeb6b7b857b738a", size = 24703, upload-time = "2025-06-22T15:54:03.961Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8d/8c/2a4b912b1afa201b25bdd0f5bccf96d5a8b5dccb6131316a8dd2d9cabcc1/aeventkit-2.1.0-py3-none-any.whl", hash = "sha256:962d43f79e731ac43527f2d0defeed118e6dbaa85f1487f5667540ebb8f00729", size = 26678, upload-time = "2025-06-22T15:54:02.141Z" },
]
[[package]] [[package]]
name = "aiodns" name = "aiodns"
version = "3.6.0" version = "3.6.0"
@ -396,18 +408,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/56/01/6f77d042b83260ef9ed73ea9647dfa0ef8414eba0a3fc57a509a088ad39b/elasticsearch-8.19.2-py3-none-any.whl", hash = "sha256:c16ba20c4c76cf6952e836dae7f4e724e00ba7bf31b94b79472b873683accdd4", size = 949706, upload-time = "2025-10-28T16:36:41.003Z" }, { url = "https://files.pythonhosted.org/packages/56/01/6f77d042b83260ef9ed73ea9647dfa0ef8414eba0a3fc57a509a088ad39b/elasticsearch-8.19.2-py3-none-any.whl", hash = "sha256:c16ba20c4c76cf6952e836dae7f4e724e00ba7bf31b94b79472b873683accdd4", size = 949706, upload-time = "2025-10-28T16:36:41.003Z" },
] ]
[[package]]
name = "eventkit"
version = "1.0.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/16/1e/0fac4e45d71ace143a2673ec642701c3cd16f833a0e77a57fa6a40472696/eventkit-1.0.3.tar.gz", hash = "sha256:99497f6f3c638a50ff7616f2f8cd887b18bbff3765dc1bd8681554db1467c933", size = 28320, upload-time = "2023-12-11T11:41:35.339Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/93/d9/7497d650b69b420e1a913329a843e16c715dac883750679240ef00a921e2/eventkit-1.0.3-py3-none-any.whl", hash = "sha256:0e199527a89aff9d195b9671ad45d2cc9f79ecda0900de8ecfb4c864d67ad6a2", size = 31837, upload-time = "2023-12-11T11:41:33.358Z" },
]
[[package]] [[package]]
name = "exceptiongroup" name = "exceptiongroup"
version = "1.3.1" version = "1.3.1"
@ -630,16 +630,17 @@ wheels = [
] ]
[[package]] [[package]]
name = "ib-insync" name = "ib-async"
version = "0.9.86" version = "2.1.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "eventkit" }, { name = "aeventkit" },
{ name = "nest-asyncio" }, { name = "nest-asyncio" },
{ name = "tzdata" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/55/bb/733d5c81c8c2f54e90898afc7ff3a99f4d53619e6917c848833f9cc1ab56/ib_insync-0.9.86.tar.gz", hash = "sha256:73af602ca2463f260999970c5bd937b1c4325e383686eff301743a4de08d381e", size = 69859, upload-time = "2023-07-02T12:43:31.968Z" } sdist = { url = "https://files.pythonhosted.org/packages/30/4d/dfc1da8224c3ffcdcd668da7283c4e5f14239a07f83ea66af99700296fc3/ib_async-2.1.0.tar.gz", hash = "sha256:6a03a87d6c06acacb0217a5bea60a8a168ecd5b5a7e86e1c73678d5b48cbc796", size = 87678, upload-time = "2025-12-08T01:42:32.004Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/8f/f3/28ea87be30570f4d6b8fd24380d12fa74e59467ee003755e76aeb29082b8/ib_insync-0.9.86-py3-none-any.whl", hash = "sha256:a61fbe56ff405d93d211dad8238d7300de76dd6399eafc04c320470edec9a4a4", size = 72980, upload-time = "2023-07-02T12:43:29.928Z" }, { url = "https://files.pythonhosted.org/packages/80/e7/8f33801788c66f15e9250957ff7f53a8000843f79af1a3ed7a96def0e96b/ib_async-2.1.0-py3-none-any.whl", hash = "sha256:f6d8b991bdbd6dd38e700c61b3dced06ebe0f14be4e5263e2ef10ba10b88d434", size = 88876, upload-time = "2025-12-08T01:42:30.883Z" },
] ]
[[package]] [[package]]
@ -1107,7 +1108,7 @@ dependencies = [
{ name = "cryptofeed" }, { name = "cryptofeed" },
{ name = "exchange-calendars" }, { name = "exchange-calendars" },
{ name = "httpx" }, { name = "httpx" },
{ name = "ib-insync" }, { name = "ib-async" },
{ name = "msgspec" }, { name = "msgspec" },
{ name = "numba" }, { name = "numba" },
{ name = "numpy" }, { name = "numpy" },
@ -1183,7 +1184,7 @@ requires-dist = [
{ name = "cryptofeed", specifier = ">=2.4.0,<3.0.0" }, { name = "cryptofeed", specifier = ">=2.4.0,<3.0.0" },
{ name = "exchange-calendars", specifier = ">=4.13.1" }, { name = "exchange-calendars", specifier = ">=4.13.1" },
{ name = "httpx", specifier = ">=0.27.0,<0.28.0" }, { name = "httpx", specifier = ">=0.27.0,<0.28.0" },
{ name = "ib-insync", specifier = ">=0.9.86,<0.10.0" }, { name = "ib-async", specifier = ">=2.1.0" },
{ name = "msgspec", specifier = ">=0.19.0,<0.20" }, { name = "msgspec", specifier = ">=0.19.0,<0.20" },
{ name = "numba", specifier = ">=0.61.0" }, { name = "numba", specifier = ">=0.61.0" },
{ name = "numpy", specifier = ">=2.0" }, { name = "numpy", specifier = ">=2.0" },