diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f02b17f --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +__pycache__/ +*.py[cod] +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ +.venv/ +dist/ +build/ +*.egg-info/ diff --git a/AGENTS.md b/AGENTS.md index c2f7b15..a7df072 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,8 +19,10 @@ Keep these areas loosely separated in code and documentation. Avoid coupling the - Use Python for implementation. - Use `mise` to control the Python version. +- Use `uv` to manage Python packages and virtual environments. - The local Python version is pinned in `.mise.toml`. - Prefer commands run through `mise exec -- ...` when the Python environment matters. +- Prefer `mise exec -- uv run ...` for project Python commands once dependencies are synced. - Git is the version control system for this project. A remote will be added later. ## Safety And Trading Constraints diff --git a/README.md b/README.md index 58dd6db..ec5cb6d 100644 --- a/README.md +++ b/README.md @@ -15,15 +15,24 @@ This repository is at the planning and scaffolding stage. The machine learning m ## Environment -Use `mise` to manage Python. +Use `mise` to manage Python and `uv` to manage Python packages. ```sh mise install -mise exec -- python --version +mise exec -- uv sync +mise exec -- uv run python --version ``` The current local Python version is pinned in `.mise.toml`. +## Dependencies + +Runtime dependencies are declared in `pyproject.toml`. + +- `ib-insync` for the IBKR API connection. +- `pandas` for tabular candle data handling. +- `pyarrow` for Parquet file support. + ## Trading Safety The default target is paper trading. Real trading should only be added later with explicit configuration, clear documentation, and tests around order generation and broker integration. @@ -33,3 +42,7 @@ Do not commit secrets such as IBKR credentials, account identifiers, API tokens, ## Documentation See [docs/README.md](docs/README.md) for the initial architecture notes and decision log. + +The first data collection design note is [docs/data-fetcher.md](docs/data-fetcher.md). + +Manual test instructions for the IBKR fetcher skeleton are in [docs/manual-test/README.md](docs/manual-test/README.md). diff --git a/docs/README.md b/docs/README.md index 904eefc..c9b85ff 100644 --- a/docs/README.md +++ b/docs/README.md @@ -10,6 +10,10 @@ Build a Python-based trading bot that uses machine learning to determine which a Responsible for acquiring and storing market, asset, and any future feature data needed for model training and evaluation. +The first planned tool is an IBKR daily candle fetcher. It should fetch open, high, low, close, and volume data for a ticker and date range, then eventually persist that data to a ticker-named Parquet file. See [data-fetcher.md](data-fetcher.md). + +Parquet files are partitioned by ticker, not by date. + Open decisions: - asset universe; @@ -72,7 +76,7 @@ Possible scope: ## Near-Term Priorities 1. Decide the initial project package structure. -2. Add Python packaging and dependency management. +2. Keep Python packaging and dependency management current with `uv`. 3. Add a minimal configuration system. 4. Define interfaces for data collection, model artifacts, and broker execution. 5. Add tests for the core trading decision boundaries before connecting real broker behavior. diff --git a/docs/data-fetcher.md b/docs/data-fetcher.md new file mode 100644 index 0000000..d864696 --- /dev/null +++ b/docs/data-fetcher.md @@ -0,0 +1,64 @@ +# Data Fetcher Design + +## Initial Goal + +Create a Python module/tool that fetches daily candlestick data from the IBKR API for a specific ticker and date range. + +The intended example workflow is: + +- ticker: `SPY`; +- date range: `2026-06-01` to `2026-06-30`; +- bar size: one trading day; +- fields: open, high, low, close, volume; +- output: one Parquet file named for the ticker, such as `SPY.parquet`. + +## Current Skeleton + +The first implementation is intentionally small: + +- hard-coded ticker: `SPY`; +- hard-coded range: `2026-06-01` to `2026-06-05`; +- hard-coded IBKR Gateway target: `127.0.0.1:4002`; +- uses the IBKR API through `ib_insync`; +- prints fetched candles as CSV-like rows; +- does not write Parquet yet; +- does not expose CLI arguments yet; +- does not define storage paths yet. + +Run it with: + +```sh +mise exec -- uv run python src/trading_bot/data/fetch_ibkr_daily.py +``` + +This expects a local IBKR Gateway session to be running and accepting API connections on `127.0.0.1:4002`. + +Manual test instructions are in [manual-test/README.md](manual-test/README.md). + +## Intended Future Behavior + +Later, this tool should accept a ticker and date range, fetch daily candles from IBKR, normalize the schema, and write the result to a ticker-named Parquet file. + +Decided storage behavior: + +- Parquet files are partitioned by ticker, not by date. +- Each ticker should have its own Parquet file, such as `SPY.parquet`. + +Candidate output schema: + +| Column | Type | Description | +| --- | --- | --- | +| `date` | date | Trading session date | +| `ticker` | string | Asset ticker | +| `open` | float | Daily open price | +| `high` | float | Daily high price | +| `low` | float | Daily low price | +| `close` | float | Daily close price | +| `volume` | integer | Daily traded volume | + +Open decisions: + +- where raw and normalized data files should live; +- how to handle adjusted versus unadjusted prices; +- how to handle missing sessions and IBKR pacing limits; +- whether to use `ib_insync` long term or a lower-level IBKR client wrapper. diff --git a/docs/manual-test/README.md b/docs/manual-test/README.md new file mode 100644 index 0000000..da172be --- /dev/null +++ b/docs/manual-test/README.md @@ -0,0 +1,64 @@ +# Manual Test Instructions + +## IBKR Daily Fetcher Skeleton + +This test checks the first hard-coded IBKR data fetcher without writing any data files. + +The fetcher currently requests: + +- gateway: `127.0.0.1:4002`; +- client id: `101`; +- ticker: `SPY`; +- date range: `2026-06-01` to `2026-06-05`; +- bar size: `1 day`; +- data type: `TRADES`; +- regular trading hours only; +- output: printed CSV-like rows. + +## Prerequisites + +1. Start IBKR Gateway. +2. Log in to the paper trading account. +3. Confirm API access is enabled in IBKR Gateway. +4. Confirm the API socket port is `4002`. +5. Confirm no other API client is already using client id `101`. +6. Sync Python dependencies: + +```sh +mise exec -- uv sync +``` + +## Run The Test + +From the repository root, run: + +```sh +mise exec -- uv run python src/trading_bot/data/fetch_ibkr_daily.py +``` + +## Expected Output + +The tool should first print the hard-coded request range and connection target: + +```text +Fetching SPY daily candles from 2026-06-01 to 2026-06-05 +Connecting to IBKR Gateway at 127.0.0.1:4002 with client id 101 +``` + +If the request succeeds, it should then print a header and one row per returned trading day: + +```text +date,ticker,open,high,low,close,volume +2026-06-01,SPY,... +``` + +Exact prices and volume depend on what IBKR returns. + +## Common Issues + +- Connection refused: IBKR Gateway is not running, the port is not `4002`, or API access is disabled. +- Client id already in use: change `IBKR_CLIENT_ID` in the fetcher or disconnect the other client. +- No historical bars: confirm the account has market data permissions and that IBKR accepts the requested historical data range. +- Pacing or permission errors: note the IBKR error message before changing the request. + +This skeleton does not write Parquet files yet. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..8987415 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,21 @@ +[project] +name = "trading-bot" +version = "0.1.0" +description = "Python trading bot using machine learning and the IBKR API." +readme = "README.md" +requires-python = ">=3.11,<3.12" +dependencies = [ + "ib-insync>=0.9.86", + "pandas>=2.3.0", + "pyarrow>=20.0.0", +] + +[dependency-groups] +dev = [] + +[tool.uv] +package = true + +[build-system] +requires = ["uv_build>=0.8.0,<0.9.0"] +build-backend = "uv_build" diff --git a/src/trading_bot/__init__.py b/src/trading_bot/__init__.py new file mode 100644 index 0000000..382f8ad --- /dev/null +++ b/src/trading_bot/__init__.py @@ -0,0 +1 @@ +"""Trading bot package.""" diff --git a/src/trading_bot/data/__init__.py b/src/trading_bot/data/__init__.py new file mode 100644 index 0000000..9aa41c0 --- /dev/null +++ b/src/trading_bot/data/__init__.py @@ -0,0 +1 @@ +"""Training data collection tools.""" diff --git a/src/trading_bot/data/fetch_ibkr_daily.py b/src/trading_bot/data/fetch_ibkr_daily.py new file mode 100644 index 0000000..7afed04 --- /dev/null +++ b/src/trading_bot/data/fetch_ibkr_daily.py @@ -0,0 +1,120 @@ +"""Skeleton IBKR daily candle fetcher. + +This intentionally uses hard-coded values while the project shape is still +being designed. Parquet persistence and configurable date ranges come later. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import date + + +TICKER = "SPY" +START_DATE = date(2026, 6, 1) +END_DATE = date(2026, 6, 5) + +IBKR_HOST = "127.0.0.1" +IBKR_PORT = 4002 +IBKR_CLIENT_ID = 101 +IBKR_CONNECT_TIMEOUT_SECONDS = 10 + + +@dataclass(frozen=True) +class DailyCandle: + """Daily OHLCV market data for one trading session.""" + + trading_day: date + open: float + high: float + low: float + close: float + volume: int + + +def fetch_hard_coded_daily_candles() -> list[DailyCandle]: + """Fetch one hard-coded week of SPY daily candles from IBKR.""" + + try: + from ib_insync import IB, Stock # type: ignore[import-not-found] + except ImportError as exc: + raise SystemExit( + "Missing dependency: ib_insync. Install it before running the " + "IBKR fetcher skeleton." + ) from exc + + ib = IB() + try: + print( + f"Connecting to IBKR Gateway at {IBKR_HOST}:{IBKR_PORT} " + f"with client id {IBKR_CLIENT_ID}" + ) + ib.connect( + IBKR_HOST, + IBKR_PORT, + clientId=IBKR_CLIENT_ID, + timeout=IBKR_CONNECT_TIMEOUT_SECONDS, + ) + + contract = Stock(TICKER, "SMART", "USD") + ib.qualifyContracts(contract) + bars = ib.reqHistoricalData( + contract, + endDateTime="20260605 23:59:59 US/Eastern", + durationStr="1 W", + barSizeSetting="1 day", + whatToShow="TRADES", + useRTH=True, + formatDate=1, + ) + + if not bars: + print("IBKR returned no historical bars for the hard-coded request.") + + candles: list[DailyCandle] = [] + for bar in bars: + trading_day = bar.date if isinstance(bar.date, date) else bar.date.date() + if START_DATE <= trading_day <= END_DATE: + candles.append( + DailyCandle( + trading_day=trading_day, + open=float(bar.open), + high=float(bar.high), + low=float(bar.low), + close=float(bar.close), + volume=int(bar.volume), + ) + ) + + return candles + finally: + if ib.isConnected(): + ib.disconnect() + + +def print_candles(candles: list[DailyCandle]) -> None: + """Print candles in a compact table.""" + + print("date,ticker,open,high,low,close,volume") + for candle in candles: + print( + f"{candle.trading_day.isoformat()}," + f"{TICKER}," + f"{candle.open:.2f}," + f"{candle.high:.2f}," + f"{candle.low:.2f}," + f"{candle.close:.2f}," + f"{candle.volume}" + ) + + +def main() -> None: + """Run the hard-coded fetcher skeleton.""" + + print(f"Fetching {TICKER} daily candles from {START_DATE} to {END_DATE}") + candles = fetch_hard_coded_daily_candles() + print_candles(candles) + + +if __name__ == "__main__": + main() diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..1fd8794 --- /dev/null +++ b/uv.lock @@ -0,0 +1,154 @@ +version = 1 +revision = 3 +requires-python = "==3.11.*" +resolution-markers = [ + "sys_platform == 'win32'", + "sys_platform == 'emscripten'", + "sys_platform != 'emscripten' and sys_platform != 'win32'", +] + +[[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]] +name = "ib-insync" +version = "0.9.86" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "eventkit" }, + { name = "nest-asyncio" }, +] +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" } +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" }, +] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111, upload-time = "2026-05-18T23:33:17.795Z" }, + { url = "https://files.pythonhosted.org/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159, upload-time = "2026-05-18T23:33:20.654Z" }, + { url = "https://files.pythonhosted.org/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936, upload-time = "2026-05-18T23:33:22.987Z" }, + { url = "https://files.pythonhosted.org/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692, upload-time = "2026-05-18T23:33:26.62Z" }, + { url = "https://files.pythonhosted.org/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164, upload-time = "2026-05-18T23:33:29.955Z" }, + { url = "https://files.pythonhosted.org/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877, upload-time = "2026-05-18T23:33:34.724Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487, upload-time = "2026-05-18T23:33:38.217Z" }, + { url = "https://files.pythonhosted.org/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945, upload-time = "2026-05-18T23:33:41.331Z" }, + { url = "https://files.pythonhosted.org/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406, upload-time = "2026-05-18T23:33:44.131Z" }, + { url = "https://files.pythonhosted.org/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528, upload-time = "2026-05-18T23:33:50.725Z" }, + { url = "https://files.pythonhosted.org/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511, upload-time = "2026-05-18T23:36:50.673Z" }, + { url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064, upload-time = "2026-05-18T23:36:53.879Z" }, + { url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157, upload-time = "2026-05-18T23:36:57.194Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728, upload-time = "2026-05-18T23:36:59.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374, upload-time = "2026-05-18T23:37:02.674Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286, upload-time = "2026-05-18T23:37:06.327Z" }, + { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" }, +] + +[[package]] +name = "pandas" +version = "3.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/4f/5f3422a2afec5ffc46308b79e53291365a93748b498ac2e58bead0197916/pandas-3.0.5.tar.gz", hash = "sha256:dca3734d6ab7c906e6730f0788b0a1dbb9f2467731f9711f77995c8e9d62d712", size = 4658219, upload-time = "2026-07-22T22:19:28.819Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/ef/f1fd7431d635bf20015489bf0bd69c17fff1018de773540f651455a3916b/pandas-3.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2946e77e4a53cd248cbde631a12f0e51c8324ce354c3eba4d20147c1ad6f4282", size = 10397178, upload-time = "2026-07-22T22:17:48.274Z" }, + { url = "https://files.pythonhosted.org/packages/31/b4/0eafac990a431561187694126de01f9b12559549b4d86360c0c4bd870fde/pandas-3.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71ecc8fb7ed1a7aa4392316b5309a6347e8e7f832f38fd897846b3a1457a9298", size = 9990736, upload-time = "2026-07-22T22:17:52.388Z" }, + { url = "https://files.pythonhosted.org/packages/de/21/359880af3ea9b7cb23bea5b51e8e70ef3866c03be09da9a2787e18e330a8/pandas-3.0.5-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b173f5951ff6b8b0ec7675e20dff3c97b7e7a57dfcce387c2d7c5afe87cb7899", size = 10814438, upload-time = "2026-07-22T22:17:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/d1/50/d6cc4d7e508bbccf5d6027314a8312bc7ac73d0ec7f195f53838daafab40/pandas-3.0.5-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c0cf1dd9b55a22d105fc46c1b489af3bd42264fcba7c66297bf47a9a1d9c78a", size = 11323634, upload-time = "2026-07-22T22:17:56.858Z" }, + { url = "https://files.pythonhosted.org/packages/70/2b/d5f0a8c90dd0ae04e64ba53b871afb796ec026b615086d382ddc2ade729b/pandas-3.0.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0fac0010c75e4efb6b99e249c183a8993ce0dc95c240f9b120a5e67c727b7928", size = 11850860, upload-time = "2026-07-22T22:17:59.1Z" }, + { url = "https://files.pythonhosted.org/packages/5c/30/183aec2e19adf778a98d29b5729a0a68f4cc4ebf9b9c3b70d0297355bcb1/pandas-3.0.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:08d24fe11a17dc33bd6e937dc9c665f9cba08fbdc9f657f405713515febe300d", size = 12411100, upload-time = "2026-07-22T22:18:01.485Z" }, + { url = "https://files.pythonhosted.org/packages/fa/9a/31f4983f191af51ab2a8f2d0c7b33dff3a84da26533f982fff02c2f9e28b/pandas-3.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:b1261758dfb6cf12c3cff8300e21cefad30e7ec709abb4c24ac7318e6a52462a", size = 9968804, upload-time = "2026-07-22T22:18:03.903Z" }, + { url = "https://files.pythonhosted.org/packages/49/97/7886c89a39045c69ad82cbceaf3343810480c8ef49a216319ce8183860a6/pandas-3.0.5-cp311-cp311-win_arm64.whl", hash = "sha256:679f4e85b30ddb1515458ab1e788d3e260eae369b1f78da7a3aa4cac8ebf4a2a", size = 9205447, upload-time = "2026-07-22T22:18:06.134Z" }, +] + +[[package]] +name = "pyarrow" +version = "25.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/f3/95428098d1fa7d04432fb750eed06b41304c2f6a5d3319985e64db2d9d41/pyarrow-25.0.0.tar.gz", hash = "sha256:d2d697008b5ec06d75952ef260c2e9a8a0f6ccfce24266c04c9c8ade927cb3b4", size = 1199181, upload-time = "2026-07-10T08:29:50.116Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/98/ae2b5acf9876dbeffa6f320776242c52caab062df55c8ac5501ed2679e74/pyarrow-25.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:2e3b6544e26e393fe2cd530f523e36c1c8d3c345bbbb60cca3fd866be8322517", size = 35939080, upload-time = "2026-07-10T08:26:04.53Z" }, + { url = "https://files.pythonhosted.org/packages/80/09/3de2a968edbd496c86cb8b932cdbee2d4b08c4a28e9884a15e5c705a646b/pyarrow-25.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:b724d127783b4c19f088fcdfc844cbc318809246a30307bcabd5ed02045e890e", size = 37633420, upload-time = "2026-07-10T08:26:10.354Z" }, + { url = "https://files.pythonhosted.org/packages/19/86/8399243a4ce080426ec37db18d5e29148b7ec960a8a8c7f9059a7bf6ef0a/pyarrow-25.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:244f98a595f70fa4fd35faa7508c4ae67e14a173397a4b3b49d2b3c360fb0062", size = 46861050, upload-time = "2026-07-10T08:26:16.397Z" }, + { url = "https://files.pythonhosted.org/packages/7b/79/72d704b02bc5fc6d06954d76a0208c1e79cad3ab370f6d6a91ffe5078870/pyarrow-25.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:0222f0071d13313962a88d21bf28b80d355ac39d81bfa6ff3fe00eeaf748e4be", size = 50056458, upload-time = "2026-07-10T08:26:23.271Z" }, + { url = "https://files.pythonhosted.org/packages/06/5d/3c31a60b6403d63cad2e0f829096f5fc5763a129ead4207a5d4690b96448/pyarrow-25.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b58726f118c079f9d4ed7e904975d4f15fd69d0741ba511a4e2dcaa4ef16354f", size = 49957793, upload-time = "2026-07-10T08:26:30.232Z" }, + { url = "https://files.pythonhosted.org/packages/34/f7/8f8a019061f9863a831915329264372a87ed25eaf9109ce56eb0e84012c5/pyarrow-25.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:38a2c887cb3883e241b70201688db34133b6dfadd04f03c8f9213df53770c18e", size = 53100544, upload-time = "2026-07-10T08:26:36.414Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e2/738071e95c5ddad7b3dfc12f569ffa992db89d7d7b4a95258fd184191249/pyarrow-25.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:161649d60a7a46c613a19fd795763ea8a88c36ba997dd99d9bc66e6794ee36e8", size = 27848311, upload-time = "2026-07-10T08:26:41.429Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "trading-bot" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "ib-insync" }, + { name = "pandas" }, + { name = "pyarrow" }, +] + +[package.metadata] +requires-dist = [ + { name = "ib-insync", specifier = ">=0.9.86" }, + { name = "pandas", specifier = ">=2.3.0" }, + { name = "pyarrow", specifier = ">=20.0.0" }, +] + +[package.metadata.requires-dev] +dev = [] + +[[package]] +name = "tzdata" +version = "2026.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/92/ff/5a28bdfd8c3ebec42564ac7d0e54ca3db65044a9314a97f9564fa7a1e926/tzdata-2026.3.tar.gz", hash = "sha256:4a1518b8993086a7982523e071643f3c0e5f213e75b21318e78bcabfff9d1415", size = 198674, upload-time = "2026-07-10T08:50:37.887Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl", hash = "sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931", size = 348168, upload-time = "2026-07-10T08:50:36.46Z" }, +]