├── .env ├── requirements-test.txt ├── .gitignore ├── healthcheck ├── .gitignore ├── gradle │ ├── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ └── libs.versions.toml ├── gradle.properties ├── .gitattributes ├── healthcheck │ ├── src │ │ ├── test │ │ │ └── kotlin │ │ │ │ └── com │ │ │ │ └── manhinhang │ │ │ │ └── ibgatewaydocker │ │ │ │ └── healthcheck │ │ │ │ └── AppTest.kt │ │ └── main │ │ │ └── kotlin │ │ │ └── com │ │ │ └── manhinhang │ │ │ └── ibgatewaydocker │ │ │ └── healthcheck │ │ │ ├── App.kt │ │ │ ├── IBGatewayClient.kt │ │ │ └── Wrapper.kt │ └── build.gradle.kts ├── healthcheck-rest │ ├── src │ │ └── main │ │ │ └── kotlin │ │ │ └── com │ │ │ └── manhinhang │ │ │ └── ibgatewaydocker │ │ │ └── healthcheck │ │ │ └── rest │ │ │ ├── Main.kt │ │ │ └── HttpControllers.kt │ └── build.gradle.kts ├── settings.gradle.kts ├── gradlew.bat └── gradlew ├── .dockerignore ├── scripts ├── extract_ib_gateway_major_minor.sh ├── detect_ib_gateway_ver.py └── detect_ibc_ver.py ├── .envrc ├── .gitattributes ├── docker-compose.yaml ├── examples └── ib_insync │ ├── scripts │ └── connect_gateway.py │ └── README.md ├── .devcontainer ├── Dockerfile └── devcontainer.json ├── doc └── Debugging.md ├── LICENSE ├── .github └── workflows │ ├── deploy.yml │ ├── build-test.yml │ └── detect-new-ver.yml ├── start.sh ├── test ├── test_ib_gateway_fail.py ├── test_ib_gateway.py └── test_docker_interactive.py ├── Dockerfile.template ├── Dockerfile ├── README.md ├── README.template ├── CLAUDE.md └── ibc └── config.ini /.env: -------------------------------------------------------------------------------- 1 | CUR_IB_GATEWAY_VER=10.37.1n 2 | CUR_IBC_VER=3.23.0 3 | -------------------------------------------------------------------------------- /requirements-test.txt: -------------------------------------------------------------------------------- 1 | pytest-testinfra 2 | ib_insync 3 | requests 4 | pandas 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .secrets 3 | .env 4 | .vscode 5 | healthcheck/healthcheck/src/main/java 6 | -------------------------------------------------------------------------------- /healthcheck/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Gradle project-specific cache directory 2 | .gradle 3 | 4 | # Ignore Gradle build output directory 5 | build 6 | -------------------------------------------------------------------------------- /healthcheck/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manhinhang/ib-gateway-docker/HEAD/healthcheck/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/__pycache__/ 2 | **/*.pyc 3 | **/*.pyo 4 | **/*.pyd 5 | .github 6 | test 7 | requirements-test.txt 8 | .gitignore 9 | Dockerfile 10 | README.md -------------------------------------------------------------------------------- /scripts/extract_ib_gateway_major_minor.sh: -------------------------------------------------------------------------------- 1 | if [[ $1 =~ ([0-9]+)\.([0-9]+) ]]; then 2 | IB_GATEWAY_MAJOR=${BASH_REMATCH[1]} 3 | IB_GATEWAY_MINOR=${BASH_REMATCH[2]} 4 | fi 5 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | export IMAGE_NAME=ib-gateway-docker 2 | export TRADING_MODE=paper 3 | if [ -f ".secrets" ] ; then 4 | source ./.secrets 5 | fi 6 | 7 | if [ -d ".venv" ] ; then 8 | source ./.venv/bin/activate 9 | unset PS1 10 | fi 11 | -------------------------------------------------------------------------------- /healthcheck/gradle.properties: -------------------------------------------------------------------------------- 1 | # This file was generated by the Gradle 'init' task. 2 | # https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties 3 | 4 | org.gradle.parallel=true 5 | org.gradle.caching=true 6 | 7 | -------------------------------------------------------------------------------- /healthcheck/.gitattributes: -------------------------------------------------------------------------------- 1 | # 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | # 4 | # Linux start script should use lf 5 | /gradlew text eol=lf 6 | 7 | # These are Windows script files and should use crlf 8 | *.bat text eol=crlf 9 | 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | # 4 | # Linux start script should use lf 5 | *.sh text eol=lf 6 | *.exp text eol=lf 7 | 8 | # These are Windows script files and should use crlf 9 | *.bat text eol=crlf 10 | 11 | -------------------------------------------------------------------------------- /healthcheck/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /healthcheck/healthcheck/src/test/kotlin/com/manhinhang/ibgatewaydocker/healthcheck/AppTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file was generated by the Gradle 'init' task 3 | */ 4 | package com.manhinhang.ibgatewaydocker.healthcheck 5 | 6 | import kotlin.test.Test 7 | import kotlin.test.assertNotNull 8 | 9 | class AppTest { 10 | @Test fun appHasAGreeting() { 11 | 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /healthcheck/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | # This file was generated by the Gradle 'init' task. 2 | # https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format 3 | 4 | [versions] 5 | guava = "32.1.3-jre" 6 | 7 | [libraries] 8 | guava = { module = "com.google.guava:guava", version.ref = "guava" } 9 | 10 | [plugins] 11 | jvm = { id = "org.jetbrains.kotlin.jvm", version = "1.9.22" } 12 | -------------------------------------------------------------------------------- /healthcheck/healthcheck-rest/src/main/kotlin/com/manhinhang/ibgatewaydocker/healthcheck/rest/Main.kt: -------------------------------------------------------------------------------- 1 | package com.manhinhang.ibgatewaydocker.healthcheck.rest 2 | 3 | import org.springframework.boot.autoconfigure.SpringBootApplication 4 | import org.springframework.boot.runApplication 5 | 6 | @SpringBootApplication 7 | class RestApplication 8 | 9 | fun main(args: Array) { 10 | runApplication(*args) 11 | } -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | ib-gateway: 3 | build: 4 | context: . 5 | ports: 6 | - 4002:4002 7 | environment: 8 | - IB_ACCOUNT=$IB_ACCOUNT 9 | - IB_PASSWORD=$IB_PASSWORD 10 | - TRADING_MODE=$TRADING_MODE 11 | healthcheck: 12 | test: /healthcheck/bin/healthcheck 13 | interval: 60s 14 | timeout: 30s 15 | retries: 3 16 | start_period: 60s 17 | 18 | -------------------------------------------------------------------------------- /healthcheck/healthcheck/src/main/kotlin/com/manhinhang/ibgatewaydocker/healthcheck/App.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file was generated by the Gradle 'init' task 3 | */ 4 | package com.manhinhang.ibgatewaydocker.healthcheck 5 | import kotlinx.coroutines.* 6 | 7 | fun main() = runBlocking { 8 | val client = IBGatewayClient() 9 | val result = client.ping() 10 | result.exceptionOrNull()?.let { throw it } 11 | return@runBlocking 12 | } -------------------------------------------------------------------------------- /examples/ib_insync/scripts/connect_gateway.py: -------------------------------------------------------------------------------- 1 | from ib_insync import IB, util, Forex 2 | 3 | if __name__ == "__main__": 4 | ib = IB() 5 | ib.connect('localhost', 4001, clientId=999) 6 | contract = Forex('EURUSD') 7 | bars = ib.reqHistoricalData( 8 | contract, endDateTime='', durationStr='30 D', 9 | barSizeSetting='1 hour', whatToShow='MIDPOINT', useRTH=True) 10 | 11 | # convert to pandas dataframe: 12 | df = util.df(bars) 13 | print(df) 14 | -------------------------------------------------------------------------------- /scripts/detect_ib_gateway_ver.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import re 4 | 5 | if __name__ == "__main__": 6 | url = "https://download2.interactivebrokers.com/installers/ibgateway/stable-standalone/version.json" 7 | regex = r"([^(]+)\)" 8 | response = requests.get(url) 9 | response_text = response.text 10 | matches = re.finditer(regex, response_text) 11 | # print(matches) 12 | json_str = next(matches).group(1) 13 | data = json.loads(json_str) 14 | print(data["buildVersion"]) 15 | -------------------------------------------------------------------------------- /scripts/detect_ibc_ver.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import os 3 | 4 | if __name__ == "__main__": 5 | url = "https://api.github.com/repos/IbcAlpha/IBC/releases" 6 | response = requests.get(url) 7 | data = response.json() 8 | latest = data[0] 9 | ver = latest["name"] 10 | for asset in latest["assets"]: 11 | if asset["name"].startswith("IBCLinux"): 12 | asset_url = asset["browser_download_url"] 13 | 14 | with open('.env', 'a') as fp: 15 | fp.write(f'IBC_VER={ver}\n') 16 | fp.write(f'IBC_ASSET_URL={asset_url}\n') 17 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/ubuntu/.devcontainer/base.Dockerfile 2 | 3 | # [Choice] Ubuntu version (use ubuntu-22.04 or ubuntu-18.04 on local arm64/Apple Silicon): ubuntu-22.04, ubuntu-20.04, ubuntu-18.04 4 | ARG VARIANT="jammy" 5 | FROM mcr.microsoft.com/vscode/devcontainers/base:${VARIANT} 6 | 7 | # [Optional] Uncomment this section to install additional OS packages. 8 | # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 9 | # && apt-get -y install --no-install-recommends 10 | -------------------------------------------------------------------------------- /healthcheck/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.7/userguide/multi_project_builds.html in the Gradle documentation. 6 | * This project uses @Incubating APIs which are subject to change. 7 | */ 8 | 9 | plugins { 10 | // Apply the foojay-resolver plugin to allow automatic download of JDKs 11 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" 12 | } 13 | 14 | rootProject.name = "healthcheck" 15 | include("healthcheck") 16 | include("healthcheck-rest") 17 | -------------------------------------------------------------------------------- /doc/Debugging.md: -------------------------------------------------------------------------------- 1 | # Debugging 2 | 3 | For debugging, Use x11 forwarding to visit IB gateway GUI for the investigation. 4 | 5 | ## Debugging in Mac OSX 6 | 7 | - install xquartz 8 | 9 | ``` 10 | brew install --cask xquartz. 11 | ``` 12 | 13 | - In *XQuartz* -> Prefences ->Security 14 | 15 | Turn on `Allow connections from network clients` 16 | 17 | - Run Docker with mounting `.Xauthority` and point DISPLAY environment variable with ip address 18 | 19 | Example: 20 | 21 | ``` 22 | docker run --platform linux/amd64 -d \ 23 | --env IB_ACCOUNT= \ 24 | --env IB_PASSWORD= \ 25 | --env TRADING_MODE= \ 26 | -v ~/.Xauthority:/root/.Xauthority \ 27 | -e DISPLAY=$ip:0 \ 28 | -p 4002:4002 \ 29 | ib-gateway-docker 30 | ``` 31 | -------------------------------------------------------------------------------- /examples/ib_insync/README.md: -------------------------------------------------------------------------------- 1 | # Example for starting up & connect IB Gateway 2 | 3 | This example showing how to using [ib_insync](https://github.com/erdewit/ib_insync) library to connect `IB Gateway` 4 | 5 | Python script 6 | 7 | | File | Description | 8 | | - | - | 9 | | [connect_gateway.py](scripts/connect_gateway.py) | connect `IB Gateway` and retrieve historical data | 10 | 11 | ## Docker run command 12 | ```bash 13 | export TRADING_MODE=#paper or live 14 | export IB_ACCOUNT=# your interactive brokers account name 15 | export IB_PASSWORD=# your interactive brokers account password 16 | 17 | docker run --rm \ 18 | -e IB_ACCOUNT=$IB_ACCOUNT \ 19 | -e IB_PASSWORD=$IB_PASSWORD \ 20 | -e TRADING_MODE=$TRADING_MODE \ 21 | -p 4001:4002 \ 22 | -d \ 23 | manhinhang/ib-gateway-docker:latest 24 | 25 | pip install ib_insync pandas 26 | python ib_insync/scripts/connect_gateway.py 27 | ``` -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020, manhinhang 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | -------------------------------------------------------------------------------- /healthcheck/healthcheck-rest/src/main/kotlin/com/manhinhang/ibgatewaydocker/healthcheck/rest/HttpControllers.kt: -------------------------------------------------------------------------------- 1 | package com.manhinhang.ibgatewaydocker.healthcheck.rest 2 | import com.manhinhang.ibgatewaydocker.healthcheck.IBGatewayClient 3 | import org.springframework.http.HttpStatus 4 | import org.springframework.http.ResponseEntity 5 | import org.springframework.web.bind.annotation.* 6 | import kotlinx.coroutines.* 7 | 8 | @RestController 9 | @RequestMapping("/") 10 | class HealthcheckApiController() { 11 | 12 | val ibClient = IBGatewayClient() 13 | 14 | @GetMapping("/ready") 15 | fun ready(): ResponseEntity { 16 | return ResponseEntity.status(HttpStatus.OK).body("OK"); 17 | } 18 | 19 | @GetMapping("/healthcheck") 20 | fun healthcheck(): ResponseEntity { 21 | val result = runBlocking { ibClient.ping() } 22 | if (result.isSuccess) { 23 | return ResponseEntity.status(HttpStatus.OK).body("OK") 24 | } 25 | return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Fail") 26 | } 27 | } -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/ubuntu 3 | { 4 | "name": "Ubuntu", 5 | "build": { 6 | "dockerfile": "Dockerfile", 7 | // Update 'VARIANT' to pick an Ubuntu version: jammy / ubuntu-22.04, focal / ubuntu-20.04, bionic /ubuntu-18.04 8 | // Use ubuntu-22.04 or ubuntu-18.04 on local arm64/Apple Silicon. 9 | "args": { "VARIANT": "ubuntu-22.04" } 10 | }, 11 | 12 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 13 | // "forwardPorts": [], 14 | "extensions": [ 15 | "esbenp.prettier-vscode", 16 | "yzhang.markdown-all-in-one", 17 | "aaron-bond.better-comments", 18 | "ms-azuretools.vscode-docker", 19 | "ms-python.python", 20 | "streetsidesoftware.code-spell-checker" 21 | ], 22 | 23 | // Use 'postCreateCommand' to run commands after the container is created. 24 | "postCreateCommand": "pip3 install --user -r requirements-test.txt", 25 | 26 | // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. 27 | "remoteUser": "vscode", 28 | "features": { 29 | "docker-from-docker": "latest", 30 | "python": "3.9" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /healthcheck/healthcheck/src/main/kotlin/com/manhinhang/ibgatewaydocker/healthcheck/IBGatewayClient.kt: -------------------------------------------------------------------------------- 1 | package com.manhinhang.ibgatewaydocker.healthcheck 2 | 3 | import com.ib.client.EClientSocket 4 | import com.ib.client.EJavaSignal 5 | import kotlinx.coroutines.* 6 | 7 | class IBGatewayClient { 8 | val client: EClientSocket 9 | companion object { 10 | val clientId = (System.getenv("HEALTHCHECK_CLIENT_ID")?.toIntOrNull() ?: 999) 11 | val port = (System.getenv("IB_GATEWAY_INTERNAL_PORT")?.toIntOrNull() ?: 4001) 12 | val host = "localhost" 13 | } 14 | 15 | init { 16 | client = createIBClient() 17 | } 18 | 19 | private fun createIBClient(): EClientSocket { 20 | val signal = EJavaSignal(); 21 | val client = EClientSocket(Wrapper(), signal) 22 | return client 23 | } 24 | 25 | private suspend fun connect():Boolean = withContext(Dispatchers.IO) { 26 | if (!client.isConnected) { 27 | client.eConnect(host, port, clientId) 28 | } 29 | client.isConnected 30 | } 31 | 32 | private fun disconnect() { 33 | client.eDisconnect() 34 | } 35 | 36 | suspend fun ping():Result = coroutineScope { 37 | runCatching { 38 | val isConnected = connect() 39 | if (isConnected) { 40 | println("Ping IB Gateway successful") 41 | disconnect() 42 | }else { 43 | throw InterruptedException("Can not connect to IB Gateway") 44 | } 45 | } 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: "Publish Docker" 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | - '!feature/**' 8 | - '!hotfix/**' 9 | - '!bugfix/**' 10 | tags: 11 | - '*' 12 | workflow_dispatch: 13 | 14 | jobs: 15 | build: 16 | strategy: 17 | matrix: 18 | include: 19 | # Dockerhub 20 | - images: manhinhang/ib-gateway-docker 21 | username: DOCKERHUB_USERNAME 22 | password: DOCKERHUB_PASSWORD 23 | registry: '' 24 | # Github 25 | - images: ghcr.io/manhinhang/ib-gateway-docker 26 | username: ${{ github.actor }} 27 | password: GITHUB_TOKEN 28 | registry: 'ghcr.io' 29 | runs-on: ubuntu-latest 30 | timeout-minutes: 20 31 | steps: 32 | - uses: actions/checkout@master 33 | - name: Docker meta 34 | id: meta 35 | uses: docker/metadata-action@v5 36 | with: 37 | images: ${{ matrix.images }} 38 | - name: Login to Docker Hub 39 | uses: docker/login-action@v3 40 | with: 41 | registry: ${{ matrix.registry }} 42 | username: ${{ matrix.registry == 'ghcr.io' && matrix.username || secrets[matrix.username] }} 43 | password: ${{ secrets[matrix.password] }} 44 | - name: Build and push 45 | uses: docker/build-push-action@v5 46 | with: 47 | context: . 48 | push: ${{ github.event_name != 'pull_request' }} 49 | tags: ${{ steps.meta.outputs.tags }} 50 | labels: ${{ steps.meta.outputs.labels }} 51 | -------------------------------------------------------------------------------- /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | name: Build test 2 | on: 3 | push: 4 | branches: 5 | - develop 6 | - master 7 | paths-ignore: 8 | - 'README.md' 9 | - 'LICENSE' 10 | pull_request: 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | if: github.event.pull_request.draft == false 16 | timeout-minutes: 20 17 | env: 18 | IMAGE_NAME: ib-gateway-docker 19 | steps: 20 | - uses: actions/checkout@master 21 | - name: Setup python 22 | uses: actions/setup-python@v5 23 | with: 24 | python-version: '3.11' 25 | - name: Install dependencies 26 | run: | 27 | python -m pip install --upgrade pip 28 | pip install pytest 29 | if [ -f requirements-test.txt ]; then pip install -r requirements-test.txt; fi 30 | - name: Build Docker image 31 | uses: docker/build-push-action@v5 32 | with: 33 | context: . 34 | push: false 35 | tags: ${{ env.IMAGE_NAME }} 36 | - name: Smoke tests container image 37 | run: pytest -x 38 | env: 39 | IB_ACCOUNT: ${{ secrets.IB_ACCOUNT }} 40 | IB_PASSWORD: ${{ secrets.IB_PASSWORD }} 41 | TRADING_MODE: paper 42 | 43 | - name: Run ib_insync example 44 | run: | 45 | docker run --rm \ 46 | -e IB_ACCOUNT=$IB_ACCOUNT \ 47 | -e IB_PASSWORD=$IB_PASSWORD \ 48 | -e TRADING_MODE=paper \ 49 | -p 4001:4002 \ 50 | -d \ 51 | $IMAGE_NAME; 52 | sleep 30; 53 | pip install ib_insync pandas; 54 | python examples/ib_insync/scripts/connect_gateway.py; 55 | docker stop $(docker ps -a -q) 56 | env: 57 | IB_ACCOUNT: ${{ secrets.IB_ACCOUNT }} 58 | IB_PASSWORD: ${{ secrets.IB_PASSWORD }} 59 | TRADING_MODE: paper 60 | 61 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | echo "Starting Xvfb..." 5 | rm -f /tmp/.X0-lock 6 | /usr/bin/Xvfb "$DISPLAY" -ac -screen 0 1024x768x16 +extension RANDR >&1 & 7 | 8 | echo "Waiting for Xvfb to be ready..." 9 | XVFB_TIMEOUT=120 10 | XVFB_WAITING_TIME=0 11 | while ! xdpyinfo -display "$DISPLAY"; do 12 | echo -n '' 13 | sleep 1 14 | XVFB_WAITING_TIME=$(($XVFB_WAITING_TIME+1)) 15 | echo "WAITING TIME: $XVFB_WAITING_TIME" 16 | if [ "$XVFB_WAITING_TIME" -gt "$XVFB_TIMEOUT" ]; then 17 | echo "Xvfb TIMED OUT" 18 | exit 1 19 | fi 20 | done 21 | 22 | echo "Xvfb is ready" 23 | echo "Setup port forwarding..." 24 | 25 | socat TCP-LISTEN:$IBGW_PORT,fork,reuseaddr,keepalive,keepidle=30,keepintvl=10 TCP:localhost:4001,forever >&1 & 26 | echo "*****************************" 27 | 28 | # python /root/bootstrap.py 29 | 30 | # echo "IB gateway is ready." 31 | 32 | #Define cleanup procedure 33 | cleanup() { 34 | pkill java 35 | pkill Xvfb 36 | pkill socat 37 | echo "Container stopped, performing cleanup..." 38 | } 39 | 40 | #Trap TERM 41 | trap 'cleanup' INT TERM 42 | echo "IB gateway starting..." 43 | IB_GATEWAY_VERSION=$(ls $TWS_PATH/ibgateway) 44 | 45 | set_java_heap() { 46 | # set java heap size in vm options 47 | if [ -n "${JAVA_HEAP_SIZE}" ]; then 48 | _vmpath="${TWS_PATH}/ibgateway/${IB_GATEWAY_VERSION}" 49 | _string="s/-Xmx([0-9]+)m/-Xmx${JAVA_HEAP_SIZE}m/g" 50 | sed -i -E "${_string}" "${_vmpath}/ibgateway.vmoptions" 51 | echo "Java heap size set to ${JAVA_HEAP_SIZE}m" 52 | else 53 | echo "Usign default Java heap size." 54 | fi 55 | } 56 | 57 | # Java heap size 58 | set_java_heap 59 | 60 | # start rest api for healthcheck 61 | if [ "$HEALTHCHECK_API_ENABLE" = true ] ; then 62 | echo "starting healthcheck api..." 63 | healthcheck-rest >&1 & 64 | else 65 | echo "Skip starting healthcheck api" 66 | fi 67 | 68 | echo "detect IB gateway version: $IBGW_VERSION" 69 | 70 | ${IBC_PATH}/scripts/ibcstart.sh "$IB_GATEWAY_VERSION" -g \ 71 | "--ibc-path=${IBC_PATH}" "--ibc-ini=${IBC_INI}" \ 72 | "--user=${IB_ACCOUNT}" "--pw=${IB_PASSWORD}" "--mode=${TRADING_MODE}" \ 73 | "--on2fatimeout=${TWOFA_TIMEOUT_ACTION}" 74 | -------------------------------------------------------------------------------- /test/test_ib_gateway_fail.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import subprocess 3 | import testinfra 4 | import os 5 | import time 6 | import requests 7 | from ib_insync import IB, util, Forex 8 | 9 | IMAGE_NAME = os.environ['IMAGE_NAME'] 10 | 11 | # scope='session' uses the same container for all the tests; 12 | # scope='function' uses a new container per test function. 13 | @pytest.fixture(scope='session') 14 | def host(request): 15 | account = 'test' 16 | password = 'test' 17 | trading_mode = 'paper' 18 | 19 | # run a container 20 | docker_id = subprocess.check_output( 21 | ['docker', 'run', 22 | '--env', 'IB_ACCOUNT={}'.format(account), 23 | '--env', 'IB_PASSWORD={}'.format(password), 24 | '--env', 'TRADING_MODE={}'.format(trading_mode), 25 | '-p', '8080:8080', 26 | '-d', IMAGE_NAME]).decode().strip() 27 | 28 | # at the end of the test suite, destroy the container 29 | def remove_container(): 30 | subprocess.check_call(['docker', 'rm', '-f', docker_id]) 31 | request.addfinalizer(remove_container) 32 | 33 | # return a testinfra connection to the container 34 | yield testinfra.get_host("docker://" + docker_id) 35 | 36 | 37 | def test_ib_insync_connect_fail(host): 38 | try: 39 | ib = IB() 40 | wait = 60 41 | while not ib.isConnected(): 42 | try: 43 | IB.sleep(1) 44 | ib.connect('localhost', 4002, clientId=999) 45 | except: 46 | pass 47 | wait -= 1 48 | if wait <= 0: 49 | break 50 | 51 | contract = Forex('EURUSD') 52 | bars = ib.reqHistoricalData( 53 | contract, endDateTime='', durationStr='30 D', 54 | barSizeSetting='1 hour', whatToShow='MIDPOINT', useRTH=True) 55 | assert False 56 | except: 57 | pass 58 | 59 | def test_healthcheck_fail(host): 60 | time.sleep(30) 61 | assert host.exists("healthcheck") 62 | assert host.run('/healthcheck/bin/healthcheck').rc == 1 63 | 64 | def test_healthcheck_rest_fail(host): 65 | time.sleep(30) 66 | try: 67 | response = requests.get("http://127.0.0.1:8080/healthcheck") 68 | assert False 69 | except requests.exceptions.ConnectionError: 70 | pass 71 | -------------------------------------------------------------------------------- /healthcheck/healthcheck-rest/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * This generated file contains a sample Kotlin application project to get you started. 5 | * For more details on building Java & JVM projects, please refer to https://docs.gradle.org/8.7/userguide/building_java_projects.html in the Gradle documentation. 6 | * This project uses @Incubating APIs which are subject to change. 7 | */ 8 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 9 | 10 | plugins { 11 | // Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin. 12 | alias(libs.plugins.jvm) 13 | id("org.springframework.boot") version "3.2.2" 14 | id("io.spring.dependency-management") version "1.1.4" 15 | kotlin("plugin.spring") version "1.9.22" 16 | kotlin("plugin.jpa") version "1.9.22" 17 | // Apply the application plugin to add support for building a CLI application in Java. 18 | application 19 | } 20 | 21 | repositories { 22 | // Use Maven Central for resolving dependencies. 23 | mavenCentral() 24 | } 25 | 26 | dependencies { 27 | // This dependency is used by the application. 28 | implementation(libs.guava) 29 | implementation(platform("org.springframework.boot:spring-boot-dependencies:3.2.2")) 30 | implementation("org.springframework.boot:spring-boot-starter") 31 | implementation("org.springframework.boot:spring-boot-starter-web") 32 | // implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") 33 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1-Beta") 34 | implementation(project(":healthcheck")) 35 | } 36 | 37 | tasks.withType { 38 | kotlinOptions { 39 | freeCompilerArgs = listOf("-Xjsr305=strict") 40 | jvmTarget = "17" 41 | } 42 | } 43 | 44 | testing { 45 | suites { 46 | // Configure the built-in test suite 47 | val test by getting(JvmTestSuite::class) { 48 | // Use Kotlin Test test framework 49 | useKotlinTest("1.9.22") 50 | } 51 | } 52 | } 53 | 54 | // Apply a specific Java toolchain to ease working on different environments. 55 | java { 56 | toolchain { 57 | languageVersion = JavaLanguageVersion.of(17) 58 | } 59 | } 60 | 61 | application { 62 | // Define the main class for the application. 63 | mainClass = "com.manhinhang.ibgatewaydocker.healthcheck.rest.MainKt" 64 | } 65 | -------------------------------------------------------------------------------- /test/test_ib_gateway.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import subprocess 3 | import testinfra 4 | import os 5 | import time 6 | import requests 7 | 8 | IMAGE_NAME = os.environ['IMAGE_NAME'] 9 | 10 | def test_healthcheck(): 11 | account = os.environ['IB_ACCOUNT'] 12 | password = os.environ['IB_PASSWORD'] 13 | trading_mode = os.environ['TRADING_MODE'] 14 | 15 | # run a container 16 | docker_id = subprocess.check_output( 17 | ['docker', 'run', 18 | '--env', 'IB_ACCOUNT={}'.format(account), 19 | '--env', 'IB_PASSWORD={}'.format(password), 20 | '--env', 'TRADING_MODE={}'.format(trading_mode), 21 | '-d', IMAGE_NAME]).decode().strip() 22 | time.sleep(30) 23 | assert subprocess.check_call(['docker', 'exec', docker_id, 'healthcheck']) == 0 24 | subprocess.check_call(['docker', 'rm', '-f', docker_id]) 25 | 26 | def test_healthcheck_api(): 27 | account = os.environ['IB_ACCOUNT'] 28 | password = os.environ['IB_PASSWORD'] 29 | trading_mode = os.environ['TRADING_MODE'] 30 | 31 | # run a container 32 | docker_id = subprocess.check_output( 33 | ['docker', 'run', 34 | '--env', 'IB_ACCOUNT={}'.format(account), 35 | '--env', 'IB_PASSWORD={}'.format(password), 36 | '--env', 'TRADING_MODE={}'.format(trading_mode), 37 | '--env', 'HEALTHCHECK_API_ENABLE=true', 38 | '-p', '8080:8080', 39 | '-d', IMAGE_NAME]).decode().strip() 40 | time.sleep(30) 41 | response = requests.get("http://127.0.0.1:8080/healthcheck") 42 | assert response.ok 43 | subprocess.check_call(['docker', 'rm', '-f', docker_id]) 44 | 45 | def test_healthcheck_api_fail(): 46 | account = 'test' 47 | password = 'test' 48 | trading_mode = os.environ['TRADING_MODE'] 49 | 50 | # run a container 51 | docker_id = subprocess.check_output( 52 | ['docker', 'run', 53 | '--env', 'IB_ACCOUNT={}'.format(account), 54 | '--env', 'IB_PASSWORD={}'.format(password), 55 | '--env', 'TRADING_MODE={}'.format(trading_mode), 56 | '--env', 'HEALTHCHECK_API_ENABLE=true', 57 | '-p', '8080:8080', 58 | '-d', IMAGE_NAME]).decode().strip() 59 | time.sleep(30) 60 | try: 61 | response = requests.get("http://127.0.0.1:8080/healthcheck") 62 | assert not response.ok 63 | except requests.exceptions.ConnectionError: 64 | pass 65 | subprocess.check_call(['docker', 'rm', '-f', docker_id]) 66 | -------------------------------------------------------------------------------- /test/test_docker_interactive.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | import subprocess 4 | import time 5 | from ib_insync import IB, util, Forex 6 | import asyncio 7 | 8 | IMAGE_NAME = os.environ['IMAGE_NAME'] 9 | 10 | @pytest.fixture(scope='function') 11 | def ib_docker(request): 12 | account = os.environ['IB_ACCOUNT'] 13 | password = os.environ['IB_PASSWORD'] 14 | trading_mode = os.environ['TRADING_MODE'] 15 | 16 | # run a container 17 | docker_id = subprocess.check_output( 18 | ['docker', 'run', 19 | '--env', 'IB_ACCOUNT={}'.format(account), 20 | '--env', 'IB_PASSWORD={}'.format(password), 21 | '--env', 'TRADING_MODE={}'.format(trading_mode), 22 | '-p', '4002:4002', 23 | '-d', IMAGE_NAME]).decode().strip() 24 | 25 | # at the end of the test suite, destroy the container 26 | def remove_container(): 27 | subprocess.check_call(['docker', 'rm', '-f', docker_id]) 28 | request.addfinalizer(remove_container) 29 | yield docker_id 30 | 31 | 32 | def test_ibgw_interactive(ib_docker): 33 | ib = IB() 34 | wait = 120 35 | while not ib.isConnected(): 36 | try: 37 | IB.sleep(1) 38 | ib.connect('localhost', 4002, clientId=999) 39 | except: 40 | pass 41 | wait -= 1 42 | if wait <= 0: 43 | break 44 | 45 | contract = Forex('EURUSD') 46 | bars = ib.reqHistoricalData( 47 | contract, endDateTime='', durationStr='30 D', 48 | barSizeSetting='1 hour', whatToShow='MIDPOINT', useRTH=True) 49 | 50 | # convert to pandas dataframe: 51 | df = util.df(bars) 52 | print(df) 53 | 54 | def test_ibgw_restart(ib_docker): 55 | 56 | subprocess.check_output( 57 | ['docker', 'container', 'stop', ib_docker]).decode().strip() 58 | subprocess.check_output( 59 | ['docker', 'container', 'start', ib_docker]).decode().strip() 60 | 61 | ib = IB() 62 | wait = 60 63 | while not ib.isConnected(): 64 | try: 65 | IB.sleep(1) 66 | ib.connect('localhost', 4002, clientId=999) 67 | except: 68 | pass 69 | wait -= 1 70 | if wait <= 0: 71 | break 72 | 73 | contract = Forex('EURUSD') 74 | bars = ib.reqHistoricalData( 75 | contract, endDateTime='', durationStr='30 D', 76 | barSizeSetting='1 hour', whatToShow='MIDPOINT', useRTH=True) 77 | 78 | # convert to pandas dataframe: 79 | df = util.df(bars) 80 | print(df) 81 | -------------------------------------------------------------------------------- /healthcheck/healthcheck/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.gradle.wrapper.Download 2 | 3 | /* 4 | * This file was generated by the Gradle 'init' task. 5 | * 6 | * This generated file contains a sample Kotlin application project to get you started. 7 | * For more details on building Java & JVM projects, please refer to https://docs.gradle.org/8.7/userguide/building_java_projects.html in the Gradle documentation. 8 | * This project uses @Incubating APIs which are subject to change. 9 | */ 10 | 11 | plugins { 12 | // Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin. 13 | alias(libs.plugins.jvm) 14 | id("de.undercouch.download") version "5.6.0" 15 | // Apply the application plugin to add support for building a CLI application in Java. 16 | application 17 | } 18 | 19 | repositories { 20 | // Use Maven Central for resolving dependencies. 21 | mavenCentral() 22 | } 23 | 24 | dependencies { 25 | // This dependency is used by the application. 26 | implementation(libs.guava) 27 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1-Beta") 28 | } 29 | 30 | val downloadIbApiTask = tasks.register("downloadIbApi") { 31 | onlyIf("ibapi.zip not found") { 32 | !File("${layout.buildDirectory.asFile.get().path}/ibapi.zip").exists() 33 | } 34 | src("https://interactivebrokers.github.io/downloads/twsapi_macunix.1019.04.zip") 35 | dest("${layout.buildDirectory.asFile.get().path}/ibapi.zip") 36 | } 37 | 38 | val unzipIbApiTask = tasks.register("unzipIbApi") { 39 | from(zipTree("${layout.buildDirectory.asFile.get().path}/ibapi.zip")) { 40 | include("IBJts/source/JavaClient/com/**") 41 | eachFile { 42 | relativePath = RelativePath(true, *relativePath.segments.drop(3).toTypedArray()) 43 | } 44 | includeEmptyDirs = false 45 | } 46 | into("${projectDir}/src/main/java") 47 | } 48 | unzipIbApiTask { dependsOn(downloadIbApiTask) } 49 | tasks.compileKotlin { dependsOn(unzipIbApiTask) } 50 | 51 | testing { 52 | suites { 53 | // Configure the built-in test suite 54 | val test by getting(JvmTestSuite::class) { 55 | // Use Kotlin Test test framework 56 | useKotlinTest("1.9.22") 57 | } 58 | } 59 | } 60 | 61 | // Apply a specific Java toolchain to ease working on different environments. 62 | java { 63 | toolchain { 64 | languageVersion = JavaLanguageVersion.of(17) 65 | } 66 | } 67 | 68 | application { 69 | // Define the main class for the application. 70 | mainClass = "com.manhinhang.ibgatewaydocker.healthcheck.AppKt" 71 | } 72 | -------------------------------------------------------------------------------- /Dockerfile.template: -------------------------------------------------------------------------------- 1 | FROM debian:bookworm-slim as downloader 2 | # IBC Version : https://github.com/IbcAlpha/IBC/releases 3 | ARG IBC_VER="###IBC_VER###" 4 | ARG IBC_ASSET_URL="###IBC_ASSET_URL###" 5 | 6 | # set environment variables 7 | ENV IBC_INI=/root/ibc/config.ini \ 8 | IBC_PATH=/opt/ibc 9 | 10 | # install dependencies 11 | RUN apt-get update \ 12 | && apt-get upgrade -y \ 13 | && apt-get install -y wget \ 14 | unzip 15 | # make dirs 16 | RUN mkdir -p /tmp 17 | 18 | # download IB TWS 19 | RUN wget -q -O /tmp/ibgw.sh https://download2.interactivebrokers.com/installers/ibgateway/stable-standalone/ibgateway-stable-standalone-linux-x64.sh 20 | RUN chmod +x /tmp/ibgw.sh 21 | 22 | # download IBC 23 | RUN wget -q -O /tmp/IBC.zip ${IBC_ASSET_URL} 24 | RUN unzip /tmp/IBC.zip -d ${IBC_PATH} 25 | RUN chmod +x ${IBC_PATH}/*.sh ${IBC_PATH}/*/*.sh 26 | 27 | # copy IBC/Jts configs 28 | COPY ibc/config.ini ${IBC_INI} 29 | 30 | FROM debian:bookworm-slim 31 | ARG IB_GATEWAY_MAJOR="###IB_GATEWAY_MAJOR###" 32 | ARG IB_GATEWAY_MINOR="###IB_GATEWAY_MINOR###" 33 | 34 | 35 | # install dependencies 36 | RUN apt-get update \ 37 | && apt-get upgrade -y \ 38 | && apt-get install -y \ 39 | xvfb \ 40 | libxtst6 \ 41 | libxrender1 \ 42 | net-tools \ 43 | x11-utils \ 44 | socat \ 45 | procps \ 46 | xterm 47 | RUN apt install -y openjdk-17-jre 48 | 49 | # set environment variables 50 | ENV TWS_INSTALL_LOG=/root/Jts/tws_install.log \ 51 | IBC_INI=/root/ibc/config.ini \ 52 | IBC_PATH=/opt/ibc \ 53 | TWS_PATH=/root/Jts \ 54 | TWOFA_TIMEOUT_ACTION=restart \ 55 | IB_GATEWAY_MAJOR=${IB_GATEWAY_MAJOR} \ 56 | IB_GATEWAY_MINOR=${IB_GATEWAY_MINOR} \ 57 | IB_GATEWAY_VERSION=${IB_GATEWAY_MAJOR}${IB_GATEWAY_MINOR} 58 | 59 | # make dirs 60 | RUN mkdir -p /tmp && mkdir -p ${IBC_PATH} && mkdir -p ${TWS_PATH} 61 | 62 | # download IB TWS 63 | COPY --from=downloader /tmp/ibgw.sh /tmp/ibgw.sh 64 | 65 | RUN /tmp/ibgw.sh -q -dir /root/Jts/ibgateway/${IB_GATEWAY_VERSION} 66 | # remove downloaded files 67 | RUN rm /tmp/ibgw.sh 68 | 69 | COPY --from=downloader /opt/ibc /opt/ibc 70 | COPY --from=downloader /root/ibc /root/ibc 71 | 72 | # install healthcheck tool 73 | ADD healthcheck/healthcheck/build/distributions/healthcheck.tar / 74 | ENV PATH="${PATH}:/healthcheck/bin" 75 | 76 | ADD healthcheck/healthcheck-rest/build/distributions/healthcheck-rest-boot.tar / 77 | ENV PATH="${PATH}:/healthcheck-rest-boot/bin" 78 | 79 | # copy cmd script 80 | WORKDIR /root 81 | COPY start.sh /root/start.sh 82 | RUN chmod +x /root/start.sh 83 | 84 | # set display environment variable (must be set after TWS installation) 85 | ENV DISPLAY=:0 86 | 87 | ENV IBGW_PORT 4002 88 | ENV JAVA_HEAP_SIZE 768 89 | 90 | EXPOSE $IBGW_PORT 91 | 92 | ENTRYPOINT [ "sh", "/root/start.sh" ] 93 | -------------------------------------------------------------------------------- /healthcheck/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 1>&2 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 48 | echo. 1>&2 49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 50 | echo location of your Java installation. 1>&2 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 1>&2 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 62 | echo. 1>&2 63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 64 | echo location of your Java installation. 1>&2 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /.github/workflows/detect-new-ver.yml: -------------------------------------------------------------------------------- 1 | name: Detect new version 2 | on: 3 | schedule: 4 | - cron: '0 0 * * *' 5 | workflow_dispatch: 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | pull-requests: write 11 | contents: write 12 | env: 13 | IMAGE_NAME: ib-gateway-docker 14 | IBC_VERSION_JSON_URL: "https://api.github.com/repos/IbcAlpha/IBC/releases" 15 | steps: 16 | - uses: actions/checkout@master 17 | - name: detect new ib gateway version 18 | id: check-update 19 | run: | 20 | IB_GATEWAY_VER=$(curl "https://download2.interactivebrokers.com/installers/ibgateway/stable-standalone/version.json" | \ 21 | grep -Eo '[^ibgatewaystable_callback(](.+})' | jq -r '.buildVersion') 22 | IBC_VER=$(curl ${IBC_VERSION_JSON_URL} | jq -r '.[0].name') 23 | source .env 24 | 25 | HAS_UPDATE='false' 26 | if [ "$IB_GATEWAY_VER" = "$CUR_IB_GATEWAY_VER" ]; then 27 | echo "No new IB gateway version available" 28 | echo has_update=false >> "$GITHUB_OUTPUT" 29 | else 30 | echo "New IB gateway version($IB_GATEWAY_VER)" 31 | echo has_update=true >> "$GITHUB_OUTPUT" 32 | HAS_UPDATE='true' 33 | fi 34 | if [ "$HAS_UPDATE" = 'false' ]; then 35 | if [ "$IBC_VER" = "$CUR_IBC_VER" ]; then 36 | echo "No new IBC version available" 37 | echo has_update=false >> "$GITHUB_OUTPUT" 38 | else 39 | echo "New IBC version($IBC_VER) available" 40 | echo has_update=true >> "$GITHUB_OUTPUT" 41 | fi 42 | fi 43 | 44 | echo "ib-gateway-ver=$IB_GATEWAY_VER" >> "$GITHUB_OUTPUT" 45 | echo "ibc-ver=$IBC_VER" >> "$GITHUB_OUTPUT" 46 | - name: Update files with new version 47 | if: steps.check-update.outputs.has_update == 'true' 48 | run: | 49 | sed -e 's/###IB_GATEWAY_VER###/${{steps.check-update.outputs.ib-gateway-ver}}/' -e 's/###IBC_VER###/${{steps.check-update.outputs.ibc-ver}}/' README.template > README.md 50 | - name: Create PR 51 | if: ${{ steps.check-update.outputs.has_update == 'true' }} 52 | env: 53 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 54 | run: | 55 | branch='feat/update-to-${{ steps.check-update.outputs.ib-gateway-ver }}-ibc${{steps.check-update.outputs.ibc-ver}}' 56 | 57 | # 检查并关闭已存在的PR 58 | existing_pr=$(gh pr list --search "head:$branch" --json number --jq '.[0].number') 59 | if [ ! -z "$existing_pr" ]; then 60 | gh pr close $existing_pr 61 | fi 62 | 63 | # 检查并删除已存在的分支 64 | if git ls-remote --heads origin "$branch" | grep -q "$branch"; then 65 | git push origin --delete "$branch" 66 | fi 67 | 68 | git config user.name github-actions 69 | git config user.email github-actions@github.com 70 | git config advice.addIgnoredFile false 71 | git stash push README.md 72 | git pull 73 | git checkout -b "$branch" origin/master 74 | # Update files 75 | git stash pop stash@{0} 76 | echo "CUR_IB_GATEWAY_VER=${{ steps.check-update.outputs.ib-gateway-ver }}" > .env 77 | echo "CUR_IBC_VER=${{ steps.check-update.outputs.ibc-ver }}" >> .env 78 | ##### 79 | git add README.md 80 | git add -f .env 81 | git commit -m 'Update to `${{ steps.check-update.outputs.ib-gateway-ver }}`' 82 | git push --set-upstream origin "$branch" 83 | 84 | gh pr create --base master --fill 85 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ######## Downloader ######## 2 | FROM debian:bookworm-slim as downloader 3 | 4 | # set environment variables 5 | ENV IBC_VERSION_JSON_URL="https://api.github.com/repos/IbcAlpha/IBC/releases" 6 | ENV IBC_INI=/root/ibc/config.ini \ 7 | IBC_PATH=/opt/ibc 8 | 9 | # install dependencies 10 | RUN apt-get update \ 11 | && apt-get upgrade -y \ 12 | && apt-get install -y wget \ 13 | unzip 14 | RUN apt install -y jq curl 15 | 16 | # make dirs 17 | RUN mkdir -p /tmp 18 | 19 | # download IB TWS 20 | RUN wget -q -O /tmp/ibgw.sh https://download2.interactivebrokers.com/installers/ibgateway/stable-standalone/ibgateway-stable-standalone-linux-x64.sh 21 | RUN chmod +x /tmp/ibgw.sh 22 | 23 | # download IBC 24 | RUN IBC_ASSET_URL=$(curl ${IBC_VERSION_JSON_URL} | jq -r '.[0].assets[]|select(.name | test("IBCLinux*")).browser_download_url') && \ 25 | wget -q -O /tmp/IBC.zip ${IBC_ASSET_URL} 26 | RUN unzip /tmp/IBC.zip -d ${IBC_PATH} 27 | RUN chmod +x ${IBC_PATH}/*.sh ${IBC_PATH}/*/*.sh 28 | 29 | # copy IBC/Jts configs 30 | COPY ibc/config.ini ${IBC_INI} 31 | 32 | # Extract IB Gateway version 33 | RUN curl "https://download2.interactivebrokers.com/installers/ibgateway/stable-standalone/version.json" | \ 34 | grep -Po '[^ibgatewaystable_callback(](.+})' | \ 35 | jq -r .buildVersion > /tmp/ibgw-version 36 | 37 | ######## healthcheck tools ######## 38 | # temp container to build using gradle 39 | FROM gradle:8.7.0-jdk17 AS healthcheck-tools 40 | ENV APP_HOME=/usr/app/ 41 | WORKDIR $APP_HOME 42 | COPY healthcheck $APP_HOME 43 | 44 | RUN gradle clean build 45 | 46 | RUN mkdir -p $APP_HOME/build 47 | 48 | RUN unzip healthcheck/build/distributions/healthcheck.zip -d $APP_HOME/build 49 | RUN unzip healthcheck-rest/build/distributions/healthcheck-rest-boot.zip -d $APP_HOME/build 50 | 51 | ######## FINAL ######## 52 | 53 | FROM debian:bookworm-slim 54 | 55 | # install dependencies 56 | RUN apt-get update \ 57 | && apt-get upgrade -y \ 58 | && apt-get install -y \ 59 | xvfb \ 60 | libxtst6 \ 61 | libxrender1 \ 62 | net-tools \ 63 | x11-utils \ 64 | socat \ 65 | procps \ 66 | xterm 67 | RUN apt install -y openjdk-17-jre 68 | 69 | # set environment variables 70 | ENV TWS_INSTALL_LOG=/root/Jts/tws_install.log \ 71 | IBC_INI=/root/ibc/config.ini \ 72 | IBC_PATH=/opt/ibc \ 73 | TWS_PATH=/root/Jts \ 74 | TWOFA_TIMEOUT_ACTION=restart 75 | 76 | # make dirs 77 | RUN mkdir -p /tmp && mkdir -p ${IBC_PATH} && mkdir -p ${TWS_PATH} && mkdir -p /healthcheck 78 | 79 | # download IB TWS 80 | COPY --from=downloader /tmp/ibgw.sh /tmp/ibgw.sh 81 | COPY --from=downloader /tmp/ibgw-version /tmp/ibgw-version 82 | RUN IB_GATEWAY_VERSION=$(cat /tmp/ibgw-version) && \ 83 | /tmp/ibgw.sh -q -dir /root/Jts/ibgateway/${IB_GATEWAY_VERSION} 84 | # remove files 85 | RUN rm /tmp/ibgw.sh 86 | RUN rm /tmp/ibgw-version 87 | 88 | COPY --from=downloader /opt/ibc /opt/ibc 89 | COPY --from=downloader /root/ibc /root/ibc 90 | 91 | # install healthcheck tool 92 | COPY --from=healthcheck-tools /usr/app/build/healthcheck /healthcheck 93 | ENV PATH="${PATH}:/healthcheck/bin" 94 | 95 | COPY --from=healthcheck-tools /usr/app/build/healthcheck-rest-boot /healthcheck-rest 96 | ENV PATH="${PATH}:/healthcheck-rest/bin" 97 | 98 | # copy cmd script 99 | WORKDIR /root 100 | COPY start.sh /root/start.sh 101 | RUN chmod +x /root/start.sh 102 | 103 | # set display environment variable (must be set after TWS installation) 104 | ENV DISPLAY=:0 105 | 106 | ENV IBGW_PORT 4002 107 | ENV JAVA_HEAP_SIZE 768 108 | ENV HEALTHCHECK_API_ENABLE=false 109 | 110 | EXPOSE $IBGW_PORT 111 | 112 | ENTRYPOINT [ "sh", "/root/start.sh" ] 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IB Gateway docker 2 | 3 | ![Build test](https://github.com/manhinhang/ib-gateway-docker/workflows/Build%20test/badge.svg?branch=master) 4 | [![Docker Pulls](https://img.shields.io/docker/pulls/manhinhang/ib-gateway-docker)](https://hub.docker.com/r/manhinhang/ib-gateway-docker) 5 | [![GitHub](https://img.shields.io/github/license/manhinhang/ib-gateway-docker)](https://github.com/manhinhang/ib-gateway-docker/blob/develop/LICENSE) 6 | 7 | lightweight interactive brokers gateway docker 8 | 9 | It's just pure `IB Gateway` and don't include any VNC service (for security reason, I don't like expose extra port) 10 | 11 | This docker image just installed: 12 | 13 | - [IB Gateway](https://www.interactivebrokers.com/en/index.php?f=16457) (10.37.1n) 14 | 15 | - [IBC](https://github.com/IbcAlpha/IBC) (3.23.0) 16 | 17 | ## Pull the Docker image from Docker Hub 18 | 19 | ```bash 20 | docker pull manhinhang/ib-gateway-docker 21 | ``` 22 | 23 | ### Create a container from the image and run it 24 | ```bash 25 | docker run -d \ 26 | --env IB_ACCOUNT= \ #YOUR_USER_ID 27 | --env IB_PASSWORD= \ #YOUR_PASSWORD 28 | --env TRADING_MODE= \ #paper or live 29 | -p 4002:4002 \ #brige IB gateway port to your local port 4002 30 | manhinhang/ib-gateway-docker 31 | ``` 32 | 33 | --- 34 | 35 | ## Build & Run locally 36 | 37 | ```bash 38 | git clone git@github.com:manhinhang/ib-gateway-docker.git 39 | cd ib-gateway-docker 40 | docker build --no-cache -t ib-gateway-docker . 41 | docker run -d \ 42 | --env IB_ACCOUNT= \ #YOUR_USER_ID 43 | --env IB_PASSWORD= \ #YOUR_PASSWORD 44 | --env TRADING_MODE= \ #paper or live 45 | -p 4002:4002 \ #brige IB gateway port to your local port 4002 46 | ib-gateway-docker 47 | ``` 48 | 49 | 50 | ## Container usage example 51 | 52 | | Example | Link | Description | 53 | | - | - | - | 54 | | ib_insync | [examples/ib_insync](./examples/ib_insync) | This example demonstrated how to connect `IB Gateway` 55 | 56 | 57 | ## Health check container 58 | 59 | ### API 60 | 61 | Healthcheck via api call `http://localhost:8080/healthcheck` 62 | 63 | Config `HEALTHCHECK_API_ENABLE=true` in environment variable to enable API 64 | 65 | ```bash 66 | curl -f http://localhost:8080/healthcheck 67 | ``` 68 | 69 | - Docker compose example 70 | 71 | ```yaml 72 | services: 73 | ib-gateway: 74 | image: manhinhang/ib-gateway-docker 75 | ports: 76 | - 4002:4002 77 | environment: 78 | - IB_ACCOUNT=$IB_ACCOUNT 79 | - IB_PASSWORD=$IB_PASSWORD 80 | - TRADING_MODE=$TRADING_MODE 81 | - HEALTHCHECK_API_ENABLE=true 82 | healthcheck: 83 | test: ["CMD", "curl", "-f", "http://localhost:8080/healthcheck"] 84 | interval: 60s 85 | timeout: 30s 86 | retries: 3 87 | start_period: 60s 88 | ``` 89 | ### CLI 90 | Execute `healthcheck` to detect IB gateway haelth status 91 | 92 | ```bash 93 | healthcheck 94 | # output: Ping IB Gateway successful 95 | echo $? 96 | # output: 0 97 | ``` 98 | 99 | ```bash 100 | healthcheck 101 | # output: Can not connect to IB Gateway 102 | echo $? 103 | # output: 1 104 | ``` 105 | 106 | - Docker compose example 107 | 108 | ```yaml 109 | services: 110 | ib-gateway: 111 | image: manhinhang/ib-gateway-docker 112 | ports: 113 | - 4002:4002 114 | environment: 115 | - IB_ACCOUNT=$IB_ACCOUNT 116 | - IB_PASSWORD=$IB_PASSWORD 117 | - TRADING_MODE=$TRADING_MODE 118 | healthcheck: 119 | test: /healthcheck/bin/healthcheck 120 | interval: 60s 121 | timeout: 30s 122 | retries: 3 123 | start_period: 60s 124 | ``` 125 | 126 | # Tests 127 | 128 | The [test cases](test/test_ib_gateway.py) written with testinfra. 129 | 130 | Run the tests 131 | 132 | ``` 133 | pytest 134 | ``` 135 | 136 | # Github Actions for continuous integration 137 | 138 | After forking `IB Gateway docker` repository, you need config your **interactive brokers** paper account & password in *github secret* 139 | 140 | | Key | Description | 141 | | - | - | 142 | | IB_ACCOUNT | your paper account name | 143 | | IB_PASSWORD | your paper account password | 144 | 145 | # Disclaimer 146 | 147 | This project is not affiliated with [Interactive Brokers Group, Inc.'s](https://www.interactivebrokers.com). 148 | 149 | Good luck and enjoy. 150 | 151 | -------------------------------------------------------------------------------- /README.template: -------------------------------------------------------------------------------- 1 | # IB Gateway docker 2 | 3 | ![Build test](https://github.com/manhinhang/ib-gateway-docker/workflows/Build%20test/badge.svg?branch=master) 4 | [![Docker Pulls](https://img.shields.io/docker/pulls/manhinhang/ib-gateway-docker)](https://hub.docker.com/r/manhinhang/ib-gateway-docker) 5 | [![GitHub](https://img.shields.io/github/license/manhinhang/ib-gateway-docker)](https://github.com/manhinhang/ib-gateway-docker/blob/develop/LICENSE) 6 | 7 | lightweight interactive brokers gateway docker 8 | 9 | It's just pure `IB Gateway` and don't include any VNC service (for security reason, I don't like expose extra port) 10 | 11 | This docker image just installed: 12 | 13 | - [IB Gateway](https://www.interactivebrokers.com/en/index.php?f=16457) (###IB_GATEWAY_VER###) 14 | 15 | - [IBC](https://github.com/IbcAlpha/IBC) (###IBC_VER###) 16 | 17 | ## Pull the Docker image from Docker Hub 18 | 19 | ```bash 20 | docker pull manhinhang/ib-gateway-docker 21 | ``` 22 | 23 | ### Create a container from the image and run it 24 | ```bash 25 | docker run -d \ 26 | --env IB_ACCOUNT= \ #YOUR_USER_ID 27 | --env IB_PASSWORD= \ #YOUR_PASSWORD 28 | --env TRADING_MODE= \ #paper or live 29 | -p 4002:4002 \ #brige IB gateway port to your local port 4002 30 | manhinhang/ib-gateway-docker 31 | ``` 32 | 33 | --- 34 | 35 | ## Build & Run locally 36 | 37 | ```bash 38 | git clone git@github.com:manhinhang/ib-gateway-docker.git 39 | cd ib-gateway-docker 40 | docker build --no-cache -t ib-gateway-docker . 41 | docker run -d \ 42 | --env IB_ACCOUNT= \ #YOUR_USER_ID 43 | --env IB_PASSWORD= \ #YOUR_PASSWORD 44 | --env TRADING_MODE= \ #paper or live 45 | -p 4002:4002 \ #brige IB gateway port to your local port 4002 46 | ib-gateway-docker 47 | ``` 48 | 49 | 50 | ## Container usage example 51 | 52 | | Example | Link | Description | 53 | | - | - | - | 54 | | ib_insync | [examples/ib_insync](./examples/ib_insync) | This example demonstrated how to connect `IB Gateway` 55 | 56 | 57 | ## Health check container 58 | 59 | ### API 60 | 61 | Healthcheck via api call `http://localhost:8080/healthcheck` 62 | 63 | Config `HEALTHCHECK_API_ENABLE=true` in environment variable to enable API 64 | 65 | ```bash 66 | curl -f http://localhost:8080/healthcheck 67 | ``` 68 | 69 | - Docker compose example 70 | 71 | ```yaml 72 | services: 73 | ib-gateway: 74 | image: manhinhang/ib-gateway-docker 75 | ports: 76 | - 4002:4002 77 | environment: 78 | - IB_ACCOUNT=$IB_ACCOUNT 79 | - IB_PASSWORD=$IB_PASSWORD 80 | - TRADING_MODE=$TRADING_MODE 81 | - HEALTHCHECK_API_ENABLE=true 82 | healthcheck: 83 | test: ["CMD", "curl", "-f", "http://localhost:8080/healthcheck"] 84 | interval: 60s 85 | timeout: 30s 86 | retries: 3 87 | start_period: 60s 88 | ``` 89 | ### CLI 90 | Execute `healthcheck` to detect IB gateway haelth status 91 | 92 | ```bash 93 | healthcheck 94 | # output: Ping IB Gateway successful 95 | echo $? 96 | # output: 0 97 | ``` 98 | 99 | ```bash 100 | healthcheck 101 | # output: Can not connect to IB Gateway 102 | echo $? 103 | # output: 1 104 | ``` 105 | 106 | - Docker compose example 107 | 108 | ```yaml 109 | services: 110 | ib-gateway: 111 | image: manhinhang/ib-gateway-docker 112 | ports: 113 | - 4002:4002 114 | environment: 115 | - IB_ACCOUNT=$IB_ACCOUNT 116 | - IB_PASSWORD=$IB_PASSWORD 117 | - TRADING_MODE=$TRADING_MODE 118 | healthcheck: 119 | test: /healthcheck/bin/healthcheck 120 | interval: 60s 121 | timeout: 30s 122 | retries: 3 123 | start_period: 60s 124 | ``` 125 | 126 | # Tests 127 | 128 | The [test cases](test/test_ib_gateway.py) written with testinfra. 129 | 130 | Run the tests 131 | 132 | ``` 133 | pytest 134 | ``` 135 | 136 | # Github Actions for continuous integration 137 | 138 | After forking `IB Gateway docker` repository, you need config your **interactive brokers** paper account & password in *github secret* 139 | 140 | | Key | Description | 141 | | - | - | 142 | | IB_ACCOUNT | your paper account name | 143 | | IB_PASSWORD | your paper account password | 144 | 145 | # Disclaimer 146 | 147 | This project is not affiliated with [Interactive Brokers Group, Inc.'s](https://www.interactivebrokers.com). 148 | 149 | Good luck and enjoy. 150 | 151 | -------------------------------------------------------------------------------- /healthcheck/gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 88 | 89 | # Use the maximum available, or set MAX_FD != -1 to use that value. 90 | MAX_FD=maximum 91 | 92 | warn () { 93 | echo "$*" 94 | } >&2 95 | 96 | die () { 97 | echo 98 | echo "$*" 99 | echo 100 | exit 1 101 | } >&2 102 | 103 | # OS specific support (must be 'true' or 'false'). 104 | cygwin=false 105 | msys=false 106 | darwin=false 107 | nonstop=false 108 | case "$( uname )" in #( 109 | CYGWIN* ) cygwin=true ;; #( 110 | Darwin* ) darwin=true ;; #( 111 | MSYS* | MINGW* ) msys=true ;; #( 112 | NONSTOP* ) nonstop=true ;; 113 | esac 114 | 115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 116 | 117 | 118 | # Determine the Java command to use to start the JVM. 119 | if [ -n "$JAVA_HOME" ] ; then 120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 121 | # IBM's JDK on AIX uses strange locations for the executables 122 | JAVACMD=$JAVA_HOME/jre/sh/java 123 | else 124 | JAVACMD=$JAVA_HOME/bin/java 125 | fi 126 | if [ ! -x "$JAVACMD" ] ; then 127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 128 | 129 | Please set the JAVA_HOME variable in your environment to match the 130 | location of your Java installation." 131 | fi 132 | else 133 | JAVACMD=java 134 | if ! command -v java >/dev/null 2>&1 135 | then 136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | fi 142 | 143 | # Increase the maximum file descriptors if we can. 144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 145 | case $MAX_FD in #( 146 | max*) 147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 148 | # shellcheck disable=SC2039,SC3045 149 | MAX_FD=$( ulimit -H -n ) || 150 | warn "Could not query maximum file descriptor limit" 151 | esac 152 | case $MAX_FD in #( 153 | '' | soft) :;; #( 154 | *) 155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 156 | # shellcheck disable=SC2039,SC3045 157 | ulimit -n "$MAX_FD" || 158 | warn "Could not set maximum file descriptor limit to $MAX_FD" 159 | esac 160 | fi 161 | 162 | # Collect all arguments for the java command, stacking in reverse order: 163 | # * args from the command line 164 | # * the main class name 165 | # * -classpath 166 | # * -D...appname settings 167 | # * --module-path (only if needed) 168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 169 | 170 | # For Cygwin or MSYS, switch paths to Windows format before running java 171 | if "$cygwin" || "$msys" ; then 172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -classpath "$CLASSPATH" \ 214 | org.gradle.wrapper.GradleWrapperMain \ 215 | "$@" 216 | 217 | # Stop when "xargs" is not available. 218 | if ! command -v xargs >/dev/null 2>&1 219 | then 220 | die "xargs is not available" 221 | fi 222 | 223 | # Use "xargs" to parse quoted args. 224 | # 225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 226 | # 227 | # In Bash we could simply go: 228 | # 229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 230 | # set -- "${ARGS[@]}" "$@" 231 | # 232 | # but POSIX shell has neither arrays nor command substitution, so instead we 233 | # post-process each arg (as a line of input to sed) to backslash-escape any 234 | # character that might be a shell metacharacter, then use eval to reverse 235 | # that process (while maintaining the separation between arguments), and wrap 236 | # the whole thing up as a single "set" statement. 237 | # 238 | # This will of course break if any of these variables contains a newline or 239 | # an unmatched quote. 240 | # 241 | 242 | eval "set -- $( 243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 244 | xargs -n1 | 245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 246 | tr '\n' ' ' 247 | )" '"$@"' 248 | 249 | exec "$JAVACMD" "$@" 250 | -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # CLAUDE.md 2 | 3 | This document provides context for AI assistants (like Claude) working with the IB Gateway Docker project. 4 | 5 | ## Project Overview 6 | 7 | **IB Gateway Docker** is a lightweight Docker container for Interactive Brokers Gateway (IB Gateway). This project provides a headless, automated solution for running IB Gateway in containerized environments without VNC or GUI dependencies. 8 | 9 | ### Key Features 10 | - Automated IB Gateway setup and login using IBC (Interactive Brokers Controller) 11 | - Headless operation using Xvfb (virtual framebuffer) 12 | - Health check capabilities (CLI and REST API) 13 | - Minimal attack surface (no VNC, no extra ports) 14 | - Automated version updates via GitHub Actions 15 | - Published to Docker Hub: `manhinhang/ib-gateway-docker` 16 | 17 | ### Current Versions 18 | - **IB Gateway**: 10.37.1m 19 | - **IBC**: 3.23.0 20 | - **Java**: OpenJDK 17 21 | - **Base Image**: Debian Bookworm Slim 22 | 23 | ## Project Structure 24 | 25 | ``` 26 | . 27 | ├── Dockerfile # Multi-stage Docker build 28 | ├── Dockerfile.template # Template for automated version updates 29 | ├── start.sh # Container entrypoint script 30 | ├── ibc/ # IBC configuration files 31 | │ └── config.ini # IBC settings 32 | ├── healthcheck/ # Health check tools (Java/Gradle) 33 | │ ├── healthcheck/ # CLI health check tool 34 | │ └── healthcheck-rest/ # REST API health check service 35 | ├── test/ # Python tests using testinfra 36 | │ ├── test_ib_gateway.py 37 | │ ├── test_ib_gateway_fail.py 38 | │ └── test_docker_interactive.py 39 | ├── examples/ # Usage examples 40 | │ └── ib_insync/ # ib_insync integration example 41 | ├── scripts/ # Utility scripts 42 | │ ├── detect_ib_gateway_ver.py 43 | │ ├── detect_ibc_ver.py 44 | │ └── extract_ib_gateway_major_minor.sh 45 | ├── .github/workflows/ # GitHub Actions CI/CD 46 | │ ├── build-test.yml # Build and test workflow 47 | │ ├── deploy.yml # Docker Hub deployment 48 | │ └── detect-new-ver.yml # Automated version detection 49 | └── doc/ # Documentation assets 50 | ``` 51 | 52 | ## Architecture 53 | 54 | ### Docker Build Stages 55 | 56 | The Dockerfile uses a **multi-stage build** approach: 57 | 58 | 1. **Downloader Stage** (`debian:bookworm-slim`) 59 | - Downloads IB Gateway installer from Interactive Brokers 60 | - Downloads latest IBC release from GitHub 61 | - Extracts version information 62 | - Prepares IBC configuration 63 | 64 | 2. **Healthcheck Tools Stage** (`gradle:8.7.0-jdk17`) 65 | - Builds Java-based health check CLI tool 66 | - Builds Java-based REST API health check service 67 | - Creates distribution packages 68 | 69 | 3. **Final Stage** (`debian:bookworm-slim`) 70 | - Installs minimal dependencies (Xvfb, Java 17, etc.) 71 | - Copies IB Gateway, IBC, and health check tools 72 | - Configures environment and entrypoint 73 | 74 | ### Runtime Flow 75 | 76 | 1. `start.sh` is executed as the container entrypoint 77 | 2. Xvfb starts on display `:0` (headless X server) 78 | 3. Port forwarding configured via `socat` (4001 → 4002) 79 | 4. Optional: Health check REST API starts on port 8080 80 | 5. IBC launches IB Gateway with provided credentials 81 | 6. Cleanup handlers trap INT/TERM signals for graceful shutdown 82 | 83 | ## Environment Variables 84 | 85 | ### Required 86 | - `IB_ACCOUNT` - Interactive Brokers account username 87 | - `IB_PASSWORD` - Interactive Brokers account password 88 | - `TRADING_MODE` - Either `paper` or `live` 89 | 90 | ### Optional 91 | - `IBGW_PORT` - Gateway port (default: 4002) 92 | - `JAVA_HEAP_SIZE` - JVM heap size in MB (default: 768) 93 | - `HEALTHCHECK_API_ENABLE` - Enable REST API health check (default: false) 94 | - `TWOFA_TIMEOUT_ACTION` - Action on 2FA timeout (default: restart) 95 | - `DISPLAY` - X display (default: :0, set automatically) 96 | 97 | ## Development Workflow 98 | 99 | ### Building Locally 100 | 101 | ```bash 102 | docker build --no-cache -t ib-gateway-docker . 103 | ``` 104 | 105 | ### Running Locally 106 | 107 | ```bash 108 | docker run -d \ 109 | --env IB_ACCOUNT=your_account \ 110 | --env IB_PASSWORD=your_password \ 111 | --env TRADING_MODE=paper \ 112 | -p 4002:4002 \ 113 | ib-gateway-docker 114 | ``` 115 | 116 | ### Running Tests 117 | 118 | Tests use **pytest** and **testinfra**: 119 | 120 | ```bash 121 | # Install test dependencies 122 | pip install -r requirements-test.txt 123 | 124 | # Run tests 125 | pytest 126 | ``` 127 | 128 | **Important**: Tests require valid IB account credentials: 129 | - `IB_ACCOUNT` - Test account username 130 | - `IB_PASSWORD` - Test account password 131 | - `TRADING_MODE` - Trading mode (paper/live) 132 | - `IMAGE_NAME` - Docker image to test 133 | 134 | ### Health Checks 135 | 136 | Two health check methods are available: 137 | 138 | 1. **CLI Health Check** 139 | ```bash 140 | docker exec healthcheck 141 | # Exit code 0 = healthy, 1 = unhealthy 142 | ``` 143 | 144 | 2. **REST API Health Check** 145 | ```bash 146 | curl -f http://localhost:8080/healthcheck 147 | # HTTP 200 = healthy, non-200 = unhealthy 148 | ``` 149 | 150 | ## CI/CD Pipeline 151 | 152 | ### GitHub Actions Workflows 153 | 154 | 1. **build-test.yml** - Build and test on push/PR 155 | - Builds Docker image 156 | - Runs pytest tests with real IB credentials 157 | - Requires secrets: `IB_ACCOUNT`, `IB_PASSWORD` 158 | 159 | 2. **deploy.yml** - Deploy to Docker Hub 160 | - Triggered on version tag pushes 161 | - Builds and pushes to Docker Hub 162 | - Requires secrets: `DOCKERHUB_USERNAME`, `DOCKERHUB_TOKEN` 163 | 164 | 3. **detect-new-ver.yml** - Automated version updates 165 | - Runs daily via cron 166 | - Detects new IB Gateway and IBC versions 167 | - Creates PR with updated Dockerfile and README 168 | - Uses version detection scripts 169 | 170 | ### GitHub Secrets Required 171 | 172 | - `IB_ACCOUNT` - Paper trading account for CI tests 173 | - `IB_PASSWORD` - Paper trading account password 174 | - `DOCKERHUB_USERNAME` - Docker Hub username 175 | - `DOCKERHUB_TOKEN` - Docker Hub access token 176 | 177 | ## Code Conventions 178 | 179 | ### Shell Scripts 180 | - Use `#!/bin/bash` shebang 181 | - Set `set -e` for error handling 182 | - Use cleanup traps for graceful shutdown 183 | - Quote variables to prevent word splitting 184 | 185 | ### Dockerfile 186 | - Use multi-stage builds to minimize final image size 187 | - Pin base image versions (e.g., `debian:bookworm-slim`) 188 | - Combine RUN commands to reduce layers 189 | - Use `.dockerignore` to exclude unnecessary files 190 | - Document environment variables with `ENV` 191 | 192 | ### Python Tests 193 | - Follow pytest conventions 194 | - Use environment variables for configuration 195 | - Clean up Docker containers after tests 196 | - Include both positive and negative test cases 197 | 198 | ### Version Management 199 | - IB Gateway version is automatically detected from Interactive Brokers 200 | - IBC version is pulled from GitHub releases API 201 | - Templates are updated via scripts when new versions are detected 202 | 203 | ## Common Tasks 204 | 205 | ### Updating IB Gateway Version 206 | 207 | The version update is **automated** via GitHub Actions, but can be done manually: 208 | 209 | 1. Update version in `Dockerfile` if not using auto-detection 210 | 2. Update version in `README.md` 211 | 3. Test the build: `docker build -t test-image .` 212 | 4. Create PR with changes 213 | 214 | ### Adding New Features 215 | 216 | 1. Create feature branch from `develop` 217 | 2. Make changes (code, tests, docs) 218 | 3. Update tests in `test/` directory 219 | 4. Run local tests: `pytest` 220 | 5. Update README.md if user-facing changes 221 | 6. Create PR to `develop` branch 222 | 223 | ### Modifying IBC Configuration 224 | 225 | Edit `ibc/config.ini` to change IBC behavior: 226 | - Login automation settings 227 | - 2FA handling 228 | - API port configuration 229 | - Logging options 230 | 231 | After changes, rebuild the Docker image. 232 | 233 | ### Adding Dependencies 234 | 235 | **System packages** (Dockerfile): 236 | ```dockerfile 237 | RUN apt-get install -y package-name 238 | ``` 239 | 240 | **Python test dependencies** (requirements-test.txt): 241 | ``` 242 | pytest==x.x.x 243 | testinfra==x.x.x 244 | ``` 245 | 246 | ## Troubleshooting 247 | 248 | ### Common Issues 249 | 250 | 1. **Container exits immediately** 251 | - Check credentials are valid 252 | - Review logs: `docker logs ` 253 | - Verify TRADING_MODE is `paper` or `live` 254 | 255 | 2. **Health check fails** 256 | - Wait 30-60s after container start 257 | - Check IB Gateway started: `docker logs ` 258 | - Verify port 4002 is accessible 259 | 260 | 3. **Xvfb timeout** 261 | - Usually indicates system resource issues 262 | - Check Docker resource limits 263 | - Review Xvfb logs in container output 264 | 265 | 4. **2FA issues** 266 | - Configure TWOFA_TIMEOUT_ACTION appropriately 267 | - Some accounts require device authentication 268 | - Check IBC configuration in `ibc/config.ini` 269 | 270 | ## Testing Strategy 271 | 272 | - **Unit tests**: N/A (no application logic, infrastructure only) 273 | - **Integration tests**: Python/testinfra tests in `test/` 274 | - **Smoke tests**: Health check validation after container start 275 | - **CI tests**: Automated builds and tests on every push 276 | 277 | ## Important Notes 278 | 279 | 1. **Security**: Never commit IB credentials to version control 280 | 2. **Paper Trading**: Use paper trading for all CI/CD testing 281 | 3. **Port Forwarding**: socat forwards 4001→4002 for compatibility 282 | 4. **Display**: Xvfb required for headless IB Gateway operation 283 | 5. **Cleanup**: Always properly stop containers to avoid orphaned processes 284 | 6. **Versions**: IB Gateway updates frequently; automated detection helps 285 | 286 | ## External Dependencies 287 | 288 | - **IB Gateway**: Downloaded from Interactive Brokers 289 | - URL: `https://download2.interactivebrokers.com/installers/ibgateway/stable-standalone/` 290 | 291 | - **IBC**: Downloaded from GitHub releases 292 | - Repo: `https://github.com/IbcAlpha/IBC` 293 | 294 | - **Base Images**: 295 | - `debian:bookworm-slim` 296 | - `gradle:8.7.0-jdk17` 297 | 298 | ## Resources 299 | 300 | - [Interactive Brokers API](https://www.interactivebrokers.com/en/index.php?f=16457) 301 | - [IBC Documentation](https://github.com/IbcAlpha/IBC) 302 | - [Docker Hub Repository](https://hub.docker.com/r/manhinhang/ib-gateway-docker) 303 | - [GitHub Repository](https://github.com/manhinhang/ib-gateway-docker) 304 | 305 | ## License 306 | 307 | This project is licensed under the terms specified in the LICENSE file. 308 | 309 | **Disclaimer**: This project is not affiliated with Interactive Brokers Group, Inc. 310 | -------------------------------------------------------------------------------- /healthcheck/healthcheck/src/main/kotlin/com/manhinhang/ibgatewaydocker/healthcheck/Wrapper.kt: -------------------------------------------------------------------------------- 1 | package com.manhinhang.ibgatewaydocker.healthcheck 2 | 3 | import com.ib.client.* 4 | import java.lang.Exception 5 | 6 | class Wrapper: EWrapper { 7 | override fun tickPrice(tickerId: Int, field: Int, price: Double, attrib: TickAttrib?) { 8 | 9 | } 10 | 11 | override fun tickSize(tickerId: Int, field: Int, size: Decimal?) { 12 | 13 | } 14 | 15 | override fun tickOptionComputation( 16 | tickerId: Int, 17 | field: Int, 18 | tickAttrib: Int, 19 | impliedVol: Double, 20 | delta: Double, 21 | optPrice: Double, 22 | pvDividend: Double, 23 | gamma: Double, 24 | vega: Double, 25 | theta: Double, 26 | undPrice: Double 27 | ) { 28 | 29 | } 30 | 31 | override fun tickGeneric(tickerId: Int, tickType: Int, value: Double) { 32 | 33 | } 34 | 35 | override fun tickString(tickerId: Int, tickType: Int, value: String?) { 36 | 37 | } 38 | 39 | override fun tickEFP( 40 | tickerId: Int, 41 | tickType: Int, 42 | basisPoints: Double, 43 | formattedBasisPoints: String?, 44 | impliedFuture: Double, 45 | holdDays: Int, 46 | futureLastTradeDate: String?, 47 | dividendImpact: Double, 48 | dividendsToLastTradeDate: Double 49 | ) { 50 | 51 | } 52 | 53 | override fun orderStatus( 54 | orderId: Int, 55 | status: String?, 56 | filled: Decimal?, 57 | remaining: Decimal?, 58 | avgFillPrice: Double, 59 | permId: Int, 60 | parentId: Int, 61 | lastFillPrice: Double, 62 | clientId: Int, 63 | whyHeld: String?, 64 | mktCapPrice: Double 65 | ) { 66 | 67 | } 68 | 69 | override fun openOrder(orderId: Int, contract: Contract?, order: Order?, orderState: OrderState?) { 70 | 71 | } 72 | 73 | override fun openOrderEnd() { 74 | 75 | } 76 | 77 | override fun updateAccountValue(key: String?, value: String?, currency: String?, accountName: String?) { 78 | 79 | } 80 | 81 | override fun updatePortfolio( 82 | contract: Contract?, 83 | position: Decimal?, 84 | marketPrice: Double, 85 | marketValue: Double, 86 | averageCost: Double, 87 | unrealizedPNL: Double, 88 | realizedPNL: Double, 89 | accountName: String? 90 | ) { 91 | 92 | } 93 | 94 | override fun updateAccountTime(timeStamp: String?) { 95 | 96 | } 97 | 98 | override fun accountDownloadEnd(accountName: String?) { 99 | 100 | } 101 | 102 | override fun nextValidId(orderId: Int) { 103 | 104 | } 105 | 106 | override fun contractDetails(reqId: Int, contractDetails: ContractDetails?) { 107 | 108 | } 109 | 110 | override fun bondContractDetails(reqId: Int, contractDetails: ContractDetails?) { 111 | 112 | } 113 | 114 | override fun contractDetailsEnd(reqId: Int) { 115 | 116 | } 117 | 118 | override fun execDetails(reqId: Int, contract: Contract?, execution: Execution?) { 119 | 120 | } 121 | 122 | override fun execDetailsEnd(reqId: Int) { 123 | 124 | } 125 | 126 | override fun updateMktDepth( 127 | tickerId: Int, 128 | position: Int, 129 | operation: Int, 130 | side: Int, 131 | price: Double, 132 | size: Decimal? 133 | ) { 134 | 135 | } 136 | 137 | override fun updateMktDepthL2( 138 | tickerId: Int, 139 | position: Int, 140 | marketMaker: String?, 141 | operation: Int, 142 | side: Int, 143 | price: Double, 144 | size: Decimal?, 145 | isSmartDepth: Boolean 146 | ) { 147 | 148 | } 149 | 150 | override fun updateNewsBulletin(msgId: Int, msgType: Int, message: String?, origExchange: String?) { 151 | 152 | } 153 | 154 | override fun managedAccounts(accountsList: String?) { 155 | 156 | } 157 | 158 | override fun receiveFA(faDataType: Int, xml: String?) { 159 | 160 | } 161 | 162 | override fun historicalData(reqId: Int, bar: Bar?) { 163 | 164 | } 165 | 166 | override fun scannerParameters(xml: String?) { 167 | 168 | } 169 | 170 | override fun scannerData( 171 | reqId: Int, 172 | rank: Int, 173 | contractDetails: ContractDetails?, 174 | distance: String?, 175 | benchmark: String?, 176 | projection: String?, 177 | legsStr: String? 178 | ) { 179 | 180 | } 181 | 182 | override fun scannerDataEnd(reqId: Int) { 183 | 184 | } 185 | 186 | override fun realtimeBar( 187 | reqId: Int, 188 | time: Long, 189 | open: Double, 190 | high: Double, 191 | low: Double, 192 | close: Double, 193 | volume: Decimal?, 194 | wap: Decimal?, 195 | count: Int 196 | ) { 197 | 198 | } 199 | 200 | override fun currentTime(time: Long) { 201 | 202 | } 203 | 204 | override fun fundamentalData(reqId: Int, data: String?) { 205 | 206 | } 207 | 208 | override fun deltaNeutralValidation(reqId: Int, deltaNeutralContract: DeltaNeutralContract?) { 209 | 210 | } 211 | 212 | override fun tickSnapshotEnd(reqId: Int) { 213 | 214 | } 215 | 216 | override fun marketDataType(reqId: Int, marketDataType: Int) { 217 | 218 | } 219 | 220 | override fun commissionReport(commissionReport: CommissionReport?) { 221 | 222 | } 223 | 224 | override fun position(account: String?, contract: Contract?, pos: Decimal?, avgCost: Double) { 225 | 226 | } 227 | 228 | override fun positionEnd() { 229 | 230 | } 231 | 232 | override fun accountSummary(reqId: Int, account: String?, tag: String?, value: String?, currency: String?) { 233 | 234 | } 235 | 236 | override fun accountSummaryEnd(reqId: Int) { 237 | 238 | } 239 | 240 | override fun verifyMessageAPI(apiData: String?) { 241 | 242 | } 243 | 244 | override fun verifyCompleted(isSuccessful: Boolean, errorText: String?) { 245 | 246 | } 247 | 248 | override fun verifyAndAuthMessageAPI(apiData: String?, xyzChallenge: String?) { 249 | 250 | } 251 | 252 | override fun verifyAndAuthCompleted(isSuccessful: Boolean, errorText: String?) { 253 | 254 | } 255 | 256 | override fun displayGroupList(reqId: Int, groups: String?) { 257 | 258 | } 259 | 260 | override fun displayGroupUpdated(reqId: Int, contractInfo: String?) { 261 | 262 | } 263 | 264 | override fun error(e: Exception?) { 265 | 266 | } 267 | 268 | override fun error(str: String?) { 269 | 270 | } 271 | 272 | override fun error(id: Int, errorCode: Int, errorMsg: String?, advancedOrderRejectJson: String?) { 273 | 274 | } 275 | 276 | override fun connectionClosed() { 277 | 278 | } 279 | 280 | override fun connectAck() { 281 | 282 | } 283 | 284 | override fun positionMulti( 285 | reqId: Int, 286 | account: String?, 287 | modelCode: String?, 288 | contract: Contract?, 289 | pos: Decimal?, 290 | avgCost: Double 291 | ) { 292 | 293 | } 294 | 295 | override fun positionMultiEnd(reqId: Int) { 296 | 297 | } 298 | 299 | override fun accountUpdateMulti( 300 | reqId: Int, 301 | account: String?, 302 | modelCode: String?, 303 | key: String?, 304 | value: String?, 305 | currency: String? 306 | ) { 307 | 308 | } 309 | 310 | override fun accountUpdateMultiEnd(reqId: Int) { 311 | 312 | } 313 | 314 | override fun securityDefinitionOptionalParameter( 315 | reqId: Int, 316 | exchange: String?, 317 | underlyingConId: Int, 318 | tradingClass: String?, 319 | multiplier: String?, 320 | expirations: MutableSet?, 321 | strikes: MutableSet? 322 | ) { 323 | 324 | } 325 | 326 | override fun securityDefinitionOptionalParameterEnd(reqId: Int) { 327 | 328 | } 329 | 330 | override fun softDollarTiers(reqId: Int, tiers: Array?) { 331 | 332 | } 333 | 334 | override fun familyCodes(familyCodes: Array?) { 335 | 336 | } 337 | 338 | override fun symbolSamples(reqId: Int, contractDescriptions: Array?) { 339 | 340 | } 341 | 342 | override fun historicalDataEnd(reqId: Int, startDateStr: String?, endDateStr: String?) { 343 | 344 | } 345 | 346 | override fun mktDepthExchanges(depthMktDataDescriptions: Array?) { 347 | 348 | } 349 | 350 | override fun tickNews( 351 | tickerId: Int, 352 | timeStamp: Long, 353 | providerCode: String?, 354 | articleId: String?, 355 | headline: String?, 356 | extraData: String? 357 | ) { 358 | 359 | } 360 | 361 | override fun smartComponents(reqId: Int, theMap: MutableMap>?) { 362 | 363 | } 364 | 365 | override fun tickReqParams(tickerId: Int, minTick: Double, bboExchange: String?, snapshotPermissions: Int) { 366 | 367 | } 368 | 369 | override fun newsProviders(newsProviders: Array?) { 370 | 371 | } 372 | 373 | override fun newsArticle(requestId: Int, articleType: Int, articleText: String?) { 374 | 375 | } 376 | 377 | override fun historicalNews( 378 | requestId: Int, 379 | time: String?, 380 | providerCode: String?, 381 | articleId: String?, 382 | headline: String? 383 | ) { 384 | 385 | } 386 | 387 | override fun historicalNewsEnd(requestId: Int, hasMore: Boolean) { 388 | 389 | } 390 | 391 | override fun headTimestamp(reqId: Int, headTimestamp: String?) { 392 | 393 | } 394 | 395 | override fun histogramData(reqId: Int, items: MutableList?) { 396 | 397 | } 398 | 399 | override fun historicalDataUpdate(reqId: Int, bar: Bar?) { 400 | 401 | } 402 | 403 | override fun rerouteMktDataReq(reqId: Int, conId: Int, exchange: String?) { 404 | 405 | } 406 | 407 | override fun rerouteMktDepthReq(reqId: Int, conId: Int, exchange: String?) { 408 | 409 | } 410 | 411 | override fun marketRule(marketRuleId: Int, priceIncrements: Array?) { 412 | 413 | } 414 | 415 | override fun pnl(reqId: Int, dailyPnL: Double, unrealizedPnL: Double, realizedPnL: Double) { 416 | 417 | } 418 | 419 | override fun pnlSingle( 420 | reqId: Int, 421 | pos: Decimal?, 422 | dailyPnL: Double, 423 | unrealizedPnL: Double, 424 | realizedPnL: Double, 425 | value: Double 426 | ) { 427 | 428 | } 429 | 430 | override fun historicalTicks(reqId: Int, ticks: MutableList?, done: Boolean) { 431 | 432 | } 433 | 434 | override fun historicalTicksBidAsk(reqId: Int, ticks: MutableList?, done: Boolean) { 435 | 436 | } 437 | 438 | override fun historicalTicksLast(reqId: Int, ticks: MutableList?, done: Boolean) { 439 | 440 | } 441 | 442 | override fun tickByTickAllLast( 443 | reqId: Int, 444 | tickType: Int, 445 | time: Long, 446 | price: Double, 447 | size: Decimal?, 448 | tickAttribLast: TickAttribLast?, 449 | exchange: String?, 450 | specialConditions: String? 451 | ) { 452 | 453 | } 454 | 455 | override fun tickByTickBidAsk( 456 | reqId: Int, 457 | time: Long, 458 | bidPrice: Double, 459 | askPrice: Double, 460 | bidSize: Decimal?, 461 | askSize: Decimal?, 462 | tickAttribBidAsk: TickAttribBidAsk? 463 | ) { 464 | 465 | } 466 | 467 | override fun tickByTickMidPoint(reqId: Int, time: Long, midPoint: Double) { 468 | 469 | } 470 | 471 | override fun orderBound(orderId: Long, apiClientId: Int, apiOrderId: Int) { 472 | 473 | } 474 | 475 | override fun completedOrder(contract: Contract?, order: Order?, orderState: OrderState?) { 476 | 477 | } 478 | 479 | override fun completedOrdersEnd() { 480 | 481 | } 482 | 483 | override fun replaceFAEnd(reqId: Int, text: String?) { 484 | 485 | } 486 | 487 | override fun wshMetaData(reqId: Int, dataJson: String?) { 488 | 489 | } 490 | 491 | override fun wshEventData(reqId: Int, dataJson: String?) { 492 | 493 | } 494 | 495 | override fun historicalSchedule( 496 | reqId: Int, 497 | startDateTime: String?, 498 | endDateTime: String?, 499 | timeZone: String?, 500 | sessions: MutableList? 501 | ) { 502 | 503 | } 504 | 505 | override fun userInfo(reqId: Int, whiteBrandingId: String?) { 506 | 507 | } 508 | 509 | } -------------------------------------------------------------------------------- /ibc/config.ini: -------------------------------------------------------------------------------- 1 | # Note that in the comments in this file, TWS refers to both the Trader 2 | # Workstation and the IB Gateway, unless explicitly stated otherwise. 3 | # 4 | # When referred to below, the default value for a setting is the value 5 | # assumed if either the setting is included but no value is specified, or 6 | # the setting is not included at all. 7 | # 8 | # IBC may also be used to start the FIX CTCI Gateway. All settings 9 | # relating to this have names prefixed with FIX. 10 | # 11 | # The IB API Gateway and the FIX CTCI Gateway share the same code. Which 12 | # gateway actually runs is governed by an option on the initial gateway 13 | # login screen. The FIX setting described under IBC Startup 14 | # Settings below controls this. 15 | 16 | 17 | 18 | # ============================================================================= 19 | # 1. IBC Startup Settings 20 | # ============================================================================= 21 | 22 | 23 | # IBC may be used to start the IB Gateway for the FIX CTCI. This 24 | # setting must be set to 'yes' if you want to run the FIX CTCI gateway. The 25 | # default is 'no'. 26 | 27 | FIX=no 28 | 29 | 30 | 31 | # ============================================================================= 32 | # 2. Authentication Settings 33 | # ============================================================================= 34 | 35 | # TWS and the IB API gateway require a single username and password. 36 | # You may specify the username and password using the following settings: 37 | # 38 | # IbLoginId 39 | # IbPassword 40 | # 41 | # Alternatively, you can specify the username and password in the command 42 | # files used to start TWS or the Gateway, but this is not recommended for 43 | # security reasons. 44 | # 45 | # If you don't specify them, you will be prompted for them in the usual 46 | # login dialog when TWS starts (but whatever you have specified will be 47 | # included in the dialog automatically: for example you may specify the 48 | # username but not the password, and then you will be prompted for the 49 | # password via the login dialog). Note that if you specify either 50 | # the username or the password (or both) in the command file, then 51 | # IbLoginId and IbPassword settings defined in this file are ignored. 52 | # 53 | # 54 | # The FIX CTCI gateway requires one username and password for FIX order 55 | # routing, and optionally a separate username and password for market 56 | # data connections. You may specify the usernames and passwords using 57 | # the following settings: 58 | # 59 | # FIXLoginId 60 | # FIXPassword 61 | # IbLoginId (optional - for market data connections) 62 | # IbPassword (optional - for market data connections) 63 | # 64 | # Alternatively you can specify the FIX username and password in the 65 | # command file used to start the FIX CTCI Gateway, but this is not 66 | # recommended for security reasons. 67 | # 68 | # If you don't specify them, you will be prompted for them in the usual 69 | # login dialog when FIX CTCI gateway starts (but whatever you have 70 | # specified will be included in the dialog automatically: for example 71 | # you may specify the usernames but not the passwords, and then you will 72 | # be prompted for the passwords via the login dialog). Note that if you 73 | # specify either the FIX username or the FIX password (or both) on the 74 | # command line, then FIXLoginId and FIXPassword settings defined in this 75 | # file are ignored; he same applies to the market data username and 76 | # password. 77 | 78 | # IB API Authentication Settings 79 | # ------------------------------ 80 | 81 | # Your TWS username: 82 | 83 | IbLoginId= 84 | 85 | 86 | # Your TWS password: 87 | 88 | IbPassword= 89 | 90 | 91 | # FIX CTCI Authentication Settings 92 | # -------------------------------- 93 | 94 | # Your FIX CTCI username: 95 | 96 | FIXLoginId= 97 | 98 | 99 | # Your FIX CTCI password: 100 | 101 | FIXPassword= 102 | 103 | 104 | # Second Factor Authentication Settings 105 | # ------------------------------------- 106 | 107 | # If you have enabled more than one second factor authentication 108 | # device, TWS presents a list from which you must select the device 109 | # you want to use for this login. You can use this setting to 110 | # instruct IBC to select a particular item in the list on your 111 | # behalf. Note that you must spell this value exactly as it appears 112 | # in the list. If no value is set, you must manually select the 113 | # relevant list entry. 114 | 115 | SecondFactorDevice=IB Key 116 | 117 | 118 | # If you use the IBKR Mobile app for second factor authentication, 119 | # and you fail to complete the process before the time limit imposed 120 | # by IBKR, this setting tells IBC whether to automatically restart 121 | # the login sequence, giving you another opportunity to complete 122 | # second factor authentication. 123 | # 124 | # Permitted values are 'yes' and 'no'. 125 | # 126 | # If this setting is not present or has no value, then the value 127 | # of the deprecated ExitAfterSecondFactorAuthenticationTimeout is 128 | # used instead. If this also has no value, then this setting defaults 129 | # to 'no'. 130 | # 131 | # NB: you must be using IBC v3.14.0 or later to use this setting: 132 | # earlier versions ignore it. 133 | 134 | ReloginAfterSecondFactorAuthenticationTimeout=no 135 | 136 | 137 | # This setting is only relevant if 138 | # ReloginAfterSecondFactorAuthenticationTimeout is set to 'yes', 139 | # or if ExitAfterSecondFactorAuthenticationTimeout is set to 'yes'. 140 | # 141 | # It controls how long (in seconds) IBC waits for login to complete 142 | # after the user acknowledges the second factor authentication 143 | # alert at the IBKR Mobile app. If login has not completed after 144 | # this time, IBC terminates. 145 | # The default value is 60. 146 | 147 | SecondFactorAuthenticationExitInterval= 148 | 149 | 150 | # This setting specifies the timeout for second factor authentication 151 | # imposed by IB. The value is in seconds. You should not change this 152 | # setting unless you have reason to believe that IB has changed the 153 | # timeout. The default value is 180. 154 | 155 | SecondFactorAuthenticationTimeout=180 156 | 157 | 158 | # DEPRECATED SETTING 159 | # ------------------ 160 | # 161 | # ExitAfterSecondFactorAuthenticationTimeout - THIS SETTING WILL BE 162 | # REMOVED IN A FUTURE RELEASE. For IBC version 3.14.0 and later, see 163 | # the notes for ReloginAfterSecondFactorAuthenticationTimeout above. 164 | # 165 | # For IBC versions earlier than 3.14.0: If you use the IBKR Mobile 166 | # app for second factor authentication, and you fail to complete the 167 | # process before the time limit imposed by IBKR, you can use this 168 | # setting to tell IBC to exit: arrangements can then be made to 169 | # automatically restart IBC in order to initiate the login sequence 170 | # afresh. Otherwise, manual intervention at TWS's 171 | # Second Factor Authentication dialog is needed to complete the 172 | # login. 173 | # 174 | # Permitted values are 'yes' and 'no'. The default is 'no'. 175 | # 176 | # Note that the scripts provided with the IBC zips for Windows and 177 | # Linux provide options to automatically restart in these 178 | # circumstances, but only if this setting is also set to 'yes'. 179 | 180 | ExitAfterSecondFactorAuthenticationTimeout=no 181 | 182 | 183 | # Trading Mode 184 | # ------------ 185 | # 186 | # This indicates whether the live account or the paper trading 187 | # account corresponding to the supplied credentials is to be used. 188 | # The allowed values are 'live' (the default) and 'paper'. 189 | # 190 | # If this is set to 'live', then the credentials for the live 191 | # account must be supplied. If it is set to 'paper', then either 192 | # the live or the paper-trading credentials may be supplied. 193 | 194 | TradingMode= 195 | 196 | 197 | # Paper-trading Account Warning 198 | # ----------------------------- 199 | # 200 | # Logging in to a paper-trading account results in TWS displaying 201 | # a dialog asking the user to confirm that they are aware that this 202 | # is not a brokerage account. Until this dialog has been accepted, 203 | # TWS will not allow API connections to succeed. Setting this 204 | # to 'yes' (the default) will cause IBC to automatically 205 | # confirm acceptance. Setting it to 'no' will leave the dialog 206 | # on display, and the user will have to deal with it manually. 207 | 208 | AcceptNonBrokerageAccountWarning=yes 209 | 210 | 211 | # Login Dialog Display Timeout 212 | #----------------------------- 213 | # 214 | # In some circumstances, starting TWS may result in failure to display 215 | # the login dialog. Restarting TWS may help to resolve this situation, 216 | # and IBC does this automatically. 217 | # 218 | # This setting controls how long (in seconds) IBC waits for the login 219 | # dialog to appear before restarting TWS. 220 | # 221 | # Note that in normal circumstances with a reasonably specified 222 | # computer the time to displaying the login dialog is typically less 223 | # than 20 seconds, and frequently much less. However many factors can 224 | # influence this, and it is unwise to set this value too low. 225 | # 226 | # The default value is 60. 227 | 228 | LoginDialogDisplayTimeout=60 229 | 230 | 231 | 232 | # ============================================================================= 233 | # 3. TWS Startup Settings 234 | # ============================================================================= 235 | 236 | # Path to settings store 237 | # ---------------------- 238 | # 239 | # Path to the directory where TWS should store its settings. This is 240 | # normally the folder in which TWS is installed. However you may set 241 | # it to some other location if you wish (for example if you want to 242 | # run multiple instances of TWS with different settings). 243 | # 244 | # It is recommended for clarity that you use an absolute path. The 245 | # effect of using a relative path is undefined. 246 | # 247 | # Linux and macOS users should use the appropriate path syntax. 248 | # 249 | # Note that, for Windows users, you MUST use double separator 250 | # characters to separate the elements of the folder path: for 251 | # example, IbDir=C:\\IBLiveSettings is valid, but 252 | # IbDir=C:\IBLiveSettings is NOT valid and will give unexpected 253 | # results. Linux and macOS users need not use double separators, 254 | # but they are acceptable. 255 | # 256 | # The default is the current working directory when IBC is 257 | # started, unless the TWS_SETTINGS_PATH setting in the relevant 258 | # start script is set. 259 | # 260 | # If both this setting and TWS_SETTINGS_PATH are set, then this 261 | # setting takes priority. Note that if they have different values, 262 | # auto-restart will not work. 263 | # 264 | # NB: this setting is now DEPRECATED. You should use the 265 | # TWS_SETTINGS_PATH setting in the relevant start script. 266 | 267 | IbDir= 268 | 269 | 270 | # Store settings on server 271 | # ------------------------ 272 | # 273 | # If you wish to store a copy of your TWS settings on IB's 274 | # servers as well as locally on your computer, set this to 275 | # 'yes': this enables you to run TWS on different computers 276 | # with the same configuration, market data lines, etc. If set 277 | # to 'no', running TWS on different computers will not share the 278 | # same settings. If no value is specified, TWS will obtain its 279 | # settings from the same place as the last time this user logged 280 | # in (whether manually or using IBC). 281 | 282 | StoreSettingsOnServer= 283 | 284 | 285 | # Minimize TWS on startup 286 | # ----------------------- 287 | # 288 | # Set to 'yes' to minimize TWS when it starts: 289 | 290 | MinimizeMainWindow=no 291 | 292 | 293 | # Existing Session Detected Action 294 | # -------------------------------- 295 | # 296 | # When a user logs on to an IBKR account for trading purposes by any means, the 297 | # IBKR account server checks to see whether the account is already logged in 298 | # elsewhere. If so, a dialog is displayed to both the users that enables them 299 | # to determine what happens next. The 'ExistingSessionDetectedAction' setting 300 | # instructs TWS how to proceed when it displays this dialog: 301 | # 302 | # * If the new TWS session is set to 'secondary', the existing session continues 303 | # and the new session terminates. Thus a secondary TWS session can never 304 | # override any other session. 305 | # 306 | # * If the existing TWS session is set to 'primary', the existing session 307 | # continues and the new session terminates (even if the new session is also 308 | # set to primary). Thus a primary TWS session can never be overridden by 309 | # any new session). 310 | # 311 | # * If both the existing and the new TWS sessions are set to 'primaryoverride', 312 | # the existing session terminates and the new session proceeds. 313 | # 314 | # * If the existing TWS session is set to 'manual', the user must handle the 315 | # dialog. 316 | # 317 | # The difference between 'primary' and 'primaryoverride' is that a 318 | # 'primaryoverride' session can be overriden over by a new 'primary' session, 319 | # but a 'primary' session cannot be overriden by any other session. 320 | # 321 | # When set to 'primary', if another TWS session is started and manually told to 322 | # end the 'primary' session, the 'primary' session is automatically reconnected. 323 | # 324 | # The default is 'manual'. 325 | 326 | ExistingSessionDetectedAction=primary 327 | 328 | 329 | # Override TWS API Port Number 330 | # ---------------------------- 331 | # 332 | # If OverrideTwsApiPort is set to an integer, IBC changes the 333 | # 'Socket port' in TWS's API configuration to that number shortly 334 | # after startup (but note that for the FIX Gateway, this setting is 335 | # actually stored in jts.ini rather than the Gateway's settings 336 | # file). Leaving the setting blank will make no change to 337 | # the current setting. This setting is only intended for use in 338 | # certain specialized situations where the port number needs to 339 | # be set dynamically at run-time, and for the FIX Gateway: most 340 | # non-FIX users will never need it, so don't use it unless you know 341 | # you need it. 342 | 343 | OverrideTwsApiPort=4001 344 | 345 | 346 | # Override TWS Master Client ID 347 | # ----------------------------- 348 | # 349 | # If OverrideTwsMasterClientID is set to an integer, IBC changes the 350 | # 'Master Client ID' value in TWS's API configuration to that 351 | # value shortly after startup. Leaving the setting blank will make 352 | # no change to the current setting. This setting is only intended 353 | # for use in certain specialized situations where the value needs to 354 | # be set dynamically at run-time: most users will never need it, 355 | # so don't use it unless you know you need it. 356 | 357 | OverrideTwsMasterClientID= 358 | 359 | 360 | # Read-only Login 361 | # --------------- 362 | # 363 | # If ReadOnlyLogin is set to 'yes', and the user is enrolled in IB's 364 | # account security programme, the user will not be asked to perform 365 | # the second factor authentication action, and login to TWS will 366 | # occur automatically in read-only mode: in this mode, placing or 367 | # managing orders is not allowed. 368 | # 369 | # If set to 'no', and the user is enrolled in IB's account security 370 | # programme, the second factor authentication process is handled 371 | # according to the Second Factor Authentication Settings described 372 | # elsewhere in this file. 373 | # 374 | # If the user is not enrolled in IB's account security programme, 375 | # this setting is ignored. The default is 'no'. 376 | 377 | ReadOnlyLogin=no 378 | 379 | 380 | # Read-only API 381 | # ------------- 382 | # 383 | # If ReadOnlyApi is set to 'yes', API programs cannot submit, modify 384 | # or cancel orders. If set to 'no', API programs can do these things. 385 | # If not set, the existing TWS/Gateway configuration is unchanged. 386 | # NB: this setting is really only supplied for the benefit of new TWS 387 | # or Gateway instances that are being automatically installed and 388 | # started without user intervention (eg Docker containers). Where 389 | # a user is involved, they should use the Global Configuration to 390 | # set the relevant checkbox (this only needs to be done once) and 391 | # not provide a value for this setting. 392 | 393 | ReadOnlyApi=no 394 | 395 | 396 | # API Precautions 397 | # --------------- 398 | # 399 | # These settings relate to the corresponding 'Precautions' checkboxes in the 400 | # API section of the Global Configuration dialog. 401 | # 402 | # For all of these, the accepted values are: 403 | # - 'yes' sets the checkbox 404 | # - 'no' clears the checkbox 405 | # - if not set, the existing TWS/Gateway configuration is unchanged 406 | # 407 | # NB: thess settings are really only supplied for the benefit of new TWS 408 | # or Gateway instances that are being automatically installed and 409 | # started without user intervention, or where user settings are not preserved 410 | # between sessions (eg some Docker containers). Where a user is involved, they 411 | # should use the Global Configuration to set the relevant checkboxes and not 412 | # provide values for these settings. 413 | 414 | BypassOrderPrecautions= 415 | 416 | BypassBondWarning= 417 | 418 | BypassNegativeYieldToWorstConfirmation= 419 | 420 | BypassCalledBondWarning= 421 | 422 | BypassSameActionPairTradeWarning= 423 | 424 | BypassPriceBasedVolatilityRiskWarning= 425 | 426 | BypassUSStocksMarketDataInSharesWarning= 427 | 428 | BypassRedirectOrderWarning= 429 | 430 | BypassNoOverfillProtectionPrecaution= 431 | 432 | 433 | # Market data size for US stocks - lots or shares 434 | # ----------------------------------------------- 435 | # 436 | # Since IB introduced the option of market data for US stocks showing 437 | # bid, ask and last sizes in shares rather than lots, TWS and Gateway 438 | # display a dialog immediately after login notifying the user about 439 | # this and requiring user input before allowing market data to be 440 | # accessed. The user can request that the dialog not be shown again. 441 | # 442 | # It is recommended that the user should handle this dialog manually 443 | # rather than using these settings, which are provided for situations 444 | # where the user interface is not easily accessible, or where user 445 | # settings are not preserved between sessions (eg some Docker images). 446 | # 447 | # - If this setting is set to 'accept', the dialog will be handled 448 | # automatically and the option to not show it again will be 449 | # selected. 450 | # 451 | # Note that in this case, the only way to allow the dialog to be 452 | # displayed again is to manually enable the 'Bid, Ask and Last 453 | # Size Display Update' message in the 'Messages' section of the TWS 454 | # configuration dialog. So you should only use 'Accept' if you are 455 | # sure you really don't want the dialog to be displayed again, or 456 | # you have easy access to the user interface. 457 | # 458 | # - If set to 'defer', the dialog will be handled automatically (so 459 | # that market data will start), but the option to not show it again 460 | # will not be selected, and it will be shown again after the next 461 | # login. 462 | # 463 | # - If set to 'ignore', the user has to deal with the dialog manually. 464 | # 465 | # The default value is 'ignore'. 466 | # 467 | # Note if set to 'accept' or 'defer', TWS also automatically sets 468 | # the API settings checkbox labelled 'Send market data in lots for 469 | # US stocks for dual-mode API clients'. IBC cannot prevent this. 470 | # However you can change this immmediately by setting 471 | # SendMarketDataInLotsForUSstocks (see below) to 'no' . 472 | 473 | AcceptBidAskLastSizeDisplayUpdateNotification= 474 | 475 | 476 | # This setting determines whether the API settings checkbox labelled 477 | # 'Send market data in lots for US stocks for dual-mode API clients' 478 | # is set or cleared. If set to 'yes', the checkbox is set. If set to 479 | # 'no' the checkbox is cleared. If defaulted, the checkbox is 480 | # unchanged. 481 | 482 | SendMarketDataInLotsForUSstocks= 483 | 484 | 485 | # Trusted API Client IPs 486 | # ---------------------- 487 | # 488 | # NB: THIS SETTING IS ONLY RELEVANT FOR THE GATEWAY, AND ONLY WHEN FIX=yes. 489 | # In all other cases it is ignored. 490 | # 491 | # This is a list of IP addresses separated by commas. API clients with IP 492 | # addresses in this list are able to connect to the API without Gateway 493 | # generating the 'Incoming connection' popup. 494 | # 495 | # Note that 127.0.0.1 is always permitted to connect, so do not include it 496 | # in this setting. 497 | 498 | TrustedTwsApiClientIPs= 499 | 500 | 501 | # Reset Order ID Sequence 502 | # ----------------------- 503 | # 504 | # The setting resets the order id sequence for orders submitted via the API, so 505 | # that the next invocation of the `NextValidId` API callback will return the 506 | # value 1. The reset occurs when TWS starts. 507 | # 508 | # Note that order ids are reset for all API clients, except those that have 509 | # outstanding (ie incomplete) orders: their order id sequence carries on as 510 | # before. 511 | # 512 | # Valid values are 'yes', 'true', 'false' and 'no'. The default is 'no'. 513 | 514 | ResetOrderIdsAtStart= 515 | 516 | 517 | # This setting specifies IBC's action when TWS displays the dialog asking for 518 | # confirmation of a request to reset the API order id sequence. 519 | # 520 | # Note that the Gateway never displays this dialog, so this setting is ignored 521 | # for a Gateway session. 522 | # 523 | # Valid values consist of two strings separated by a solidus '/'. The first 524 | # value specifies the action to take when the order id reset request resulted 525 | # from setting ResetOrderIdsAtStart=yes. The second specifies the action to 526 | # take when the order id reset request is a result of the user clicking the 527 | # 'Reset API order ID sequence' button in the API configuration. Each value 528 | # must be one of the following: 529 | # 530 | # 'confirm' 531 | # order ids will be reset 532 | # 533 | # 'reject' 534 | # order ids will not be reset 535 | # 536 | # 'ignore' 537 | # IBC will ignore the dialog. The user must take action. 538 | # 539 | # The default setting is ignore/ignore 540 | 541 | # Examples: 542 | # 543 | # 'confirm/reject' - confirm order id reset only if ResetOrderIdsAtStart=yes 544 | # and reject any user-initiated requests 545 | # 546 | # 'ignore/confirm' - user must decide what to do if ResetOrderIdsAtStart=yes 547 | # and confirm user-initiated requests 548 | # 549 | # 'reject/ignore' - reject order id reset if ResetOrderIdsAtStart=yes but 550 | # allow user to handle user-initiated requests 551 | 552 | ConfirmOrderIdReset= 553 | 554 | 555 | 556 | # ============================================================================= 557 | # 4. TWS Auto-Logoff and Auto-Restart 558 | # ============================================================================= 559 | # 560 | # TWS and Gateway insist on being restarted every day. Two alternative 561 | # automatic options are offered: 562 | # 563 | # - Auto-Logoff: at a specified time, TWS shuts down tidily, without 564 | # restarting. 565 | # 566 | # - Auto-Restart: at a specified time, TWS shuts down and then restarts 567 | # without the user having to re-autheticate. 568 | # 569 | # The normal way to configure the time at which this happens is via the Lock 570 | # and Exit section of the Configuration dialog. Once this time has been 571 | # configured in this way, the setting persists until the user changes it again. 572 | # 573 | # However, there are situations where there is no user available to do this 574 | # configuration, or where there is no persistent storage (for example some 575 | # Docker images). In such cases, the auto-restart or auto-logoff time can be 576 | # set whenever IBC starts with the settings below. 577 | # 578 | # The value, if specified, must be a time in HH:MM AM/PM format, for example 579 | # 08:00 AM or 10:00 PM. Note that there must be a single space between the 580 | # two parts of this value; also that midnight is "12:00 AM" and midday is 581 | # "12:00 PM". 582 | # 583 | # If no value is specified for either setting, the currently configured 584 | # settings will apply. If a value is supplied for one setting, the other 585 | # setting is cleared. If values are supplied for both settings, only the 586 | # auto-restart time is set, and the auto-logoff time is cleared. 587 | # 588 | # Note that for a normal TWS/Gateway installation with persistent storage 589 | # (for example on a desktop computer) the value will be persisted as if the 590 | # user had set it via the configuration dialog. 591 | # 592 | # If you choose to auto-restart, you should take note of the considerations 593 | # described at the link below. Note that where this information mentions 594 | # 'manual authentication', restarting IBC will do the job (IBKR does not 595 | # recognise the existence of IBC in its docuemntation). 596 | # 597 | # https://www.interactivebrokers.com/en/software/tws/twsguide.htm#usersguidebook/configuretws/auto_restart_info.htm 598 | # 599 | # If you use the "RESTART" command via the IBC command server, and IBC is 600 | # running any version of the Gateway (or a version of TWS earlier than 1018), 601 | # note that this will set the Auto-Restart time in Gateway/TWS's configuration 602 | # dialog to the time at which the restart actually happens (which may be up to 603 | # a minute after the RESTART command is issued). To prevent future auto- 604 | # restarts at this time, you must make sure you have set AutoLogoffTime or 605 | # AutoRestartTime to your desired value before running IBC. NB: this does not 606 | # apply to TWS from version 1018 onwards. 607 | 608 | AutoLogoffTime= 609 | 610 | AutoRestartTime= 611 | 612 | 613 | # ============================================================================= 614 | # 5. TWS Tidy Closedown Time 615 | # ============================================================================= 616 | # 617 | # Specifies a time at which TWS will close down tidily, with no restart. 618 | # 619 | # There is little reason to use this setting. It is similar to AutoLogoffTime, 620 | # but can include a day-of-the-week, whereas AutoLogoffTime and AutoRestartTime 621 | # apply every day. So for example you could use ClosedownAt in conjunction with 622 | # AutoRestartTime to shut down TWS on Friday evenings after the markets 623 | # close, without it running on Saturday as well. 624 | # 625 | # To tell IBC to tidily close TWS at a specified time every 626 | # day, set this value to , for example: 627 | # ClosedownAt=22:00 628 | # 629 | # To tell IBC to tidily close TWS at a specified day and time 630 | # each week, set this value to , for example: 631 | # ClosedownAt=Friday 22:00 632 | # 633 | # Note that the day of the week must be specified using your 634 | # default locale. Also note that Java will only accept 635 | # characters encoded to ISO 8859-1 (Latin-1). This means that 636 | # if the day name in your default locale uses any non-Latin-1 637 | # characters you need to encode them using Unicode escapes 638 | # (see http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.3 639 | # for details). For example, to tidily close TWS at 12:00 on 640 | # Saturday where the default locale is Simplified Chinese, 641 | # use the following: 642 | # #ClosedownAt=\u661F\u671F\u516D 12:00 643 | 644 | ClosedownAt= 645 | 646 | 647 | 648 | # ============================================================================= 649 | # 6. Other TWS Settings 650 | # ============================================================================= 651 | 652 | # Accept Incoming Connection 653 | # -------------------------- 654 | # 655 | # If set to 'accept', IBC automatically accepts incoming 656 | # API connection dialogs. If set to 'reject', IBC 657 | # automatically rejects incoming API connection dialogs. If 658 | # set to 'manual', the user must decide whether to accept or reject 659 | # incoming API connection dialogs. The default is 'manual'. 660 | # NB: it is recommended to set this to 'reject', and to explicitly 661 | # configure which IP addresses can connect to the API in TWS's API 662 | # configuration page, as this is much more secure (in this case, no 663 | # incoming API connection dialogs will occur for those IP addresses). 664 | 665 | AcceptIncomingConnectionAction=manual 666 | 667 | 668 | # Allow Blind Trading 669 | # ------------------- 670 | # 671 | # If you attempt to place an order for a contract for which 672 | # you have no market data subscription, TWS displays a dialog 673 | # to warn you against such blind trading. 674 | # 675 | # yes means the dialog is dismissed as though the user had 676 | # clicked the 'Ok' button: this means that you accept 677 | # the risk and want the order to be submitted. 678 | # 679 | # no means the dialog remains on display and must be 680 | # handled by the user. 681 | 682 | AllowBlindTrading=yes 683 | 684 | 685 | # Save Settings on a Schedule 686 | # --------------------------- 687 | # 688 | # You can tell TWS to automatically save its settings on a schedule 689 | # of your choosing. You can specify one or more specific times, 690 | # like this: 691 | # 692 | # SaveTwsSettingsAt=HH:MM [ HH:MM]... 693 | # 694 | # for example: 695 | # SaveTwsSettingsAt=08:00 12:30 17:30 696 | # 697 | # Or you can specify an interval at which settings are to be saved, 698 | # optionally starting at a specific time and continuing until another 699 | # time, like this: 700 | # 701 | #SaveTwsSettingsAt=Every n [{mins | hours}] [hh:mm] [hh:mm] 702 | # 703 | # where the first hh:mm is the start time and the second is the end 704 | # time. If you don't specify the end time, settings are saved regularly 705 | # from the start time till midnight. If you don't specify the start time. 706 | # settings are saved regularly all day, beginning at 00:00. Note that 707 | # settings will always be saved at the end time, even if that is not 708 | # exactly one interval later than the previous time. If neither 'mins' 709 | # nor 'hours' is specified, 'mins' is assumed. Examples: 710 | # 711 | # To save every 30 minutes all day starting at 00:00 712 | #SaveTwsSettingsAt=Every 30 713 | #SaveTwsSettingsAt=Every 30 mins 714 | # 715 | # To save every hour starting at 08:00 and ending at midnight 716 | #SaveTwsSettingsAt=Every 1 hours 08:00 717 | #SaveTwsSettingsAt=Every 1 hours 08:00 00:00 718 | # 719 | # To save every 90 minutes starting at 08:00 up to and including 17:43 720 | #SaveTwsSettingsAt=Every 90 08:00 17:43 721 | 722 | SaveTwsSettingsAt= 723 | 724 | 725 | # Confirm Crypto Currency Orders Automatically 726 | # -------------------------------------------- 727 | # 728 | # When you place an order for a cryptocurrency contract, a dialog is displayed 729 | # asking you to confirm that you want to place the order, and notifying you 730 | # that you are placing an order to trade cryptocurrency with Paxos, a New York 731 | # limited trust company, and not at Interactive Brokers. 732 | # 733 | # transmit means that the order will be placed automatically, and the 734 | # dialog will then be closed 735 | # 736 | # cancel means that the order will not be placed, and the dialog will 737 | # then be closed 738 | # 739 | # manual means that IBC will take no action and the user must deal 740 | # with the dialog 741 | 742 | ConfirmCryptoCurrencyOrders=manual 743 | 744 | 745 | 746 | # ============================================================================= 747 | # 7. Settings Specific to Indian Versions of TWS 748 | # ============================================================================= 749 | 750 | # Indian versions of TWS may display a password expiry 751 | # notification dialog and a NSE Compliance dialog. These can be 752 | # dismissed by setting the following to yes. By default the 753 | # password expiry notice is not dismissed, but the NSE Compliance 754 | # notice is dismissed. 755 | 756 | # Warning: setting DismissPasswordExpiryWarning=yes will mean 757 | # you will not be notified when your password is about to expire. 758 | # You must then take other measures to ensure that your password 759 | # is changed within the expiry period, otherwise IBC will 760 | # not be able to login successfully. 761 | 762 | DismissPasswordExpiryWarning=no 763 | DismissNSEComplianceNotice=yes 764 | 765 | 766 | 767 | # ============================================================================= 768 | # 8. IBC Command Server Settings 769 | # ============================================================================= 770 | 771 | # Do NOT CHANGE THE FOLLOWING SETTINGS unless you 772 | # intend to issue commands to IBC (for example 773 | # using telnet). Note that these settings have nothing to 774 | # do with running programs that use the TWS API. 775 | 776 | # Command Server Port Number 777 | # -------------------------- 778 | # 779 | # The port number that IBC listens on for commands 780 | # such as "STOP". DO NOT set this to the port number 781 | # used for TWS API connections. 782 | # 783 | # The convention is to use 7462 for this port, 784 | # but it must be set to a different value from any other 785 | # IBC instance that might run at the same time. 786 | # 787 | # The default value is 0, which tells IBC not to start 788 | # the command server 789 | 790 | #CommandServerPort=7462 791 | CommandServerPort=0 792 | 793 | 794 | # Permitted Command Sources 795 | # ------------------------- 796 | # 797 | # A comma separated list of IP addresses, or host names, 798 | # which are allowed addresses for sending commands to 799 | # IBC. Commands can always be sent from the 800 | # same host as IBC is running on. 801 | 802 | ControlFrom= 803 | 804 | 805 | # Address for Receiving Commands 806 | # ------------------------------ 807 | # 808 | # Specifies the IP address on which the Command Server 809 | # is to listen. For a multi-homed host, this can be used 810 | # to specify that connection requests are only to be 811 | # accepted on the specified address. The default is to 812 | # accept connection requests on all local addresses. 813 | 814 | BindAddress= 815 | 816 | 817 | # Command Prompt 818 | # -------------- 819 | # 820 | # The specified string is output by the server when 821 | # the connection is first opened and after the completion 822 | # of each command. This can be useful if sending commands 823 | # using an interactive program such as telnet. The default 824 | # is that no prompt is output. 825 | # For example: 826 | # 827 | # CommandPrompt=> 828 | 829 | CommandPrompt= 830 | 831 | 832 | # Suppress Command Server Info Messages 833 | # ------------------------------------- 834 | # 835 | # Some commands can return intermediate information about 836 | # their progress. This setting controls whether such 837 | # information is sent. The default is that such information 838 | # is not sent. 839 | 840 | SuppressInfoMessages=yes 841 | 842 | 843 | 844 | # ============================================================================= 845 | # 9. Diagnostic Settings 846 | # ============================================================================= 847 | # 848 | # IBC can log information about the structure of windows 849 | # displayed by TWS. This information is useful when adding 850 | # new features to IBC or when behaviour is not as expected. 851 | # 852 | # The logged information shows the hierarchical organisation 853 | # of all the components of the window, and includes the 854 | # current values of text boxes and labels. 855 | # 856 | # Note that this structure logging has a small performance 857 | # impact, and depending on the settings can cause the logfile 858 | # size to be significantly increased. It is therefore 859 | # recommended that the LogStructureWhen setting be set to 860 | # 'never' (the default) unless there is a specific reason 861 | # that this information is needed. 862 | 863 | 864 | # Scope of Structure Logging 865 | # -------------------------- 866 | # 867 | # The LogStructureScope setting indicates which windows are 868 | # eligible for structure logging: 869 | # 870 | # - (default value) if set to 'known', only windows that 871 | # IBC recognizes are eligible - these are windows that 872 | # IBC has some interest in monitoring, usually to take 873 | # some action on the user's behalf; 874 | # 875 | # - if set to 'unknown', only windows that IBC does not 876 | # recognize are eligible. Most windows displayed by 877 | # TWS fall into this category; 878 | # 879 | # - if set to 'untitled', only windows that IBC does not 880 | # recognize and that have no title are eligible. These 881 | # are usually message boxes or similar small windows, 882 | # 883 | # - if set to 'all', then every window displayed by TWS 884 | # is eligible. 885 | # 886 | 887 | LogStructureScope=known 888 | 889 | 890 | # When to Log Window Structure 891 | # ---------------------------- 892 | # 893 | # The LogStructureWhen setting specifies the circumstances 894 | # when eligible TWS windows have their structure logged: 895 | # 896 | # - if set to 'open' or 'yes' or 'true', IBC logs the 897 | # structure of an eligible window the first time it 898 | # is encountered; 899 | # 900 | # - if set to 'openclose', the structure is logged every 901 | # time an eligible window is opened or closed; 902 | # 903 | # - if set to 'activate', the structure is logged every 904 | # time an eligible window is made active; 905 | # 906 | # - (default value) if set to 'never' or 'no' or 'false', 907 | # structure information is never logged. 908 | # 909 | 910 | LogStructureWhen=never 911 | 912 | 913 | # DEPRECATED SETTING 914 | # ------------------ 915 | # 916 | # LogComponents - THIS SETTING WILL BE REMOVED IN A FUTURE 917 | # RELEASE 918 | # 919 | # If LogComponents is set to any value, this is equivalent 920 | # to setting LogStructureWhen to that same value and 921 | # LogStructureScope to 'all': the actual values of those 922 | # settings are ignored. The default is that the values 923 | # of LogStructureScope and LogStructureWhen are honoured. 924 | 925 | #LogComponents= 926 | --------------------------------------------------------------------------------