py-ib-controller 0.1.3__tar.gz → 0.1.5__tar.gz
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.
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/.github/workflows/ci.yml +5 -2
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/.github/workflows/publish.yml +1 -1
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/Makefile +15 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/PKG-INFO +141 -18
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/README.md +140 -17
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/agent/src/main/java/ibcontroller/agent/AgentMain.java +78 -66
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/agent/src/main/java/ibcontroller/agent/ComponentLookup.java +37 -83
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/agent/src/main/java/ibcontroller/agent/EventBridge.java +74 -41
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/agent/src/main/java/ibcontroller/agent/Protocol.java +25 -12
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/agent/src/main/java/ibcontroller/agent/Security.java +3 -4
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/agent/src/main/java/ibcontroller/agent/WriteOps.java +51 -99
- py_ib_controller-0.1.5/agent/src/test/java/ibcontroller/agent/AgentTests.java +243 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/docs/Configuration.md +82 -5
- py_ib_controller-0.1.5/docs/development.md +469 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/ibcontroller/actions.py +97 -81
- py_ib_controller-0.1.5/ibcontroller/agent_client.py +605 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/ibcontroller/cli.py +55 -22
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/ibcontroller/config.py +132 -34
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/ibcontroller/control_loop.py +331 -220
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/ibcontroller/data/builtin_settings.toml +1 -1
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/ibcontroller/data/ibcontroller.toml.example +64 -18
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/ibcontroller/data/ibkr_settings.toml.example +30 -20
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/ibcontroller/data/labels.json +65 -4
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/ibcontroller/data/labels.json.example +10 -11
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/ibcontroller/diagnostics.py +53 -18
- py_ib_controller-0.1.5/ibcontroller/dispatch.py +391 -0
- py_ib_controller-0.1.5/ibcontroller/errors.py +33 -0
- py_ib_controller-0.1.5/ibcontroller/ibcontroller-agent.jar +0 -0
- py_ib_controller-0.1.5/ibcontroller/labels.py +437 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/ibcontroller/launcher.py +393 -219
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/ibcontroller/logging_setup.py +86 -22
- py_ib_controller-0.1.5/ibcontroller/login.py +660 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/ibcontroller/main.py +22 -13
- py_ib_controller-0.1.5/ibcontroller/management.py +124 -0
- py_ib_controller-0.1.5/ibcontroller/recognisers.py +789 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/ibcontroller/schedule.py +2 -1
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/ibcontroller/settings.py +158 -95
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/ibcontroller.env +33 -11
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/py_ib_controller.egg-info/PKG-INFO +141 -18
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/py_ib_controller.egg-info/SOURCES.txt +7 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/py_ib_controller.egg-info/scm_file_list.json +7 -0
- py_ib_controller-0.1.5/py_ib_controller.egg-info/scm_version.json +8 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/pyproject.toml +1 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/tests/conftest.py +12 -4
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/tests/fakes.py +42 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/tests/test_actions.py +214 -196
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/tests/test_agent_client.py +456 -33
- py_ib_controller-0.1.5/tests/test_agent_jar.py +136 -0
- py_ib_controller-0.1.5/tests/test_cli.py +351 -0
- py_ib_controller-0.1.5/tests/test_command_wire.py +162 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/tests/test_config.py +170 -19
- py_ib_controller-0.1.5/tests/test_control_loop.py +1172 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/tests/test_diagnostics.py +101 -4
- py_ib_controller-0.1.5/tests/test_dispatch.py +710 -0
- py_ib_controller-0.1.5/tests/test_errors.py +73 -0
- py_ib_controller-0.1.5/tests/test_labels.py +240 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/tests/test_launcher.py +676 -309
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/tests/test_logging_setup.py +46 -3
- py_ib_controller-0.1.5/tests/test_login.py +1858 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/tests/test_main.py +7 -8
- py_ib_controller-0.1.5/tests/test_management.py +306 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/tests/test_recognisers.py +719 -32
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/tests/test_schedule.py +5 -3
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/tests/test_settings.py +505 -60
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/tests/test_trace_integration.py +12 -11
- py_ib_controller-0.1.3/docs/development.md +0 -132
- py_ib_controller-0.1.3/ibcontroller/agent_client.py +0 -466
- py_ib_controller-0.1.3/ibcontroller/dispatch.py +0 -288
- py_ib_controller-0.1.3/ibcontroller/ibcontroller-agent.jar +0 -0
- py_ib_controller-0.1.3/ibcontroller/labels.py +0 -242
- py_ib_controller-0.1.3/ibcontroller/login.py +0 -578
- py_ib_controller-0.1.3/ibcontroller/recognisers.py +0 -448
- py_ib_controller-0.1.3/py_ib_controller.egg-info/scm_version.json +0 -8
- py_ib_controller-0.1.3/tests/test_cli.py +0 -173
- py_ib_controller-0.1.3/tests/test_control_loop.py +0 -688
- py_ib_controller-0.1.3/tests/test_dispatch.py +0 -360
- py_ib_controller-0.1.3/tests/test_labels.py +0 -85
- py_ib_controller-0.1.3/tests/test_login.py +0 -1263
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/.github/ISSUE_TEMPLATE.md +0 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/.github/dependabot.yml +0 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/.gitignore +0 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/.pre-commit-config.yaml +0 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/.sdkmanrc +0 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/CONTRIBUTING.md +0 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/LICENSE.txt +0 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/docs/Architecture.svg +0 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/ibcontroller/__init__.py +0 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/ibcontroller/app_dirs.py +0 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/ibcontroller/py.typed +0 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/py_ib_controller.egg-info/dependency_links.txt +0 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/py_ib_controller.egg-info/entry_points.txt +0 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/py_ib_controller.egg-info/requires.txt +0 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/py_ib_controller.egg-info/top_level.txt +0 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/setup.cfg +0 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/tests/__init__.py +0 -0
- {py_ib_controller-0.1.3 → py_ib_controller-0.1.5}/tests/test_app_dirs.py +0 -0
|
@@ -21,7 +21,7 @@ jobs:
|
|
|
21
21
|
- uses: actions/checkout@v7
|
|
22
22
|
|
|
23
23
|
- name: Install uv and Python
|
|
24
|
-
uses: astral-sh/setup-uv@v10.
|
|
24
|
+
uses: astral-sh/setup-uv@v10.2.0
|
|
25
25
|
with:
|
|
26
26
|
python-version: ${{ matrix.python-version }}
|
|
27
27
|
|
|
@@ -40,8 +40,11 @@ jobs:
|
|
|
40
40
|
- name: Build Java agent jar
|
|
41
41
|
run: make build
|
|
42
42
|
|
|
43
|
+
- name: Test Java agent
|
|
44
|
+
run: make test-java
|
|
45
|
+
|
|
43
46
|
- name: Test
|
|
44
|
-
run: uv run pytest
|
|
47
|
+
run: uv run pytest -n auto
|
|
45
48
|
|
|
46
49
|
- name: Build
|
|
47
50
|
run: uv build
|
|
@@ -19,6 +19,21 @@ build/ibcontroller-agent.jar: $(SRC)
|
|
|
19
19
|
.PHONY: build
|
|
20
20
|
build: build/ibcontroller-agent.jar
|
|
21
21
|
|
|
22
|
+
# Dependency-free Java unit tests (plain javac + java, same toolchain as the jar).
|
|
23
|
+
TEST_SRC := $(wildcard agent/src/test/java/ibcontroller/agent/*.java)
|
|
24
|
+
|
|
25
|
+
.PHONY: test-java
|
|
26
|
+
test-java: $(SRC) $(TEST_SRC)
|
|
27
|
+
mkdir -p build/test-classes
|
|
28
|
+
$(JAVAC) -d build/test-classes $(SRC) $(TEST_SRC)
|
|
29
|
+
$(JDK)/bin/java -Djava.awt.headless=true -cp build/test-classes ibcontroller.agent.AgentTests
|
|
30
|
+
|
|
31
|
+
# Everything: build the jar, run the Java unit tests, then the Python suite (which
|
|
32
|
+
# includes the tests that drive the built jar).
|
|
33
|
+
.PHONY: test
|
|
34
|
+
test: build test-java
|
|
35
|
+
uv run pytest -n auto
|
|
36
|
+
|
|
22
37
|
# The Python package can't be built standalone -- `ibcontroller/ibcontroller-agent.jar`
|
|
23
38
|
# must exist before `uv build` runs, since it's shipped via [tool.setuptools.package-data]
|
|
24
39
|
# the same way ibcontroller/data/*.json is (see pyproject.toml). This target makes that
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: py-ib-controller
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: Login/launch controller for TWS and IBKR Gateway, via an in-process Java agent
|
|
5
5
|
Author-email: gnzsnz <8376642+gnzsnz@users.noreply.github.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -52,7 +52,9 @@ Give it a spin, `ib-controller run --dotenv=.env-paper --trading-mode=paper --pr
|
|
|
52
52
|
|
|
53
53
|
https://github.com/user-attachments/assets/b97f7180-29d9-4e09-b32f-8be838dff0e2
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
You can check the full list of [cli parameters](#cli-parameters) or [config keys](#configuration).
|
|
56
|
+
|
|
57
|
+
To it build from source:
|
|
56
58
|
|
|
57
59
|
```bash
|
|
58
60
|
# 0. Fetch the code
|
|
@@ -70,9 +72,11 @@ uv run ibcontroller init
|
|
|
70
72
|
uv run ibcontroller run
|
|
71
73
|
```
|
|
72
74
|
|
|
75
|
+
For a detailed build guide please check the [development guide](./docs/development.md).
|
|
76
|
+
|
|
73
77
|
`ibcontroller run` scaffolds the config directory automatically the first time
|
|
74
78
|
`ibcontroller.toml` is missing (same effect as running `init` first), so it can also be
|
|
75
|
-
run directly on a fresh install
|
|
79
|
+
run directly on a fresh install. It will still fail with a clear error naming whichever
|
|
76
80
|
setting is actually missing (in practice, just the two credential env vars).
|
|
77
81
|
|
|
78
82
|
### CLI parameters
|
|
@@ -121,7 +125,7 @@ And the `ibcontroller run` parameters:
|
|
|
121
125
|
│ --trading-mode <live|paper> Override Config.trading_mode ('live'/'paper') for this invocation. │
|
|
122
126
|
│ --tws-path <path> Override Config.tws_path (the TWS/Gateway install-path inference) for this invocation. │
|
|
123
127
|
│ --tws-channel <str> Override Config.tws_channel ('stable'/'latest', filters install auto-detection) for this invocation. │
|
|
124
|
-
│ --program <
|
|
128
|
+
│ --program <gateway|tws> Override Config.program ('gateway'/'tws') for this invocation. │
|
|
125
129
|
│ --tws-settings-path <path> Override Config.tws_settings_path (where TWS/Gateway stores its own settings) for this invocation. │
|
|
126
130
|
│ --instance <str> Override Config.instance (per-instance log/trace/socket names) for this invocation. Defaults to '{program}-{trading_mode}', so │
|
|
127
131
|
│ --trading-mode alone is usually enough to keep paper/live instances apart. │
|
|
@@ -179,6 +183,11 @@ Each also accepts a `_FILE`-suffixed variant (`IBC_USERID_FILE`,
|
|
|
179
183
|
`IBC_PASSWORD_FILE`) that reads the value from a file instead —
|
|
180
184
|
Docker/Compose secrets, so a value never has to sit in the process environment.
|
|
181
185
|
|
|
186
|
+
The startup `IBC_*` environment dump masks any variable whose name contains
|
|
187
|
+
`PASSWORD`, `USERID`, `SECRET`, `TOKEN`, `TOTP` or `KEY` (e.g.
|
|
188
|
+
`IBC_PASSWORD_PAPER`, set by other tooling), except `_FILE` variants, which
|
|
189
|
+
only hold a path.
|
|
190
|
+
|
|
182
191
|
### File location
|
|
183
192
|
|
|
184
193
|
When environment variable `IBC_APP_DIR` is not set, then
|
|
@@ -188,7 +197,7 @@ When environment variable `IBC_APP_DIR` is not set, then
|
|
|
188
197
|
| --- | --- | --- |
|
|
189
198
|
| `config_dir` | `~/.config/ibcontroller` | Linux |
|
|
190
199
|
| `config_dir` | `~/Library/Application Support/ibcontroller` | macOS |
|
|
191
|
-
| `log_dir` | `~/.local/
|
|
200
|
+
| `log_dir` | `~/.local/state/ibcontroller/log` | Linux |
|
|
192
201
|
| `log_dir` | `~/Library/Logs/ibcontroller` | macOS |
|
|
193
202
|
| `socket_dir` | `/run/user/<uid>/ibcontroller` | Linux |
|
|
194
203
|
| `socket_dir` | `~/Library/Caches/TemporaryItems/ibcontroller` | macOS |
|
|
@@ -199,6 +208,13 @@ When `IBC_APP_DIR` is set then:
|
|
|
199
208
|
- `log_dir`-> `IBC_APP_DIR/log`
|
|
200
209
|
- `socket_dir` -> `IBC_APP_DIR/run`
|
|
201
210
|
|
|
211
|
+
Socket paths (`socket_dir/ibcontroller-agent-<instance>-events.sock`) must fit
|
|
212
|
+
the Unix socket limit: 102 bytes on macOS, 106 on Linux. On macOS the default
|
|
213
|
+
`socket_dir` leaves room for an instance name of about 15 characters with a
|
|
214
|
+
5-letter user name (each extra user-name character costs one). Over the limit,
|
|
215
|
+
`ibcontroller run` stops with an error naming the path; use a shorter instance
|
|
216
|
+
name or set `IBC_APP_DIR` to a shorter directory.
|
|
217
|
+
|
|
202
218
|
### Settings
|
|
203
219
|
|
|
204
220
|
`ibcontroller`ships with sensible defaults. **Only the two credential env vars below are actually mandatory**
|
|
@@ -214,7 +230,7 @@ allowing `None`. `Config`'s schema is closed — an unknown key in
|
|
|
214
230
|
| TOML key | Env var or CLI flag | Description | Default |
|
|
215
231
|
| --- | --- | --- | --- |
|
|
216
232
|
| `instance` | `IBC_INSTANCE`, `--instance` | Instance name; separate log/trace files per instance | `"{program}-{trading_mode}"` (e.g. `"gateway-paper"`) |
|
|
217
|
-
| `program` | `IBC_PROGRAM`, `--program` | `"gateway"` or `"tws"` | `"gateway"` |
|
|
233
|
+
| `program` | `IBC_PROGRAM`, `--program` | `"gateway"` or `"tws"` (any letter case); anything else is a config error | `"gateway"` |
|
|
218
234
|
| `tws_version` | `IBC_TWS_VERSION` | Installed TWS/Gateway version, e.g. `"10.50"` | `None` — auto-detected (see below) |
|
|
219
235
|
| `tws_channel` | `IBC_TWS_CHANNEL`, `--tws-channel` | `"stable"`/`"latest"`; narrows auto-detection to one update channel | `"stable"` |
|
|
220
236
|
| `tws_path` | `IBC_TWS_PATH`, `--tws-path` | Override TWS/gateway install-path inference | `None` |
|
|
@@ -224,8 +240,11 @@ allowing `None`. `Config`'s schema is closed — an unknown key in
|
|
|
224
240
|
| `log_dir` | `IBC_LOG_DIR` | Where ibcontroller's own log file lives; always resolved at startup | platform default |
|
|
225
241
|
| `log_level` | `IBC_LOG_LEVEL` | `debug`/`info`/`warning`/`error` | `info` |
|
|
226
242
|
| `log_sink` | `IBC_LOG_SINK` | `"std"` (console only) or `"file"` (only, under `log_dir`) — exclusive, not both. Covers `ibcontroller-{instance}.log` and `gateway-{instance}.log` only; the Java agent log and the wire trace are always file, unaffected — see "Logging" below | `"std"` |
|
|
243
|
+
| `log_max_bytes` | `IBC_LOG_MAX_BYTES` | Rotate log/trace files at this size in bytes; `0` disables rotation. Only respected when `log_sink="file"` | `10485760` (10 MiB) |
|
|
244
|
+
| `log_backup_count` | `IBC_LOG_BACKUP_COUNT` | Number of rotated backups to keep. Only respected when `log_sink="file"` | `5` |
|
|
227
245
|
| `diagnostic_scope` | `IBC_DIAGNOSTIC_SCOPE` | `"known"`/`"unknown"`/`"all"` — which windows get a structure dump logged (see `docs/Configuration.md`'s "Diagnostics") | `"known"` |
|
|
228
246
|
| `diagnostic_when` | `IBC_DIAGNOSTIC_WHEN` | `"open"`/`"openclose"`/`"never"` — when to log a structure dump | `"never"` |
|
|
247
|
+
| `diagnostic_settings_sweep` | `IBC_DIAGNOSTIC_SETTINGS_SWEEP` | Log a dump of each Global Configuration tree node in `labels.settings.sweep_paths` (see `docs/Configuration.md`'s "Diagnostics") | `false` |
|
|
229
248
|
| (env only) | `IBC_APP_DIR`, `--app-dir` | Override file locations (config/log/run) for container mode; `--app-dir`/`init`/`run` set the env var for the invocation, same effect |
|
|
230
249
|
|
|
231
250
|
TWS/ibgateway settings
|
|
@@ -233,16 +252,19 @@ TWS/ibgateway settings
|
|
|
233
252
|
| TOML key | Env var or CLI flag | Description | Default |
|
|
234
253
|
| --- | --- | --- | --- |
|
|
235
254
|
| `trading_mode` | `IBC_TRADING_MODE`, `--trading-mode` | `"live"` or `"paper"`; which account to authenticate as | `"paper"` |
|
|
236
|
-
| `read_only_login` | `IBC_READ_ONLY_LOGIN` | TWS-only;
|
|
255
|
+
| `read_only_login` | `IBC_READ_ONLY_LOGIN` | TWS-only (Gateway warns and logs in normally); clicks "Enter Read Only" on the 2FA frame instead of waiting for the push, no effect without 2FA (paper) | `false` |
|
|
237
256
|
| `read_only_api` | `IBC_READ_ONLY_API` | `true`/`false` sets it; omit to leave unchanged. Applied to Gateway/TWS automatically -- see "Declarative configuration" below | `None` |
|
|
238
257
|
| `accept_incoming_connections` | `IBC_ACCEPT_INCOMING_CONNECTIONS` | `manual`/`accept`/`reject` | `"manual"` |
|
|
239
258
|
| `existing_session_action` | `IBC_EXISTING_SESSION_ACTION` | `manual`/`primary`/`primaryoverride`/`secondary` | `"manual"` |
|
|
240
259
|
| `login_dialog_display_timeout` | `IBC_LOGIN_DIALOG_DISPLAY_TIMEOUT` | Seconds to wait for the login dialog | `60.0` |
|
|
241
|
-
| `mfa_timeout` | `IBC_MFA_TIMEOUT` | IB's 2FA
|
|
242
|
-
| `relogin_after_mfa_timeout` | `IBC_RELOGIN_AFTER_MFA_TIMEOUT` |
|
|
243
|
-
| `mfa_exit_interval` | `IBC_MFA_EXIT_INTERVAL` |
|
|
260
|
+
| `mfa_timeout` | `IBC_MFA_TIMEOUT` | IB's 2FA limit in seconds; a dialog closing at or after it counts as timed out | `180.0` |
|
|
261
|
+
| `relogin_after_mfa_timeout` | `IBC_RELOGIN_AFTER_MFA_TIMEOUT` | Log in again after a timed-out push, and enforce `mfa_exit_interval` after an approved one | `false` |
|
|
262
|
+
| `mfa_exit_interval` | `IBC_MFA_EXIT_INTERVAL` | Post-approval wait for login to complete when relogin is on | `60.0` |
|
|
263
|
+
| `mfa_timeout_action` | `IBC_MFA_TIMEOUT_ACTION` | `exit` (exit 75, see "Exit codes" above) or `restart` (relaunch with a fresh login) when `mfa_exit_interval` expires after an approved push | `"exit"` |
|
|
264
|
+
| `restart_cap` | `IBC_RESTART_CAP` | Max consecutive relaunches that never reach READY (login frame timeout, transient login failure, logout, crash during login); then exit 70 | `3` |
|
|
244
265
|
| `auto_restart_time` | `IBC_AUTO_RESTART_TIME` | `"hh:mm AM/PM"` daily auto-restart time. Applied to Gateway/TWS automatically -- see "Declarative configuration" below | `None` |
|
|
245
266
|
| `auto_logoff_time` | `IBC_AUTO_LOGOFF_TIME` | `"hh:mm AM/PM"` daily auto-logoff time; same "Lock and Exit" radio-button pair as `auto_restart_time` -- if both are set, `auto_restart_time` wins | `None` |
|
|
267
|
+
| `time_zone` | `IBC_TIME_ZONE` | IANA zone name, e.g. `"Europe/Zurich"`, written to `jts.ini`'s `TimeZone`. Auto-detected from `TZ`, then `/etc/localtime` -- no setup needed in the common case; never overwrites an existing `TimeZone` line | auto-detected |
|
|
246
268
|
| `cold_restart_time` | `IBC_COLD_RESTART_TIME` | TWS and Gateway. `"HH:MM"` 24-hour local time; every Sunday, ibcontroller closes the instance tidily and relaunches with a full fresh login, forcing IBKR's weekly Sunday 01:00 US/Eastern token-invalidation reauth -- not a GUI setting, see "Scheduled shutdown" below | `None` |
|
|
247
269
|
| `closedown_at` | `IBC_CLOSEDOWN_AT` | TWS and Gateway. `"HH:MM"` (daily) or `"<Weekday> HH:MM"` (weekly); closes the instance tidily at that time, no relaunch -- not a GUI setting, see "Scheduled shutdown" below | `None` |
|
|
248
270
|
|
|
@@ -288,7 +310,7 @@ US/Eastern invalidation window if that's the intent.
|
|
|
288
310
|
|
|
289
311
|
## Declarative configuration
|
|
290
312
|
|
|
291
|
-
Beyond `ibcontroller.toml`,
|
|
313
|
+
Beyond `ibcontroller.toml`, two things let you shape ibcontroller's behavior without
|
|
292
314
|
writing any Python: **`labels.json`** (window/button text, plus simple pop-up dismissal
|
|
293
315
|
rules) and **`ibkr_settings.toml`** (Global Configuration settings applied to Gateway/TWS
|
|
294
316
|
at startup). Each is covered below.
|
|
@@ -300,10 +322,19 @@ more fields of the bundled default, field by field — you only need to include
|
|
|
300
322
|
you're changing, not the whole file. Use it when a TWS/Gateway release renames a button
|
|
301
323
|
or dialog title ibcontroller matches against.
|
|
302
324
|
|
|
325
|
+
A malformed file is rejected at startup (exit code 78) with a message naming the file and
|
|
326
|
+
the field: a string where a list is expected, an empty list of titles or buttons, invalid
|
|
327
|
+
JSON, or a `dismiss_rules` entry without `match_title`/`match_text`. A misspelled key is
|
|
328
|
+
logged as a warning and ignored, so check the log if an override seems to have no effect.
|
|
329
|
+
|
|
330
|
+
`generic.dismiss_buttons` lists the buttons tried, in order, to close a pop-up no specific
|
|
331
|
+
handler recognises (default `OK`, `Close`, `Dismiss`).
|
|
332
|
+
|
|
303
333
|
The one part of `labels.json` that isn't just text is `dismiss_rules` — a list of
|
|
304
334
|
"if a window matches, click this button" entries, checked on every window that opens.
|
|
305
335
|
Each entry needs at least one of `match_title`/`match_text` (both, if given, must
|
|
306
|
-
match); `click` is the button's label.
|
|
336
|
+
match); `click` is the button's label. `match_text` also matches message text with HTML
|
|
337
|
+
tags removed and entities decoded, so copy the phrase as it shows on screen.
|
|
307
338
|
|
|
308
339
|
```json
|
|
309
340
|
{
|
|
@@ -317,13 +348,18 @@ match); `click` is the button's label.
|
|
|
317
348
|
}
|
|
318
349
|
```
|
|
319
350
|
|
|
320
|
-
`dismiss_rules`
|
|
321
|
-
|
|
322
|
-
confirmation notice)
|
|
323
|
-
|
|
351
|
+
`dismiss_rules` entries are merged by `name`: an entry with a bundled rule's name replaces
|
|
352
|
+
that rule, an entry with a new name is added, and bundled rules (the non-brokerage-account
|
|
353
|
+
warning, the auto-restart confirmation notice) you don't name are kept. Removing a bundled
|
|
354
|
+
rule is not supported. Good for any pop-up that's always handled the same
|
|
324
355
|
simple way; a dialog needing real logic (parsing a value out of its text, retrying,
|
|
325
356
|
raising an error) isn't expressible this way and needs actual code.
|
|
326
357
|
|
|
358
|
+
Login-outcome dialogs ("Login failed", "Unrecognized Username or Password", the connection
|
|
359
|
+
and too-many-attempts warnings) and the "Shutdown progress" / "Restart in progress" windows
|
|
360
|
+
are always handled first. A `dismiss_rules` entry that matches one of them never runs, so a
|
|
361
|
+
credential rejection still exits with code 77 and a restart is still recognised as one.
|
|
362
|
+
|
|
327
363
|
### `ibkr_settings.toml` — Global Configuration settings
|
|
328
364
|
|
|
329
365
|
Global Configuration settings (the "Configure" / "Settings" dialog inside Gateway/TWS)
|
|
@@ -338,7 +374,13 @@ are applied once, right after login, from two layers combined into one list:
|
|
|
338
374
|
- **Your own `ibkr_settings.toml`** — anything else: any other Global Configuration
|
|
339
375
|
checkbox or text field, activated by pointing `[settings] file = "..."` (or
|
|
340
376
|
`IBC_SETTINGS_FILE`) at a real file (rename `ibkr_settings.toml.example` to
|
|
341
|
-
start from the bundled worked examples
|
|
377
|
+
start from the bundled worked examples: the `api_precautions_*` toggles, plus commented
|
|
378
|
+
entries for the API socket port, the master API client ID and "Send market data in lots
|
|
379
|
+
for US stocks" under `API/Settings`).
|
|
380
|
+
|
|
381
|
+
When no entry resolves to a value (the default), the dialog is not opened. Only the TWS Mosaic
|
|
382
|
+
layout is supported: on TWS Classic no setting is applied and the attempt waits for
|
|
383
|
+
`mfa_timeout` (default 180s) before failing. Gateway is not affected.
|
|
342
384
|
|
|
343
385
|
An entry looks like this:
|
|
344
386
|
|
|
@@ -409,10 +451,17 @@ scrollback.
|
|
|
409
451
|
| --- | --- | --- | --- |
|
|
410
452
|
| `ibcontroller-{instance}.log` | ibcontroller app control flow | `log_sink=file` (else console) | `log_level` (default `info`) |
|
|
411
453
|
| `gateway-{instance}.log` | TWS/Gateway own stdout/stderr | `log_sink=file` (else console) | TWS/Gateway's own |
|
|
412
|
-
| `ibcontroller-java-agent-{instance}.log` | Java agent (in-process JUL) | always file, not affected by `log_sink` | `log_level` (see mapping below) |
|
|
454
|
+
| `ibcontroller-java-agent-{instance}.log` (see rotation note below) | Java agent (in-process JUL) | always file, not affected by `log_sink` | `log_level` (see mapping below) |
|
|
413
455
|
| `cmd-{instance}.jsonl` | raw wire: commands sent + results, NDJSON | `trace_enabled`; always file, not affected by `log_sink` | always (DEBUG emit, gated separately) |
|
|
414
456
|
| `events-{instance}.jsonl` | raw wire: every event message received, NDJSON | `trace_enabled`; always file, not affected by `log_sink` | always (DEBUG emit, gated separately) |
|
|
415
457
|
|
|
458
|
+
**Rotation** (`IBC_LOG_MAX_BYTES`/`IBC_LOG_BACKUP_COUNT`, default 10 MiB x 5
|
|
459
|
+
backups, **on by default**) applies to every file above in file mode. Set
|
|
460
|
+
`IBC_LOG_MAX_BYTES=0` to disable it (unbounded files). Python's files keep
|
|
461
|
+
their usual name active, with `.1`/`.2`/... as old backups. The Java agent log
|
|
462
|
+
is the opposite: once rotation is on, the active file is
|
|
463
|
+
`ibcontroller-java-agent-{instance}.log.0`, not the unsuffixed name.
|
|
464
|
+
|
|
416
465
|
### Log levels
|
|
417
466
|
|
|
418
467
|
`IBC_LOG_LEVEL` (`debug`/`info`/`warning`/`error`, default `info`) controls
|
|
@@ -484,6 +533,80 @@ or both.
|
|
|
484
533
|
instances (e.g. `paper` and `live`) can run in parallel and each tail its own files
|
|
485
534
|
without interleaving, even if copied or globbed out of their directory.
|
|
486
535
|
|
|
536
|
+
## Finding the TWS / Gateway installation
|
|
537
|
+
|
|
538
|
+
ibcontroller does not install anything: it launches the TWS or Gateway you already
|
|
539
|
+
installed with IBKR's own installer, using that install's jars and bundled JRE. It
|
|
540
|
+
resolves the installation in three steps.
|
|
541
|
+
|
|
542
|
+
1. **Install root** (`tws_path`). Unless set, it defaults to:
|
|
543
|
+
|
|
544
|
+
| OS | Default root |
|
|
545
|
+
| --- | --- |
|
|
546
|
+
| macOS | `~/Applications` |
|
|
547
|
+
| Linux | `~/Jts` |
|
|
548
|
+
|
|
549
|
+
2. **Version** (`tws_version`). A pinned `tws_version` is used as-is. Otherwise
|
|
550
|
+
ibcontroller scans the root, picks the greatest installed version and logs the
|
|
551
|
+
choice; `tws_channel` narrows the scan first. See "`tws_version` auto-detection"
|
|
552
|
+
above.
|
|
553
|
+
|
|
554
|
+
3. **Install directory.** Version and `program` select the directory below the root:
|
|
555
|
+
|
|
556
|
+
| OS | Gateway | TWS |
|
|
557
|
+
| --- | --- | --- |
|
|
558
|
+
| macOS | `IB Gateway <version>/` | `Trader Workstation <version>/` |
|
|
559
|
+
| Linux | `ibgateway/<version>/` | `<version>/` |
|
|
560
|
+
|
|
561
|
+
A directory counts as an installation only if it has a `jars/` subfolder. The
|
|
562
|
+
classpath is built from those jars plus the install's `.install4j/i4jruntime.jar`,
|
|
563
|
+
and the JVM options come from its `ibgateway.vmoptions` / `tws.vmoptions`.
|
|
564
|
+
|
|
565
|
+
If nothing matches, ibcontroller exits with an error naming `tws_path` and
|
|
566
|
+
`tws_channel`. A `program = "tws"` request with no TWS install falls back to a
|
|
567
|
+
Gateway install; the reverse does not (details above).
|
|
568
|
+
|
|
569
|
+
Settings (`tws_settings_path`) are separate from the install root: each instance
|
|
570
|
+
defaults to its own `~/Jts/<instance>`, so `paper` and `live` do not share state.
|
|
571
|
+
|
|
572
|
+
## Exit codes
|
|
573
|
+
|
|
574
|
+
`ibcontroller run` exits with a [BSD sysexits](https://man.freebsd.org/cgi/man.cgi?query=sysexits)
|
|
575
|
+
code, grouped by what a supervisor (a container's `restart:` policy, systemd's
|
|
576
|
+
`Restart=`) should do about it. The specific cause is always in the logs and in the
|
|
577
|
+
`stopped: <CAUSE>` line.
|
|
578
|
+
|
|
579
|
+
| Code | Meaning | Supervisor action |
|
|
580
|
+
| --- | --- | --- |
|
|
581
|
+
| `0` | Intentional stop (Ctrl-C, `closedown_at`, Gateway/TWS closed normally, e.g. File>Close, or this session yielded to another via `existing_session_action`: `stopped: SESSION_CEDED`) | Do not restart |
|
|
582
|
+
| `1` | Unhandled exception (a bug) | Restart, report |
|
|
583
|
+
| `2` | CLI usage error | Fix the command |
|
|
584
|
+
| `78` (`EX_CONFIG`) | Deployment error (bad config, install not found) | Do not retry |
|
|
585
|
+
| `77` (`EX_NOPERM`) | Credentials rejected | Do not retry (lockout risk) |
|
|
586
|
+
| `76` (`EX_PROTOCOL`) | A dialog `existing_session_action` rules out (Trading Login Handoff with `secondary`/`primaryoverride`) | Do not retry |
|
|
587
|
+
| `75` (`EX_TEMPFAIL`) | Login did not complete after 2FA, `mfa_timeout_action=exit` | Retry per policy |
|
|
588
|
+
| `70` (`EX_SOFTWARE`) | `restart_cap` consecutive relaunches never reached READY | Do not retry |
|
|
589
|
+
| `69` (`EX_UNAVAILABLE`) | Transient runtime failure (process crashed or killed, connection lost, JVM exited before becoming ready) | Retry |
|
|
590
|
+
|
|
591
|
+
`70`/`76`/`77`/`78` work with systemd's `RestartPreventExitStatus=`.
|
|
592
|
+
|
|
593
|
+
Some login-blocking dialogs IB reports as a server-side condition -- not a credential
|
|
594
|
+
rejection -- never reach this table at all: a "Login failed"/"Login Error"/"not currently available" dialog, or a
|
|
595
|
+
Gateway "Connection to server failed" reason other than a known credential/account one
|
|
596
|
+
(e.g. a stale restart token), make ibcontroller relaunch in-process with a full fresh
|
|
597
|
+
login, the same way a scheduled `cold_restart_time` does -- the container
|
|
598
|
+
itself does not exit for these, until `restart_cap` consecutive relaunches never reach READY
|
|
599
|
+
(exit 70).
|
|
600
|
+
|
|
601
|
+
## Stopping under Docker
|
|
602
|
+
|
|
603
|
+
On SIGTERM (`docker stop`, Ctrl-C) a logged-in instance closes through File > Exit/Close so
|
|
604
|
+
TWS/Gateway can save its session and upload settings. Measured over 88 shutdowns: mean 1.3 s,
|
|
605
|
+
p99 4.3 s, max 4.9 s. A slow path (a dialog open, a stuck menu) can take longer: the menu retry
|
|
606
|
+
waits up to 5 s, the exit up to 8 s, then SIGTERM gets 5 s. Docker sends SIGKILL after 10 s by
|
|
607
|
+
default, which loses the graceful close. Set `stop_grace_period: 30s` (Compose) or
|
|
608
|
+
`docker stop -t 30` to keep it.
|
|
609
|
+
|
|
487
610
|
## License
|
|
488
611
|
|
|
489
612
|
```text
|
|
@@ -30,7 +30,9 @@ Give it a spin, `ib-controller run --dotenv=.env-paper --trading-mode=paper --pr
|
|
|
30
30
|
|
|
31
31
|
https://github.com/user-attachments/assets/b97f7180-29d9-4e09-b32f-8be838dff0e2
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
You can check the full list of [cli parameters](#cli-parameters) or [config keys](#configuration).
|
|
34
|
+
|
|
35
|
+
To it build from source:
|
|
34
36
|
|
|
35
37
|
```bash
|
|
36
38
|
# 0. Fetch the code
|
|
@@ -48,9 +50,11 @@ uv run ibcontroller init
|
|
|
48
50
|
uv run ibcontroller run
|
|
49
51
|
```
|
|
50
52
|
|
|
53
|
+
For a detailed build guide please check the [development guide](./docs/development.md).
|
|
54
|
+
|
|
51
55
|
`ibcontroller run` scaffolds the config directory automatically the first time
|
|
52
56
|
`ibcontroller.toml` is missing (same effect as running `init` first), so it can also be
|
|
53
|
-
run directly on a fresh install
|
|
57
|
+
run directly on a fresh install. It will still fail with a clear error naming whichever
|
|
54
58
|
setting is actually missing (in practice, just the two credential env vars).
|
|
55
59
|
|
|
56
60
|
### CLI parameters
|
|
@@ -99,7 +103,7 @@ And the `ibcontroller run` parameters:
|
|
|
99
103
|
│ --trading-mode <live|paper> Override Config.trading_mode ('live'/'paper') for this invocation. │
|
|
100
104
|
│ --tws-path <path> Override Config.tws_path (the TWS/Gateway install-path inference) for this invocation. │
|
|
101
105
|
│ --tws-channel <str> Override Config.tws_channel ('stable'/'latest', filters install auto-detection) for this invocation. │
|
|
102
|
-
│ --program <
|
|
106
|
+
│ --program <gateway|tws> Override Config.program ('gateway'/'tws') for this invocation. │
|
|
103
107
|
│ --tws-settings-path <path> Override Config.tws_settings_path (where TWS/Gateway stores its own settings) for this invocation. │
|
|
104
108
|
│ --instance <str> Override Config.instance (per-instance log/trace/socket names) for this invocation. Defaults to '{program}-{trading_mode}', so │
|
|
105
109
|
│ --trading-mode alone is usually enough to keep paper/live instances apart. │
|
|
@@ -157,6 +161,11 @@ Each also accepts a `_FILE`-suffixed variant (`IBC_USERID_FILE`,
|
|
|
157
161
|
`IBC_PASSWORD_FILE`) that reads the value from a file instead —
|
|
158
162
|
Docker/Compose secrets, so a value never has to sit in the process environment.
|
|
159
163
|
|
|
164
|
+
The startup `IBC_*` environment dump masks any variable whose name contains
|
|
165
|
+
`PASSWORD`, `USERID`, `SECRET`, `TOKEN`, `TOTP` or `KEY` (e.g.
|
|
166
|
+
`IBC_PASSWORD_PAPER`, set by other tooling), except `_FILE` variants, which
|
|
167
|
+
only hold a path.
|
|
168
|
+
|
|
160
169
|
### File location
|
|
161
170
|
|
|
162
171
|
When environment variable `IBC_APP_DIR` is not set, then
|
|
@@ -166,7 +175,7 @@ When environment variable `IBC_APP_DIR` is not set, then
|
|
|
166
175
|
| --- | --- | --- |
|
|
167
176
|
| `config_dir` | `~/.config/ibcontroller` | Linux |
|
|
168
177
|
| `config_dir` | `~/Library/Application Support/ibcontroller` | macOS |
|
|
169
|
-
| `log_dir` | `~/.local/
|
|
178
|
+
| `log_dir` | `~/.local/state/ibcontroller/log` | Linux |
|
|
170
179
|
| `log_dir` | `~/Library/Logs/ibcontroller` | macOS |
|
|
171
180
|
| `socket_dir` | `/run/user/<uid>/ibcontroller` | Linux |
|
|
172
181
|
| `socket_dir` | `~/Library/Caches/TemporaryItems/ibcontroller` | macOS |
|
|
@@ -177,6 +186,13 @@ When `IBC_APP_DIR` is set then:
|
|
|
177
186
|
- `log_dir`-> `IBC_APP_DIR/log`
|
|
178
187
|
- `socket_dir` -> `IBC_APP_DIR/run`
|
|
179
188
|
|
|
189
|
+
Socket paths (`socket_dir/ibcontroller-agent-<instance>-events.sock`) must fit
|
|
190
|
+
the Unix socket limit: 102 bytes on macOS, 106 on Linux. On macOS the default
|
|
191
|
+
`socket_dir` leaves room for an instance name of about 15 characters with a
|
|
192
|
+
5-letter user name (each extra user-name character costs one). Over the limit,
|
|
193
|
+
`ibcontroller run` stops with an error naming the path; use a shorter instance
|
|
194
|
+
name or set `IBC_APP_DIR` to a shorter directory.
|
|
195
|
+
|
|
180
196
|
### Settings
|
|
181
197
|
|
|
182
198
|
`ibcontroller`ships with sensible defaults. **Only the two credential env vars below are actually mandatory**
|
|
@@ -192,7 +208,7 @@ allowing `None`. `Config`'s schema is closed — an unknown key in
|
|
|
192
208
|
| TOML key | Env var or CLI flag | Description | Default |
|
|
193
209
|
| --- | --- | --- | --- |
|
|
194
210
|
| `instance` | `IBC_INSTANCE`, `--instance` | Instance name; separate log/trace files per instance | `"{program}-{trading_mode}"` (e.g. `"gateway-paper"`) |
|
|
195
|
-
| `program` | `IBC_PROGRAM`, `--program` | `"gateway"` or `"tws"` | `"gateway"` |
|
|
211
|
+
| `program` | `IBC_PROGRAM`, `--program` | `"gateway"` or `"tws"` (any letter case); anything else is a config error | `"gateway"` |
|
|
196
212
|
| `tws_version` | `IBC_TWS_VERSION` | Installed TWS/Gateway version, e.g. `"10.50"` | `None` — auto-detected (see below) |
|
|
197
213
|
| `tws_channel` | `IBC_TWS_CHANNEL`, `--tws-channel` | `"stable"`/`"latest"`; narrows auto-detection to one update channel | `"stable"` |
|
|
198
214
|
| `tws_path` | `IBC_TWS_PATH`, `--tws-path` | Override TWS/gateway install-path inference | `None` |
|
|
@@ -202,8 +218,11 @@ allowing `None`. `Config`'s schema is closed — an unknown key in
|
|
|
202
218
|
| `log_dir` | `IBC_LOG_DIR` | Where ibcontroller's own log file lives; always resolved at startup | platform default |
|
|
203
219
|
| `log_level` | `IBC_LOG_LEVEL` | `debug`/`info`/`warning`/`error` | `info` |
|
|
204
220
|
| `log_sink` | `IBC_LOG_SINK` | `"std"` (console only) or `"file"` (only, under `log_dir`) — exclusive, not both. Covers `ibcontroller-{instance}.log` and `gateway-{instance}.log` only; the Java agent log and the wire trace are always file, unaffected — see "Logging" below | `"std"` |
|
|
221
|
+
| `log_max_bytes` | `IBC_LOG_MAX_BYTES` | Rotate log/trace files at this size in bytes; `0` disables rotation. Only respected when `log_sink="file"` | `10485760` (10 MiB) |
|
|
222
|
+
| `log_backup_count` | `IBC_LOG_BACKUP_COUNT` | Number of rotated backups to keep. Only respected when `log_sink="file"` | `5` |
|
|
205
223
|
| `diagnostic_scope` | `IBC_DIAGNOSTIC_SCOPE` | `"known"`/`"unknown"`/`"all"` — which windows get a structure dump logged (see `docs/Configuration.md`'s "Diagnostics") | `"known"` |
|
|
206
224
|
| `diagnostic_when` | `IBC_DIAGNOSTIC_WHEN` | `"open"`/`"openclose"`/`"never"` — when to log a structure dump | `"never"` |
|
|
225
|
+
| `diagnostic_settings_sweep` | `IBC_DIAGNOSTIC_SETTINGS_SWEEP` | Log a dump of each Global Configuration tree node in `labels.settings.sweep_paths` (see `docs/Configuration.md`'s "Diagnostics") | `false` |
|
|
207
226
|
| (env only) | `IBC_APP_DIR`, `--app-dir` | Override file locations (config/log/run) for container mode; `--app-dir`/`init`/`run` set the env var for the invocation, same effect |
|
|
208
227
|
|
|
209
228
|
TWS/ibgateway settings
|
|
@@ -211,16 +230,19 @@ TWS/ibgateway settings
|
|
|
211
230
|
| TOML key | Env var or CLI flag | Description | Default |
|
|
212
231
|
| --- | --- | --- | --- |
|
|
213
232
|
| `trading_mode` | `IBC_TRADING_MODE`, `--trading-mode` | `"live"` or `"paper"`; which account to authenticate as | `"paper"` |
|
|
214
|
-
| `read_only_login` | `IBC_READ_ONLY_LOGIN` | TWS-only;
|
|
233
|
+
| `read_only_login` | `IBC_READ_ONLY_LOGIN` | TWS-only (Gateway warns and logs in normally); clicks "Enter Read Only" on the 2FA frame instead of waiting for the push, no effect without 2FA (paper) | `false` |
|
|
215
234
|
| `read_only_api` | `IBC_READ_ONLY_API` | `true`/`false` sets it; omit to leave unchanged. Applied to Gateway/TWS automatically -- see "Declarative configuration" below | `None` |
|
|
216
235
|
| `accept_incoming_connections` | `IBC_ACCEPT_INCOMING_CONNECTIONS` | `manual`/`accept`/`reject` | `"manual"` |
|
|
217
236
|
| `existing_session_action` | `IBC_EXISTING_SESSION_ACTION` | `manual`/`primary`/`primaryoverride`/`secondary` | `"manual"` |
|
|
218
237
|
| `login_dialog_display_timeout` | `IBC_LOGIN_DIALOG_DISPLAY_TIMEOUT` | Seconds to wait for the login dialog | `60.0` |
|
|
219
|
-
| `mfa_timeout` | `IBC_MFA_TIMEOUT` | IB's 2FA
|
|
220
|
-
| `relogin_after_mfa_timeout` | `IBC_RELOGIN_AFTER_MFA_TIMEOUT` |
|
|
221
|
-
| `mfa_exit_interval` | `IBC_MFA_EXIT_INTERVAL` |
|
|
238
|
+
| `mfa_timeout` | `IBC_MFA_TIMEOUT` | IB's 2FA limit in seconds; a dialog closing at or after it counts as timed out | `180.0` |
|
|
239
|
+
| `relogin_after_mfa_timeout` | `IBC_RELOGIN_AFTER_MFA_TIMEOUT` | Log in again after a timed-out push, and enforce `mfa_exit_interval` after an approved one | `false` |
|
|
240
|
+
| `mfa_exit_interval` | `IBC_MFA_EXIT_INTERVAL` | Post-approval wait for login to complete when relogin is on | `60.0` |
|
|
241
|
+
| `mfa_timeout_action` | `IBC_MFA_TIMEOUT_ACTION` | `exit` (exit 75, see "Exit codes" above) or `restart` (relaunch with a fresh login) when `mfa_exit_interval` expires after an approved push | `"exit"` |
|
|
242
|
+
| `restart_cap` | `IBC_RESTART_CAP` | Max consecutive relaunches that never reach READY (login frame timeout, transient login failure, logout, crash during login); then exit 70 | `3` |
|
|
222
243
|
| `auto_restart_time` | `IBC_AUTO_RESTART_TIME` | `"hh:mm AM/PM"` daily auto-restart time. Applied to Gateway/TWS automatically -- see "Declarative configuration" below | `None` |
|
|
223
244
|
| `auto_logoff_time` | `IBC_AUTO_LOGOFF_TIME` | `"hh:mm AM/PM"` daily auto-logoff time; same "Lock and Exit" radio-button pair as `auto_restart_time` -- if both are set, `auto_restart_time` wins | `None` |
|
|
245
|
+
| `time_zone` | `IBC_TIME_ZONE` | IANA zone name, e.g. `"Europe/Zurich"`, written to `jts.ini`'s `TimeZone`. Auto-detected from `TZ`, then `/etc/localtime` -- no setup needed in the common case; never overwrites an existing `TimeZone` line | auto-detected |
|
|
224
246
|
| `cold_restart_time` | `IBC_COLD_RESTART_TIME` | TWS and Gateway. `"HH:MM"` 24-hour local time; every Sunday, ibcontroller closes the instance tidily and relaunches with a full fresh login, forcing IBKR's weekly Sunday 01:00 US/Eastern token-invalidation reauth -- not a GUI setting, see "Scheduled shutdown" below | `None` |
|
|
225
247
|
| `closedown_at` | `IBC_CLOSEDOWN_AT` | TWS and Gateway. `"HH:MM"` (daily) or `"<Weekday> HH:MM"` (weekly); closes the instance tidily at that time, no relaunch -- not a GUI setting, see "Scheduled shutdown" below | `None` |
|
|
226
248
|
|
|
@@ -266,7 +288,7 @@ US/Eastern invalidation window if that's the intent.
|
|
|
266
288
|
|
|
267
289
|
## Declarative configuration
|
|
268
290
|
|
|
269
|
-
Beyond `ibcontroller.toml`,
|
|
291
|
+
Beyond `ibcontroller.toml`, two things let you shape ibcontroller's behavior without
|
|
270
292
|
writing any Python: **`labels.json`** (window/button text, plus simple pop-up dismissal
|
|
271
293
|
rules) and **`ibkr_settings.toml`** (Global Configuration settings applied to Gateway/TWS
|
|
272
294
|
at startup). Each is covered below.
|
|
@@ -278,10 +300,19 @@ more fields of the bundled default, field by field — you only need to include
|
|
|
278
300
|
you're changing, not the whole file. Use it when a TWS/Gateway release renames a button
|
|
279
301
|
or dialog title ibcontroller matches against.
|
|
280
302
|
|
|
303
|
+
A malformed file is rejected at startup (exit code 78) with a message naming the file and
|
|
304
|
+
the field: a string where a list is expected, an empty list of titles or buttons, invalid
|
|
305
|
+
JSON, or a `dismiss_rules` entry without `match_title`/`match_text`. A misspelled key is
|
|
306
|
+
logged as a warning and ignored, so check the log if an override seems to have no effect.
|
|
307
|
+
|
|
308
|
+
`generic.dismiss_buttons` lists the buttons tried, in order, to close a pop-up no specific
|
|
309
|
+
handler recognises (default `OK`, `Close`, `Dismiss`).
|
|
310
|
+
|
|
281
311
|
The one part of `labels.json` that isn't just text is `dismiss_rules` — a list of
|
|
282
312
|
"if a window matches, click this button" entries, checked on every window that opens.
|
|
283
313
|
Each entry needs at least one of `match_title`/`match_text` (both, if given, must
|
|
284
|
-
match); `click` is the button's label.
|
|
314
|
+
match); `click` is the button's label. `match_text` also matches message text with HTML
|
|
315
|
+
tags removed and entities decoded, so copy the phrase as it shows on screen.
|
|
285
316
|
|
|
286
317
|
```json
|
|
287
318
|
{
|
|
@@ -295,13 +326,18 @@ match); `click` is the button's label.
|
|
|
295
326
|
}
|
|
296
327
|
```
|
|
297
328
|
|
|
298
|
-
`dismiss_rules`
|
|
299
|
-
|
|
300
|
-
confirmation notice)
|
|
301
|
-
|
|
329
|
+
`dismiss_rules` entries are merged by `name`: an entry with a bundled rule's name replaces
|
|
330
|
+
that rule, an entry with a new name is added, and bundled rules (the non-brokerage-account
|
|
331
|
+
warning, the auto-restart confirmation notice) you don't name are kept. Removing a bundled
|
|
332
|
+
rule is not supported. Good for any pop-up that's always handled the same
|
|
302
333
|
simple way; a dialog needing real logic (parsing a value out of its text, retrying,
|
|
303
334
|
raising an error) isn't expressible this way and needs actual code.
|
|
304
335
|
|
|
336
|
+
Login-outcome dialogs ("Login failed", "Unrecognized Username or Password", the connection
|
|
337
|
+
and too-many-attempts warnings) and the "Shutdown progress" / "Restart in progress" windows
|
|
338
|
+
are always handled first. A `dismiss_rules` entry that matches one of them never runs, so a
|
|
339
|
+
credential rejection still exits with code 77 and a restart is still recognised as one.
|
|
340
|
+
|
|
305
341
|
### `ibkr_settings.toml` — Global Configuration settings
|
|
306
342
|
|
|
307
343
|
Global Configuration settings (the "Configure" / "Settings" dialog inside Gateway/TWS)
|
|
@@ -316,7 +352,13 @@ are applied once, right after login, from two layers combined into one list:
|
|
|
316
352
|
- **Your own `ibkr_settings.toml`** — anything else: any other Global Configuration
|
|
317
353
|
checkbox or text field, activated by pointing `[settings] file = "..."` (or
|
|
318
354
|
`IBC_SETTINGS_FILE`) at a real file (rename `ibkr_settings.toml.example` to
|
|
319
|
-
start from the bundled worked examples
|
|
355
|
+
start from the bundled worked examples: the `api_precautions_*` toggles, plus commented
|
|
356
|
+
entries for the API socket port, the master API client ID and "Send market data in lots
|
|
357
|
+
for US stocks" under `API/Settings`).
|
|
358
|
+
|
|
359
|
+
When no entry resolves to a value (the default), the dialog is not opened. Only the TWS Mosaic
|
|
360
|
+
layout is supported: on TWS Classic no setting is applied and the attempt waits for
|
|
361
|
+
`mfa_timeout` (default 180s) before failing. Gateway is not affected.
|
|
320
362
|
|
|
321
363
|
An entry looks like this:
|
|
322
364
|
|
|
@@ -387,10 +429,17 @@ scrollback.
|
|
|
387
429
|
| --- | --- | --- | --- |
|
|
388
430
|
| `ibcontroller-{instance}.log` | ibcontroller app control flow | `log_sink=file` (else console) | `log_level` (default `info`) |
|
|
389
431
|
| `gateway-{instance}.log` | TWS/Gateway own stdout/stderr | `log_sink=file` (else console) | TWS/Gateway's own |
|
|
390
|
-
| `ibcontroller-java-agent-{instance}.log` | Java agent (in-process JUL) | always file, not affected by `log_sink` | `log_level` (see mapping below) |
|
|
432
|
+
| `ibcontroller-java-agent-{instance}.log` (see rotation note below) | Java agent (in-process JUL) | always file, not affected by `log_sink` | `log_level` (see mapping below) |
|
|
391
433
|
| `cmd-{instance}.jsonl` | raw wire: commands sent + results, NDJSON | `trace_enabled`; always file, not affected by `log_sink` | always (DEBUG emit, gated separately) |
|
|
392
434
|
| `events-{instance}.jsonl` | raw wire: every event message received, NDJSON | `trace_enabled`; always file, not affected by `log_sink` | always (DEBUG emit, gated separately) |
|
|
393
435
|
|
|
436
|
+
**Rotation** (`IBC_LOG_MAX_BYTES`/`IBC_LOG_BACKUP_COUNT`, default 10 MiB x 5
|
|
437
|
+
backups, **on by default**) applies to every file above in file mode. Set
|
|
438
|
+
`IBC_LOG_MAX_BYTES=0` to disable it (unbounded files). Python's files keep
|
|
439
|
+
their usual name active, with `.1`/`.2`/... as old backups. The Java agent log
|
|
440
|
+
is the opposite: once rotation is on, the active file is
|
|
441
|
+
`ibcontroller-java-agent-{instance}.log.0`, not the unsuffixed name.
|
|
442
|
+
|
|
394
443
|
### Log levels
|
|
395
444
|
|
|
396
445
|
`IBC_LOG_LEVEL` (`debug`/`info`/`warning`/`error`, default `info`) controls
|
|
@@ -462,6 +511,80 @@ or both.
|
|
|
462
511
|
instances (e.g. `paper` and `live`) can run in parallel and each tail its own files
|
|
463
512
|
without interleaving, even if copied or globbed out of their directory.
|
|
464
513
|
|
|
514
|
+
## Finding the TWS / Gateway installation
|
|
515
|
+
|
|
516
|
+
ibcontroller does not install anything: it launches the TWS or Gateway you already
|
|
517
|
+
installed with IBKR's own installer, using that install's jars and bundled JRE. It
|
|
518
|
+
resolves the installation in three steps.
|
|
519
|
+
|
|
520
|
+
1. **Install root** (`tws_path`). Unless set, it defaults to:
|
|
521
|
+
|
|
522
|
+
| OS | Default root |
|
|
523
|
+
| --- | --- |
|
|
524
|
+
| macOS | `~/Applications` |
|
|
525
|
+
| Linux | `~/Jts` |
|
|
526
|
+
|
|
527
|
+
2. **Version** (`tws_version`). A pinned `tws_version` is used as-is. Otherwise
|
|
528
|
+
ibcontroller scans the root, picks the greatest installed version and logs the
|
|
529
|
+
choice; `tws_channel` narrows the scan first. See "`tws_version` auto-detection"
|
|
530
|
+
above.
|
|
531
|
+
|
|
532
|
+
3. **Install directory.** Version and `program` select the directory below the root:
|
|
533
|
+
|
|
534
|
+
| OS | Gateway | TWS |
|
|
535
|
+
| --- | --- | --- |
|
|
536
|
+
| macOS | `IB Gateway <version>/` | `Trader Workstation <version>/` |
|
|
537
|
+
| Linux | `ibgateway/<version>/` | `<version>/` |
|
|
538
|
+
|
|
539
|
+
A directory counts as an installation only if it has a `jars/` subfolder. The
|
|
540
|
+
classpath is built from those jars plus the install's `.install4j/i4jruntime.jar`,
|
|
541
|
+
and the JVM options come from its `ibgateway.vmoptions` / `tws.vmoptions`.
|
|
542
|
+
|
|
543
|
+
If nothing matches, ibcontroller exits with an error naming `tws_path` and
|
|
544
|
+
`tws_channel`. A `program = "tws"` request with no TWS install falls back to a
|
|
545
|
+
Gateway install; the reverse does not (details above).
|
|
546
|
+
|
|
547
|
+
Settings (`tws_settings_path`) are separate from the install root: each instance
|
|
548
|
+
defaults to its own `~/Jts/<instance>`, so `paper` and `live` do not share state.
|
|
549
|
+
|
|
550
|
+
## Exit codes
|
|
551
|
+
|
|
552
|
+
`ibcontroller run` exits with a [BSD sysexits](https://man.freebsd.org/cgi/man.cgi?query=sysexits)
|
|
553
|
+
code, grouped by what a supervisor (a container's `restart:` policy, systemd's
|
|
554
|
+
`Restart=`) should do about it. The specific cause is always in the logs and in the
|
|
555
|
+
`stopped: <CAUSE>` line.
|
|
556
|
+
|
|
557
|
+
| Code | Meaning | Supervisor action |
|
|
558
|
+
| --- | --- | --- |
|
|
559
|
+
| `0` | Intentional stop (Ctrl-C, `closedown_at`, Gateway/TWS closed normally, e.g. File>Close, or this session yielded to another via `existing_session_action`: `stopped: SESSION_CEDED`) | Do not restart |
|
|
560
|
+
| `1` | Unhandled exception (a bug) | Restart, report |
|
|
561
|
+
| `2` | CLI usage error | Fix the command |
|
|
562
|
+
| `78` (`EX_CONFIG`) | Deployment error (bad config, install not found) | Do not retry |
|
|
563
|
+
| `77` (`EX_NOPERM`) | Credentials rejected | Do not retry (lockout risk) |
|
|
564
|
+
| `76` (`EX_PROTOCOL`) | A dialog `existing_session_action` rules out (Trading Login Handoff with `secondary`/`primaryoverride`) | Do not retry |
|
|
565
|
+
| `75` (`EX_TEMPFAIL`) | Login did not complete after 2FA, `mfa_timeout_action=exit` | Retry per policy |
|
|
566
|
+
| `70` (`EX_SOFTWARE`) | `restart_cap` consecutive relaunches never reached READY | Do not retry |
|
|
567
|
+
| `69` (`EX_UNAVAILABLE`) | Transient runtime failure (process crashed or killed, connection lost, JVM exited before becoming ready) | Retry |
|
|
568
|
+
|
|
569
|
+
`70`/`76`/`77`/`78` work with systemd's `RestartPreventExitStatus=`.
|
|
570
|
+
|
|
571
|
+
Some login-blocking dialogs IB reports as a server-side condition -- not a credential
|
|
572
|
+
rejection -- never reach this table at all: a "Login failed"/"Login Error"/"not currently available" dialog, or a
|
|
573
|
+
Gateway "Connection to server failed" reason other than a known credential/account one
|
|
574
|
+
(e.g. a stale restart token), make ibcontroller relaunch in-process with a full fresh
|
|
575
|
+
login, the same way a scheduled `cold_restart_time` does -- the container
|
|
576
|
+
itself does not exit for these, until `restart_cap` consecutive relaunches never reach READY
|
|
577
|
+
(exit 70).
|
|
578
|
+
|
|
579
|
+
## Stopping under Docker
|
|
580
|
+
|
|
581
|
+
On SIGTERM (`docker stop`, Ctrl-C) a logged-in instance closes through File > Exit/Close so
|
|
582
|
+
TWS/Gateway can save its session and upload settings. Measured over 88 shutdowns: mean 1.3 s,
|
|
583
|
+
p99 4.3 s, max 4.9 s. A slow path (a dialog open, a stuck menu) can take longer: the menu retry
|
|
584
|
+
waits up to 5 s, the exit up to 8 s, then SIGTERM gets 5 s. Docker sends SIGKILL after 10 s by
|
|
585
|
+
default, which loses the graceful close. Set `stop_grace_period: 30s` (Compose) or
|
|
586
|
+
`docker stop -t 30` to keep it.
|
|
587
|
+
|
|
465
588
|
## License
|
|
466
589
|
|
|
467
590
|
```text
|