├── .env.template ├── .github └── workflows │ └── python-package.yaml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── docker-compose.yaml ├── img ├── option-strategy.png ├── slack-example.png └── trading-workflow.png ├── requirements.txt ├── setup.cfg ├── src ├── market_watcher │ ├── __init__.py │ ├── common.py │ ├── config.py │ ├── ib_client │ │ ├── MANIFEST.in │ │ ├── README.md │ │ ├── __init__.py │ │ ├── examples │ │ │ └── Testbed │ │ │ │ ├── AvailableAlgoParams.py │ │ │ │ ├── ContractSamples.py │ │ │ │ ├── FaAllocationSamples.py │ │ │ │ ├── OrderSamples.py │ │ │ │ ├── Program.py │ │ │ │ └── ScannerSubscriptionSamples.py │ │ ├── ibapi │ │ │ ├── __init__.py │ │ │ ├── account_summary_tags.py │ │ │ ├── client.py │ │ │ ├── comm.py │ │ │ ├── commission_report.py │ │ │ ├── common.py │ │ │ ├── connection.py │ │ │ ├── contract.py │ │ │ ├── decoder.py │ │ │ ├── enum_implem.py │ │ │ ├── errors.py │ │ │ ├── execution.py │ │ │ ├── message.py │ │ │ ├── news.py │ │ │ ├── object_implem.py │ │ │ ├── order.py │ │ │ ├── order_condition.py │ │ │ ├── order_state.py │ │ │ ├── orderdecoder.py │ │ │ ├── reader.py │ │ │ ├── scanner.py │ │ │ ├── server_versions.py │ │ │ ├── softdollartier.py │ │ │ ├── tag_value.py │ │ │ ├── ticktype.py │ │ │ ├── utils.py │ │ │ └── wrapper.py │ │ ├── setup.py │ │ ├── test.py │ │ ├── tests │ │ │ ├── manual.py │ │ │ ├── test_account_summary_tags.py │ │ │ ├── test_comm.py │ │ │ ├── test_enum_implem.py │ │ │ ├── test_order_conditions.py │ │ │ └── test_utils.py │ │ └── tox.ini │ ├── market_watcher_cli.py │ ├── notifier.py │ ├── research │ │ ├── target_stocks.yaml │ │ └── target_stocks_short.yaml │ ├── state.json │ └── version.py └── setup.py └── tests └── test_intestment_opportunity.py /.env.template: -------------------------------------------------------------------------------- 1 | # Market Watcher configuration 2 | # This file is a template of how .env file should look like 3 | 4 | # Update interval for mails (in seconds) 5 | UPDATE_TIMEOUT=5 6 | 7 | # Daily P&N to trigger notification for Long straddle strategy 8 | LONG_THRESHOLD=0.02 9 | 10 | # Daily P&N to trigger notification for Short straddle strategy 11 | SHORT_THRESHOLD=0.005 12 | 13 | # Email and SMTP configuration (example for Gmail) 14 | SMTP_HOSTNAME=smtp.gmail.com 15 | SMTP_PORT=465 16 | SMTP_USERNAME=mymail@gmail.com 17 | SMTP_PASSWORD=password12345! 18 | 19 | EMAIL_SENDER=sender@gmail.com 20 | EMAIL_RECIPIENTS=user1@gmail.com;user2@gmail.com 21 | 22 | # Configure Slack app: https://api.slack.com/messaging/webhooks 23 | # Webhook link for channel where to post long straddle opportunities 24 | SLACK_LONG_WEBHOOK=https://hooks.slack.com/services/example-webhook-link 25 | 26 | # Webhook link for channel where to post short straddle opportunities 27 | SLACK_SHORT_WEBHOOK=https://hooks.slack.com/services/example-webhook-link 28 | 29 | -------------------------------------------------------------------------------- /.github/workflows/python-package.yaml: -------------------------------------------------------------------------------- 1 | name: MarketWatcher Python package 2 | 3 | on: push 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | python-version: [3.8, 3.9] 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Set up Python ${{ matrix.python-version }} 16 | uses: actions/setup-python@v2 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | - name: Install dependencies 20 | run: | 21 | python -m pip install --upgrade pip 22 | python -m pip install flake8 pytest wheel 23 | # install requirements 24 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 25 | # install IBKR python api client 26 | cd src/market_watcher/ib_client && python setup.py bdist_wheel && cd ../../.. 27 | python -m pip install src/market_watcher/ib_client/dist/ibapi-9.76.1-py3-none-any.whl 28 | # Installing market_watcher_cli tool 29 | python -m pip install --editable src/. 30 | - name: Lint with flake8 31 | run: | 32 | flake8 src tests 33 | - name: Test with pytest 34 | run: | 35 | pytest 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Virtual environment 2 | .venv 3 | 4 | # Python files 5 | .pyc 6 | __pycache__ 7 | __pycache__/* 8 | *.egg-info 9 | build 10 | dist 11 | 12 | # Log files 13 | log/* 14 | *.log 15 | log.* 16 | 17 | # IDEs 18 | .vscode 19 | *.pyproj 20 | 21 | # Environment variables file 22 | .env 23 | 24 | # PyCharm files 25 | .idea 26 | 27 | # Pytest 28 | .pytest_cache -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8-slim 2 | 3 | # Setting working directory 4 | WORKDIR /app 5 | 6 | # Copying requirements before other files for faster sequential builds 7 | COPY requirements.txt ./ 8 | 9 | # Installing 3rd party python requirements 10 | RUN pip install --no-cache-dir -r requirements.txt 11 | 12 | # Copying the source code 13 | COPY . . 14 | 15 | # Installing IBRK Python API client 16 | RUN cd src/market_watcher/ib_client && \ 17 | python setup.py bdist_wheel && \ 18 | cd ../../.. 19 | RUN pip install src/market_watcher/ib_client/dist/ibapi-9.76.1-py3-none-any.whl 20 | 21 | 22 | # Installing market_watcher_cli tool 23 | RUN pip install --editable src/. 24 | 25 | CMD ["bash"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 MCF Long Short 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Interactive Brokers: Volatility trading with options 2 | 3 | [![MarketWatcher Python package](https://github.com/mcf-long-short/ibkr-options-volatility-trading/actions/workflows/python-package.yaml/badge.svg)](https://github.com/mcf-long-short/ibkr-options-volatility-trading/actions/workflows/python-package.yaml) 4 | 5 | ## Introduction 6 | 7 | `Volatility trading` using `equity options` and `long/short straddle` option strategies combined with a `momentum strategy` to profit from a high/low volatility on a daily level in the US equities. 8 | 9 | For testing that strategy Interactive Brokers Trader Workstation was used with paper trading account. In order to quickly react on the market moves, simple `market watcher`/`trading bot` was implemented. `MarketWatcher bot` uses data from [Yahoo Finance](https://github.com/JECSand/yahoofinancials) and [Python TWS API](https://tradersacademy.online/trading-courses/python-tws-api). 10 | 11 | This repository represents group project work for implementing option trading strategies (course in Financial Derivatives for advanced degree [Masters in Computational Finance, Union University](http://mcf.raf.edu.rs/)). 12 | 13 | ## Trading strategy 14 | 15 | Our trading strategy is based on `volatility trading`. To exploit and profit from both the high and low volatility, we day traded - getting in and out of the positions in the same trading session. Some positions were held overnight as well. 16 | 17 | In order to profit on a high or low volatility in the equities we traded `options` using `long straddle` and `short stradle` strategy respectively. For intuition about these strategies we highly recommend watching following videos: 18 | 19 | - [Long Straddle](https://www.youtube.com/watch?v=4UlIMmXhjsc) 20 | - [Short Straddle](https://www.youtube.com/watch?v=Lsk9ppb8ffs) 21 | 22 |

23 | 24 |

25 | 26 | ## Trading Workflow 27 | 28 | Firstly, we started with doing basic market research and finding `historically the most and least volatile stocks` in the past `1 week`, `2 week` and a `1 month` period in the `Russel 3000 index` (in order to capture both the large-cap and the small-cap companies). From those stocks we chose the top 50 stocks with highest and the top 50 lowest historical weighted volatility: 29 | `Weighted volatility = 0.5 * Vol_1_week + 0.3 * Vol_2_week + 0.2 * Vol_1_month`. 30 | 31 | Using naive expecation for the future volatility based on the historical volatility, combined with the momentum detected from the daily P&L for the target companies, we enter into `long straddle` or `short straddle` positions. 32 | 33 | Our MarketWatcher bot was alerting us for all potential investment opportunities. From the provided list of `target stocks`, trading bot was sending us `alerts via Email and Slack`, when target stocks have `reached configured daily P&L threashold`: 34 | 35 | - Long straddle - stocks with absolute daily P&L that are `greater` than `5% P&L threashold` 36 | - Short straddle - stocks with absolute daily P&L that are `lower` than `0.5% P&L threashold` 37 | 38 | Upon getting alerted by the MarketWatcher bot, we `hand picked` into which long and short straddle positions to enter. 39 | 40 |

41 | 42 |

43 | 44 | ### Future improvements 45 | 46 | In the next iteration of development we intend to ship more IBKR TWS API functionality so that daily P&L is fetched from there. We would also like to automate the whole process, by first adding support to our trading bot to place orders on TWS and after that to fully implement algotrading strategy. 47 | 48 | ## How to use MarketWatcher trading bot? 49 | 50 | After cloning the repo, rename the `env.template` file to `.env` and populate it with your settings: `email notification interval time`, `daily P&L threashold values`, `email settings`, `slack channel webhook links`, ... 51 | 52 | Create `.yaml file with target stocks` (ticker and long/short straddle strategy indicator for that ticker). MarketWatcher engine listens to real-time ticker data feed and notifies you when there is a `potential opportunity for entering into long or short straddle options position`, with that stock as an underlying. 53 | 54 | You can control MarketWatcher trading bot using simple `CLI`. After bot is started it will send us an email/slack messages for each investment opportunity it finds. 55 | 56 | CLI commands: 57 | 58 | ```bash 59 | market_watcher_cli --help 60 | market_watcher_cli start 61 | market_watcher_cli stop 62 | market_watcher_cli config 63 | ``` 64 | 65 | Example CLI output upon starting MarketWatcher engine: 66 | 67 | ``` 68 | ______ _ _ _ _ _ 69 | | ___ \ | | _ | || || | _ | | 70 | | | _ | | ____ ____| | _ ____| |_| || || | ____| |_ ____| | _ ____ ____ 71 | | || || |/ _ |/ ___) | / ) _ ) _) ||_|| |/ _ | _)/ ___) || \ / _ )/ ___) 72 | | || || ( ( | | | | |< ( (/ /| |_| |___| ( ( | | |_( (___| | | ( (/ /| | 73 | |_||_||_|\_||_|_| |_| \_)____)\___)______|\_||_|\___)____)_| |_|\____)_| 74 | 75 | MarketWatcher tool for finding investiments opportunities on Interacive Brokers 76 | for volatility trading on equity market using long and short options strategy. 77 | 78 | version: v0.1.0 79 | 80 | 81 | 82 | 83 | Starting MarketWatcher... 84 | MarketWatcher started 85 | Reading target stocks from file: src/market_watcher/research/target_stocks.yaml 86 | Instantiating email notifier... 87 | Instantiating slack notifier... 88 | Instantiating MarketWatcher and running the engine 89 | ``` 90 | 91 | You can run cli tool from: 92 | 93 | - Docker container (using docker-compose) 94 | - Python virtual environment 95 | 96 | ### 1. Running in Docker 97 | 98 | Building docker image: 99 | 100 | ```bash 101 | docker build -t ibkr --rm . 102 | ``` 103 | 104 | Running CLI from docker-compose: 105 | 106 | ```bash 107 | docker-compose run --rm mw market_watcher_cli start 108 | ``` 109 | 110 | ### 2. Running in virtual environment 111 | 112 | Install and activatre virtual environment for python 3.x: 113 | 114 | ```bash 115 | python3 -m venv /path/to/new/virtual/environment 116 | source .venv/bin/activate 117 | ``` 118 | 119 | Install requirements: 120 | 121 | ```bash 122 | pip install -r requirements.txt 123 | ``` 124 | 125 | Install `ibapi` (client for IBKR TWS API): 126 | 127 | ```bash 128 | # Installing ibapi 129 | cd src/market_watcher/ib_client 130 | python setup.py bdist_wheel 131 | pip install src/ib_client/dist/ibapi-9.76.1-py3-none-any.whl 132 | 133 | # Running ibapi tests 134 | python -m unittest discover -s src/ib_client/tests 135 | ``` 136 | 137 | ### Installing MarketWatcher trading bot: 138 | 139 | ```bash 140 | cd src 141 | pip install --editable . 142 | 143 | market_watcher_cli start --help 144 | ``` 145 | 146 | ### Slack requirements 147 | 148 | In order to receive the investment opportunities from MarketWatcher bot as [Slack](https://slack.com/intl/en-rs/) messages, you must first create a new Slack app in your workspace and enable [Incoming-Webhooks](https://api.slack.com/messaging/webhooks). We expect to have two separate channels - one for receiving opportunities for a long straddle and the other for the short straddle strategies (see image bellow). Links for Slack webhooks need to be put in the `.env` file. 149 | 150 |

151 | 152 |

153 | 154 | ### IBKR requirements 155 | 156 | Requirements for enabling [Trader Workstation API](https://interactivebrokers.github.io/tws-api/): 157 | 158 | - Download and install [Trader Workstation](https://www.interactivebrokers.com/en/index.php?f=14099#tws-software) 159 | - Download and install [IB Gateway](https://www.interactivebrokers.com/en/index.php?f=16457) 160 | - Enable following setting in the TWS (`File -> Global Configuration -> API -> Settings`): 161 | 1. Enable ActiveX and Socket Clients 162 | 2. Read-Only API (no order placing, only data reading for our scraping bot) 163 | 3. Socket port: 7497 (Paper)/7496(Live) 164 | 4. Allow connections from localhost only 165 | 5. Create API message log file 166 | 6. Logging Level: Error 167 | - Download [TWS API source code](https://interactivebrokers.github.io/#) 168 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | mw: 4 | build: . 5 | image: mw 6 | stdin_open: true 7 | tty: true 8 | -------------------------------------------------------------------------------- /img/option-strategy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcf-long-short/ibkr-options-volatility-trading/f63c03b74439488d723fecdbc7fd3edd51f7bbbd/img/option-strategy.png -------------------------------------------------------------------------------- /img/slack-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcf-long-short/ibkr-options-volatility-trading/f63c03b74439488d723fecdbc7fd3edd51f7bbbd/img/slack-example.png -------------------------------------------------------------------------------- /img/trading-workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcf-long-short/ibkr-options-volatility-trading/f63c03b74439488d723fecdbc7fd3edd51f7bbbd/img/trading-workflow.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | appdirs==1.4.4 2 | attrs==21.2.0 3 | black==21.5b1 4 | click==8.0.1 5 | iniconfig==1.1.1 6 | mypy-extensions==0.4.3 7 | packaging==20.9 8 | pathspec==0.8.1 9 | pluggy==0.13.1 10 | py==1.10.0 11 | pyparsing==2.4.7 12 | pytest==6.2.4 13 | python-dotenv==0.17.1 14 | PyYAML==5.4.1 15 | regex==2021.4.4 16 | slack-sdk==3.5.1 17 | toml==0.10.2 18 | yahoofinancials==1.7 19 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 120 3 | exclude = src/market_watcher/ib_client -------------------------------------------------------------------------------- /src/market_watcher/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcf-long-short/ibkr-options-volatility-trading/f63c03b74439488d723fecdbc7fd3edd51f7bbbd/src/market_watcher/__init__.py -------------------------------------------------------------------------------- /src/market_watcher/common.py: -------------------------------------------------------------------------------- 1 | import time 2 | from enum import Enum 3 | 4 | import yaml 5 | 6 | from yahoofinancials import YahooFinancials 7 | 8 | from market_watcher.config import context 9 | 10 | 11 | class STRATEGIES(Enum): 12 | LONG_STRADDLE = "long straddle" 13 | SHORT_STRADDLE = "short straddle" 14 | 15 | 16 | def get_terget_stocks(file_path): 17 | """Reads target stocks for long/short straddle strategies.""" 18 | try: 19 | with open(file_path) as f: 20 | target_stocks = yaml.load(f, Loader=yaml.FullLoader) 21 | return target_stocks 22 | except Exception as e: 23 | print(e) 24 | 25 | 26 | def get_email_config(): 27 | """Returns email notifier related configuration.""" 28 | email_config = {} 29 | email_config["hostname"] = context.config["SMTP_HOSTNAME"] 30 | email_config["port"] = context.config["SMTP_PORT"] 31 | email_config["username"] = context.config["SMTP_USERNAME"] 32 | email_config["password"] = context.config["SMTP_PASSWORD"] 33 | email_config["sender"] = context.config["EMAIL_SENDER"] 34 | email_config["recipients"] = context.config["EMAIL_RECIPIENTS"] 35 | return email_config 36 | 37 | 38 | def get_pnl_threashold_config(): 39 | """Returns strategy alert configuration.""" 40 | pnl_threashold = {} 41 | pnl_threashold["LONG_THRESHOLD"] = float(context.config["LONG_THRESHOLD"]) 42 | pnl_threashold["SHORT_THRESHOLD"] = float(context.config["SHORT_THRESHOLD"]) 43 | return pnl_threashold 44 | 45 | 46 | def get_slack_config(): 47 | slack_config = {} 48 | slack_config["long url"] = context.config["SLACK_LONG_WEBHOOK"] 49 | slack_config["short url"] = context.config["SLACK_SHORT_WEBHOOK"] 50 | return slack_config 51 | 52 | 53 | class MarketWatcherEngine: 54 | """MarketWatcher core engine logic for scarping financial data.""" 55 | 56 | def __init__(self, target_stocks=None, notifiers=None): 57 | self.target_stocks = target_stocks 58 | self.notifiers = notifiers 59 | 60 | pnl_threashold_config = get_pnl_threashold_config() 61 | self.long_threshlold = pnl_threashold_config["LONG_THRESHOLD"] 62 | self.short_threshlold = pnl_threashold_config["SHORT_THRESHOLD"] 63 | 64 | self.daily_pnls = None 65 | 66 | def search_for_intestment_opportunities(self): 67 | # Update interval for sending email notifications 68 | update_timeout = int(context.config["UPDATE_TIMEOUT"]) 69 | 70 | # Remaining time until email alert 71 | remaining_seconds = update_timeout 72 | 73 | while context.running: 74 | if remaining_seconds > 0: 75 | remaining_seconds -= 1 76 | time.sleep(1) 77 | else: 78 | remaining_seconds = update_timeout 79 | self.process_latest_market_movements() 80 | 81 | def process_latest_market_movements(self): 82 | """Goes through each target stock and checks if there is a 83 | potential invetment opportunity. 84 | 85 | If the opportunity is found trader is notified about it though email 86 | noticication stating which options trading strategy trader should 87 | implement. 88 | """ 89 | self.daily_pnls = self.get_daily_pnls() 90 | investment_opportunities = [] 91 | for ticker in self.target_stocks: 92 | if not self.daily_pnls[ticker]: 93 | continue 94 | 95 | if self.is_investment_opportunity( 96 | self.target_stocks[ticker]["strategy"], abs(self.daily_pnls[ticker]) 97 | ): 98 | investment_opportunities.append(ticker) 99 | 100 | investment_data = self.get_investment_data(investment_opportunities) 101 | 102 | self.notify(investment_data) 103 | 104 | return investment_data 105 | 106 | def notify(self, investment_data): 107 | """Sends investment updates to subscribed notifiers.""" 108 | if self.notifiers: 109 | for notifier in self.notifiers: 110 | notifier.notify(investment_data) 111 | 112 | def get_daily_pnls(self): 113 | """Returns daily pnls""" 114 | target_stocks = list(self.target_stocks.keys()) 115 | yahoo_financials_target_stocks = YahooFinancials(target_stocks) 116 | return yahoo_financials_target_stocks.get_current_percent_change() 117 | 118 | def get_investment_data(self, investment_opportunities): 119 | """Returns two dictionaries that contain investment data for both strategies""" 120 | long_straddle = {} 121 | short_straddle = {} 122 | 123 | for ticker in investment_opportunities: 124 | if STRATEGIES.LONG_STRADDLE.value == self.target_stocks[ticker]["strategy"]: 125 | long_straddle[ticker] = self.daily_pnls[ticker] 126 | elif ( 127 | STRATEGIES.SHORT_STRADDLE.value 128 | == self.target_stocks[ticker]["strategy"] 129 | ): 130 | short_straddle[ticker] = self.daily_pnls[ticker] 131 | 132 | return { 133 | STRATEGIES.LONG_STRADDLE.value: long_straddle, 134 | STRATEGIES.SHORT_STRADDLE.value: short_straddle, 135 | } 136 | 137 | def is_investment_opportunity(self, strategy, abs_daily_pnl): 138 | """Check if the stock is applicable for one of the options trading strategies.""" 139 | if STRATEGIES.LONG_STRADDLE.value == strategy: 140 | if abs_daily_pnl > self.long_threshlold: 141 | return True 142 | elif STRATEGIES.SHORT_STRADDLE.value == strategy: 143 | if abs_daily_pnl < self.short_threshlold: 144 | return True 145 | 146 | return False 147 | -------------------------------------------------------------------------------- /src/market_watcher/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | from dataclasses import dataclass 4 | from typing import Any, Dict 5 | 6 | from dotenv import dotenv_values 7 | 8 | 9 | @dataclass 10 | class Context: 11 | """Global context for MarketWatcher trading bot.""" 12 | 13 | # Config from environment variables 14 | config: Dict[str, Any] = None 15 | 16 | # MarketWatcher state path 17 | state_path: str = "" 18 | 19 | @property 20 | def state(self) -> Dict[str, Any]: 21 | """Returns entire state object from json file.""" 22 | with open(self.state_path, "r") as f: 23 | state = json.load(f) 24 | return state 25 | 26 | @property 27 | def running(self) -> bool: 28 | """Reads and returns running flag from state json.""" 29 | with open(self.state_path, "r") as f: 30 | state = json.load(f) 31 | return state["running"] 32 | 33 | @running.setter 34 | def running(self, new_state: bool) -> None: 35 | """Sets the running flag in the state json.""" 36 | 37 | old_state = self.state 38 | 39 | # Check if state needs updating 40 | if old_state["running"] == new_state: 41 | state = "running" if new_state else "stopped" 42 | raise ValueError(f"MarketWatcher bot is already {state}.") 43 | 44 | # Store new running flag value to state json file 45 | with open(self.state_path, "w") as f: 46 | old_state["running"] = new_state 47 | json.dump(obj=old_state, fp=f) 48 | 49 | 50 | # Environment variable settings from .env files 51 | config = { 52 | **dotenv_values(".env"), # load shared development variables 53 | **os.environ, # override loaded values with environment variables 54 | } 55 | 56 | 57 | context = Context(config=config, state_path=r"src/market_watcher/state.json") 58 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include ibapi/*.py 2 | include README.md 3 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/README.md: -------------------------------------------------------------------------------- 1 | A couple of things/definitions/conventions: 2 | * a *low level message* is some data prefixed with its size 3 | * a *high level message* is a list of fields separated by the NULL character; the fields are all strings; the message ID is the first field, the come others whose number and semantics depend on the message itself 4 | * a *request* is a message from client to TWS/IBGW (IB Gateway) 5 | * an *answer* is a message from TWS/IBGW to client 6 | 7 | 8 | How the code is organized: 9 | * *comm* module: has tools that know how to handle (eg: encode/decode) low and high level messages 10 | * *Connection*: glorified socket 11 | * *Reader*: thread that uses Connection to read packets, transform to low level messages and put in a Queue 12 | * *Decoder*: knows how to take a low level message and decode into high level message 13 | * *Client*: 14 | + knows to send requests 15 | + has the message loop which takes low level messages from Queue and uses Decoder to tranform into high level message with which it then calls the corresponding Wrapper method 16 | * *Wrapper*: class that needs to be subclassed by the user so that it can get the incoming messages 17 | 18 | 19 | The info/data flow is: 20 | 21 | * receiving: 22 | + *Connection.recv_msg()* (which is essentially a socket) receives the packets 23 | - uses *Connection._recv_all_msgs()* which tries to combine smaller packets into bigger ones based on some trivial heuristic 24 | + *Reader.run()* uses *Connection.recv_msg()* to get a packet and then uses *comm.read_msg()* to try to make it a low level message. If that can't be done yet (size prefix says so) then it waits for more packets 25 | + if a full low level message is received then it is placed in the Queue (remember this is a standalone thread) 26 | + the main thread runs the *Client.run()* loop which: 27 | - gets a low level message from Queue 28 | - uses *comm.py* to translate into high level message (fields) 29 | - uses *Decoder.interpret()* to act based on that message 30 | + *Decoder.interpret()* will translate the fields into function parameters of the correct type and call with the correct/corresponding method of *Wrapper* class 31 | 32 | * sending: 33 | + *Client* class has methods that implement the _requests_. The user will call those request methods with the needed parameters and *Client* will send them to the TWS/IBGW. 34 | 35 | 36 | Implementation notes: 37 | 38 | * the *Decoder* has two ways of handling a message (esentially decoding the fields) 39 | + some message very neatly map to a function call; meaning that the number of fields and order are the same as the method parameters. For example: Wrapper.tickSize(). In this case a simple mapping is made between the incoming msg id and the Wrapper method: 40 | 41 | IN.TICK_SIZE: HandleInfo(wrap=Wrapper.tickSize), 42 | 43 | + other messages are more complex, depend on version number heavily or need field massaging. In this case the incoming message id is mapped to a processing function that will do all that and call the Wrapper method at the end. For example: 44 | 45 | IN.TICK_PRICE: HandleInfo(proc=processTickPriceMsg), 46 | 47 | 48 | Instalation notes: 49 | 50 | * you can use this to build a source distribution 51 | 52 | python3 setup.py sdist 53 | 54 | * you can use this to build a wheel 55 | 56 | python3 setup.py bdist_wheel 57 | 58 | * you can use this to install the wheel 59 | 60 | python3 -m pip install --user --upgrade dist/ibapi-9.75.1-py3-none-any.whl 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcf-long-short/ibkr-options-volatility-trading/f63c03b74439488d723fecdbc7fd3edd51f7bbbd/src/market_watcher/ib_client/__init__.py -------------------------------------------------------------------------------- /src/market_watcher/ib_client/examples/Testbed/AvailableAlgoParams.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | from ibapi.object_implem import Object 7 | from ibapi.tag_value import TagValue 8 | from ibapi.order import Order 9 | 10 | 11 | class AvailableAlgoParams(Object): 12 | 13 | # ! [scale_params] 14 | @staticmethod 15 | def FillScaleParams(baseOrder: Order, scaleInitLevelSize: int, scaleSubsLevelSize: int, scaleRandomPercent: bool, 16 | scalePriceIncrement: float, scalePriceAdjustValue: float, scalePriceAdjustInterval: int, 17 | scaleProfitOffset: float, scaleAutoReset: bool, scaleInitPosition: int, scaleInitFillQty: int): 18 | baseOrder.scaleInitLevelSize = scaleInitLevelSize # Initial Component Size 19 | baseOrder.scaleSubsLevelSize = scaleSubsLevelSize # Subsequent Comp. Size 20 | baseOrder.scaleRandomPercent = scaleRandomPercent # Randomize size by +/-55% 21 | baseOrder.scalePriceIncrement = scalePriceIncrement # Price Increment 22 | 23 | # Auto Price adjustment 24 | baseOrder.scalePriceAdjustValue = scalePriceAdjustValue # starting price by 25 | baseOrder.scalePriceAdjustInterval = scalePriceAdjustInterval # in seconds 26 | 27 | # Profit Orders 28 | baseOrder.scaleProfitOffset = scaleProfitOffset # Create profit taking order Profit Offset 29 | baseOrder.scaleAutoReset = scaleAutoReset # Restore size after taking profit 30 | baseOrder.scaleInitPosition = scaleInitPosition # Initial Position 31 | baseOrder.scaleInitFillQty = scaleInitFillQty # Filled initial Component Size 32 | # ! [scale_params] 33 | 34 | # ! [arrivalpx_params] 35 | @staticmethod 36 | def FillArrivalPriceParams(baseOrder: Order, maxPctVol: float, 37 | riskAversion: str, startTime: str, endTime: str, 38 | forceCompletion: bool, allowPastTime: bool, 39 | monetaryValue: float): 40 | baseOrder.algoStrategy = "ArrivalPx" 41 | baseOrder.algoParams = [] 42 | baseOrder.algoParams.append(TagValue("maxPctVol", maxPctVol)) 43 | baseOrder.algoParams.append(TagValue("riskAversion", riskAversion)) 44 | baseOrder.algoParams.append(TagValue("startTime", startTime)) 45 | baseOrder.algoParams.append(TagValue("endTime", endTime)) 46 | baseOrder.algoParams.append(TagValue("forceCompletion", 47 | int(forceCompletion))) 48 | baseOrder.algoParams.append(TagValue("allowPastEndTime", 49 | int(allowPastTime))) 50 | baseOrder.algoParams.append(TagValue("monetaryValue", monetaryValue)) 51 | 52 | # ! [arrivalpx_params] 53 | 54 | 55 | # ! [darkice_params] 56 | @staticmethod 57 | def FillDarkIceParams(baseOrder: Order, displaySize: int, startTime: str, 58 | endTime: str, allowPastEndTime: bool, 59 | monetaryValue: float): 60 | baseOrder.algoStrategy = "DarkIce" 61 | baseOrder.algoParams = [] 62 | baseOrder.algoParams.append(TagValue("displaySize", displaySize)) 63 | baseOrder.algoParams.append(TagValue("startTime", startTime)) 64 | baseOrder.algoParams.append(TagValue("endTime", endTime)) 65 | baseOrder.algoParams.append(TagValue("allowPastEndTime", 66 | int(allowPastEndTime))) 67 | baseOrder.algoParams.append(TagValue("monetaryValue", monetaryValue)) 68 | 69 | # ! [darkice_params] 70 | 71 | 72 | # ! [pctvol_params] 73 | @staticmethod 74 | def FillPctVolParams(baseOrder: Order, pctVol: float, startTime: str, 75 | endTime: str, noTakeLiq: bool, 76 | monetaryValue: float): 77 | baseOrder.algoStrategy = "PctVol" 78 | baseOrder.algoParams = [] 79 | baseOrder.algoParams.append(TagValue("pctVol", pctVol)) 80 | baseOrder.algoParams.append(TagValue("startTime", startTime)) 81 | baseOrder.algoParams.append(TagValue("endTime", endTime)) 82 | baseOrder.algoParams.append(TagValue("noTakeLiq", int(noTakeLiq))) 83 | baseOrder.algoParams.append(TagValue("monetaryValue", monetaryValue)) 84 | 85 | # ! [pctvol_params] 86 | 87 | 88 | # ! [twap_params] 89 | @staticmethod 90 | def FillTwapParams(baseOrder: Order, strategyType: str, startTime: str, 91 | endTime: str, allowPastEndTime: bool, 92 | monetaryValue: float): 93 | baseOrder.algoStrategy = "Twap" 94 | baseOrder.algoParams = [] 95 | baseOrder.algoParams.append(TagValue("strategyType", strategyType)) 96 | baseOrder.algoParams.append(TagValue("startTime", startTime)) 97 | baseOrder.algoParams.append(TagValue("endTime", endTime)) 98 | baseOrder.algoParams.append(TagValue("allowPastEndTime", 99 | int(allowPastEndTime))) 100 | baseOrder.algoParams.append(TagValue("monetaryValue", monetaryValue)) 101 | 102 | # ! [twap_params] 103 | 104 | 105 | # ! [vwap_params] 106 | @staticmethod 107 | def FillVwapParams(baseOrder: Order, maxPctVol: float, startTime: str, 108 | endTime: str, allowPastEndTime: bool, noTakeLiq: bool, 109 | monetaryValue: float): 110 | baseOrder.algoStrategy = "Vwap" 111 | baseOrder.algoParams = [] 112 | baseOrder.algoParams.append(TagValue("maxPctVol", maxPctVol)) 113 | baseOrder.algoParams.append(TagValue("startTime", startTime)) 114 | baseOrder.algoParams.append(TagValue("endTime", endTime)) 115 | baseOrder.algoParams.append(TagValue("allowPastEndTime", 116 | int(allowPastEndTime))) 117 | baseOrder.algoParams.append(TagValue("noTakeLiq", int(noTakeLiq))) 118 | baseOrder.algoParams.append(TagValue("monetaryValue", monetaryValue)) 119 | 120 | # ! [vwap_params] 121 | 122 | 123 | # ! [ad_params] 124 | @staticmethod 125 | def FillAccumulateDistributeParams(baseOrder: Order, componentSize: int, 126 | timeBetweenOrders: int, randomizeTime20: bool, randomizeSize55: bool, 127 | giveUp: int, catchUp: bool, waitForFill: bool, startTime: str, 128 | endTime: str): 129 | baseOrder.algoStrategy = "AD" 130 | baseOrder.algoParams = [] 131 | baseOrder.algoParams.append(TagValue("componentSize", componentSize)) 132 | baseOrder.algoParams.append(TagValue("timeBetweenOrders", timeBetweenOrders)) 133 | baseOrder.algoParams.append(TagValue("randomizeTime20", 134 | int(randomizeTime20))) 135 | baseOrder.algoParams.append(TagValue("randomizeSize55", 136 | int(randomizeSize55))) 137 | baseOrder.algoParams.append(TagValue("giveUp", giveUp)) 138 | baseOrder.algoParams.append(TagValue("catchUp", int(catchUp))) 139 | baseOrder.algoParams.append(TagValue("waitForFill", int(waitForFill))) 140 | baseOrder.algoParams.append(TagValue("activeTimeStart", startTime)) 141 | baseOrder.algoParams.append(TagValue("activeTimeEnd", endTime)) 142 | 143 | # ! [ad_params] 144 | 145 | 146 | # ! [balanceimpactrisk_params] 147 | @staticmethod 148 | def FillBalanceImpactRiskParams(baseOrder: Order, maxPctVol: float, 149 | riskAversion: str, forceCompletion: bool): 150 | baseOrder.algoStrategy = "BalanceImpactRisk" 151 | baseOrder.algoParams = [] 152 | baseOrder.algoParams.append(TagValue("maxPctVol", maxPctVol)) 153 | baseOrder.algoParams.append(TagValue("riskAversion", riskAversion)) 154 | baseOrder.algoParams.append(TagValue("forceCompletion", 155 | int(forceCompletion))) 156 | 157 | # ! [balanceimpactrisk_params] 158 | 159 | 160 | # ! [minimpact_params] 161 | @staticmethod 162 | def FillMinImpactParams(baseOrder: Order, maxPctVol: float): 163 | baseOrder.algoStrategy = "MinImpact" 164 | baseOrder.algoParams = [] 165 | baseOrder.algoParams.append(TagValue("maxPctVol", maxPctVol)) 166 | 167 | # ! [minimpact_params] 168 | 169 | 170 | # ! [adaptive_params] 171 | @staticmethod 172 | def FillAdaptiveParams(baseOrder: Order, priority: str): 173 | baseOrder.algoStrategy = "Adaptive" 174 | baseOrder.algoParams = [] 175 | baseOrder.algoParams.append(TagValue("adaptivePriority", priority)) 176 | 177 | # ! [adaptive_params] 178 | 179 | # ! [closepx_params] 180 | @staticmethod 181 | def FillClosePriceParams(baseOrder: Order, maxPctVol: float, riskAversion: str, 182 | startTime: str, forceCompletion: bool, 183 | monetaryValue: float): 184 | baseOrder.algoStrategy = "ClosePx" 185 | baseOrder.algoParams = [] 186 | baseOrder.algoParams.append(TagValue("maxPctVol", maxPctVol)) 187 | baseOrder.algoParams.append(TagValue("riskAversion", riskAversion)) 188 | baseOrder.algoParams.append(TagValue("startTime", startTime)) 189 | baseOrder.algoParams.append(TagValue("forceCompletion", int(forceCompletion))) 190 | baseOrder.algoParams.append(TagValue("monetaryValue", monetaryValue)) 191 | 192 | # ! [closepx_params] 193 | 194 | 195 | # ! [pctvolpx_params] 196 | @staticmethod 197 | def FillPriceVariantPctVolParams(baseOrder: Order, pctVol: float, 198 | deltaPctVol: float, minPctVol4Px: float, 199 | maxPctVol4Px: float, startTime: str, 200 | endTime: str, noTakeLiq: bool, 201 | monetaryValue: float): 202 | baseOrder.algoStrategy = "PctVolPx" 203 | baseOrder.algoParams = [] 204 | baseOrder.algoParams.append(TagValue("pctVol", pctVol)) 205 | baseOrder.algoParams.append(TagValue("deltaPctVol", deltaPctVol)) 206 | baseOrder.algoParams.append(TagValue("minPctVol4Px", minPctVol4Px)) 207 | baseOrder.algoParams.append(TagValue("maxPctVol4Px", maxPctVol4Px)) 208 | baseOrder.algoParams.append(TagValue("startTime", startTime)) 209 | baseOrder.algoParams.append(TagValue("endTime", endTime)) 210 | baseOrder.algoParams.append(TagValue("noTakeLiq", int(noTakeLiq))) 211 | baseOrder.algoParams.append(TagValue("monetaryValue", monetaryValue)) 212 | 213 | # ! [pctvolpx_params] 214 | 215 | 216 | # ! [pctvolsz_params] 217 | @staticmethod 218 | def FillSizeVariantPctVolParams(baseOrder: Order, startPctVol: float, 219 | endPctVol: float, startTime: str, 220 | endTime: str, noTakeLiq: bool, 221 | monetaryValue: float): 222 | baseOrder.algoStrategy = "PctVolSz" 223 | baseOrder.algoParams = [] 224 | baseOrder.algoParams.append(TagValue("startPctVol", startPctVol)) 225 | baseOrder.algoParams.append(TagValue("endPctVol", endPctVol)) 226 | baseOrder.algoParams.append(TagValue("startTime", startTime)) 227 | baseOrder.algoParams.append(TagValue("endTime", endTime)) 228 | baseOrder.algoParams.append(TagValue("noTakeLiq", int(noTakeLiq))) 229 | baseOrder.algoParams.append(TagValue("monetaryValue", monetaryValue)) 230 | 231 | # ! [pctvolsz_params] 232 | 233 | 234 | # ! [pctvoltm_params] 235 | @staticmethod 236 | def FillTimeVariantPctVolParams(baseOrder: Order, startPctVol: float, 237 | endPctVol: float, startTime: str, 238 | endTime: str, noTakeLiq: bool, 239 | monetaryValue: float): 240 | baseOrder.algoStrategy = "PctVolTm" 241 | baseOrder.algoParams = [] 242 | baseOrder.algoParams.append(TagValue("startPctVol", startPctVol)) 243 | baseOrder.algoParams.append(TagValue("endPctVol", endPctVol)) 244 | baseOrder.algoParams.append(TagValue("startTime", startTime)) 245 | baseOrder.algoParams.append(TagValue("endTime", endTime)) 246 | baseOrder.algoParams.append(TagValue("noTakeLiq", int(noTakeLiq))) 247 | baseOrder.algoParams.append(TagValue("monetaryValue", monetaryValue)) 248 | # ! [pctvoltm_params] 249 | 250 | # ! [jefferies_vwap_params] 251 | @staticmethod 252 | def FillJefferiesVWAPParams(baseOrder: Order, startTime: str, 253 | endTime: str, relativeLimit: float, 254 | maxVolumeRate: float, excludeAuctions: str, 255 | triggerPrice: float, wowPrice: float, 256 | minFillSize: int, wowOrderPct: float, 257 | wowMode: str, isBuyBack: bool, wowReference: str): 258 | # must be direct-routed to "JEFFALGO" 259 | baseOrder.algoStrategy = "VWAP" 260 | baseOrder.algoParams = [] 261 | baseOrder.algoParams.append(TagValue("StartTime", startTime)) 262 | baseOrder.algoParams.append(TagValue("EndTime", endTime)) 263 | baseOrder.algoParams.append(TagValue("RelativeLimit", relativeLimit)) 264 | baseOrder.algoParams.append(TagValue("MaxVolumeRate", maxVolumeRate)) 265 | baseOrder.algoParams.append(TagValue("ExcludeAuctions", excludeAuctions)) 266 | baseOrder.algoParams.append(TagValue("TriggerPrice", triggerPrice)) 267 | baseOrder.algoParams.append(TagValue("WowPrice", wowPrice)) 268 | baseOrder.algoParams.append(TagValue("MinFillSize", minFillSize)) 269 | baseOrder.algoParams.append(TagValue("WowOrderPct", wowOrderPct)) 270 | baseOrder.algoParams.append(TagValue("WowMode", wowMode)) 271 | baseOrder.algoParams.append(TagValue("IsBuyBack", int(isBuyBack))) 272 | baseOrder.algoParams.append(TagValue("WowReference", wowReference)) 273 | # ! [jefferies_vwap_params] 274 | 275 | # ! [csfb_inline_params] 276 | @staticmethod 277 | def FillCSFBInlineParams(baseOrder: Order, startTime: str, 278 | endTime: str, execStyle: str, 279 | minPercent: int, maxPercent: int, 280 | displaySize: int, auction: str, 281 | blockFinder: bool, blockPrice: float, 282 | minBlockSize: int, maxBlockSize: int, iWouldPrice: float): 283 | # must be direct-routed to "CSFBALGO" 284 | baseOrder.algoStrategy = "INLINE" 285 | baseOrder.algoParams = [] 286 | baseOrder.algoParams.append(TagValue("StartTime", startTime)) 287 | baseOrder.algoParams.append(TagValue("EndTime", endTime)) 288 | baseOrder.algoParams.append(TagValue("ExecStyle", execStyle)) 289 | baseOrder.algoParams.append(TagValue("MinPercent", minPercent)) 290 | baseOrder.algoParams.append(TagValue("MaxPercent", maxPercent)) 291 | baseOrder.algoParams.append(TagValue("DisplaySize", displaySize)) 292 | baseOrder.algoParams.append(TagValue("Auction", auction)) 293 | baseOrder.algoParams.append(TagValue("BlockFinder", int(blockFinder))) 294 | baseOrder.algoParams.append(TagValue("BlockPrice", blockPrice)) 295 | baseOrder.algoParams.append(TagValue("MinBlockSize", minBlockSize)) 296 | baseOrder.algoParams.append(TagValue("MaxBlockSize", maxBlockSize)) 297 | baseOrder.algoParams.append(TagValue("IWouldPrice", iWouldPrice)) 298 | # ! [csfb_inline_params] 299 | 300 | # ! [qbalgo_strobe_params] 301 | @staticmethod 302 | def FillQBAlgoInLineParams(baseOrder: Order, startTime: str, 303 | endTime: str, duration: float, 304 | benchmark: str, percentVolume: float, 305 | noCleanUp: bool): 306 | # must be direct-routed to "QBALGO" 307 | baseOrder.algoStrategy = "STROBE" 308 | baseOrder.algoParams = [] 309 | baseOrder.algoParams.append(TagValue("StartTime", startTime)) 310 | baseOrder.algoParams.append(TagValue("EndTime", endTime)) 311 | #This example uses endTime instead of duration 312 | #baseOrder.algoParams.append(TagValue("Duration", str(duration))) 313 | baseOrder.algoParams.append(TagValue("Benchmark", benchmark)) 314 | baseOrder.algoParams.append(TagValue("PercentVolume", str(percentVolume))) 315 | baseOrder.algoParams.append(TagValue("NoCleanUp", int(noCleanUp))) 316 | # ! [qbalgo_strobe_params] 317 | 318 | 319 | def Test(): 320 | av = AvailableAlgoParams() # @UnusedVariable 321 | 322 | 323 | if "__main__" == __name__: 324 | Test() 325 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/examples/Testbed/ContractSamples.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | from ibapi.contract import * # @UnusedWildImport 7 | 8 | 9 | class ContractSamples: 10 | 11 | """ Usually, the easiest way to define a Stock/CASH contract is through 12 | these four attributes. """ 13 | 14 | @staticmethod 15 | def EurGbpFx(): 16 | #! [cashcontract] 17 | contract = Contract() 18 | contract.symbol = "EUR" 19 | contract.secType = "CASH" 20 | contract.currency = "GBP" 21 | contract.exchange = "IDEALPRO" 22 | #! [cashcontract] 23 | return contract 24 | 25 | 26 | @staticmethod 27 | def Index(): 28 | #! [indcontract] 29 | contract = Contract() 30 | contract.symbol = "DAX" 31 | contract.secType = "IND" 32 | contract.currency = "EUR" 33 | contract.exchange = "DTB" 34 | #! [indcontract] 35 | return contract 36 | 37 | 38 | @staticmethod 39 | def CFD(): 40 | #! [cfdcontract] 41 | contract = Contract() 42 | contract.symbol = "IBDE30" 43 | contract.secType = "CFD" 44 | contract.currency = "EUR" 45 | contract.exchange = "SMART" 46 | #! [cfdcontract] 47 | return contract 48 | 49 | 50 | @staticmethod 51 | def EuropeanStock(): 52 | contract = Contract() 53 | contract.symbol = "BMW" 54 | contract.secType = "STK" 55 | contract.currency = "EUR" 56 | contract.exchange = "SMART" 57 | contract.primaryExchange = "IBIS" 58 | return contract 59 | 60 | @staticmethod 61 | def EuropeanStock2(): 62 | contract = Contract() 63 | contract.symbol = "NOKIA" 64 | contract.secType = "STK" 65 | contract.currency = "EUR" 66 | contract.exchange = "SMART" 67 | contract.primaryExchange = "HEX" 68 | return contract 69 | 70 | @staticmethod 71 | def OptionAtIse(): 72 | contract = Contract() 73 | contract.symbol = "COF" 74 | contract.secType = "OPT" 75 | contract.currency = "USD" 76 | contract.exchange = "ISE" 77 | contract.lastTradeDateOrContractMonth = "20190315" 78 | contract.right = "P" 79 | contract.strike = 105 80 | contract.multiplier = "100" 81 | return contract 82 | 83 | 84 | @staticmethod 85 | def BondWithCusip(): 86 | #! [bondwithcusip] 87 | contract = Contract() 88 | # enter CUSIP as symbol 89 | contract.symbol= "912828C57" 90 | contract.secType = "BOND" 91 | contract.exchange = "SMART" 92 | contract.currency = "USD" 93 | #! [bondwithcusip] 94 | return contract 95 | 96 | 97 | @staticmethod 98 | def Bond(): 99 | #! [bond] 100 | contract = Contract() 101 | contract.conId = 15960357 102 | contract.exchange = "SMART" 103 | #! [bond] 104 | return contract 105 | 106 | 107 | @staticmethod 108 | def MutualFund(): 109 | #! [fundcontract] 110 | contract = Contract() 111 | contract.symbol = "VINIX" 112 | contract.secType = "FUND" 113 | contract.exchange = "FUNDSERV" 114 | contract.currency = "USD" 115 | #! [fundcontract] 116 | return contract 117 | 118 | 119 | @staticmethod 120 | def Commodity(): 121 | #! [commoditycontract] 122 | contract = Contract() 123 | contract.symbol = "XAUUSD" 124 | contract.secType = "CMDTY" 125 | contract.exchange = "SMART" 126 | contract.currency = "USD" 127 | #! [commoditycontract] 128 | return contract 129 | 130 | 131 | @staticmethod 132 | def USStock(): 133 | #! [stkcontract] 134 | contract = Contract() 135 | contract.symbol = "IBKR" 136 | contract.secType = "STK" 137 | contract.currency = "USD" 138 | #In the API side, NASDAQ is always defined as ISLAND in the exchange field 139 | contract.exchange = "ISLAND" 140 | #! [stkcontract] 141 | return contract 142 | 143 | 144 | @staticmethod 145 | def USStockWithPrimaryExch(): 146 | #! [stkcontractwithprimary] 147 | contract = Contract() 148 | contract.symbol = "MSFT" 149 | contract.secType = "STK" 150 | contract.currency = "USD" 151 | contract.exchange = "SMART" 152 | #Specify the Primary Exchange attribute to avoid contract ambiguity 153 | #(there is an ambiguity because there is also a MSFT contract with primary exchange = "AEB") 154 | contract.primaryExchange = "ISLAND" 155 | #! [stkcontractwithprimary] 156 | return contract 157 | 158 | 159 | @staticmethod 160 | def USStockAtSmart(): 161 | contract = Contract() 162 | contract.symbol = "IBM" 163 | contract.secType = "STK" 164 | contract.currency = "USD" 165 | contract.exchange = "SMART" 166 | return contract 167 | 168 | 169 | @staticmethod 170 | def USOptionContract(): 171 | #! [optcontract_us] 172 | contract = Contract() 173 | contract.symbol = "GOOG" 174 | contract.secType = "OPT" 175 | contract.exchange = "SMART" 176 | contract.currency = "USD" 177 | contract.lastTradeDateOrContractMonth = "20190315" 178 | contract.strike = 1180 179 | contract.right = "C" 180 | contract.multiplier = "100" 181 | #! [optcontract_us] 182 | return contract 183 | 184 | 185 | @staticmethod 186 | def OptionAtBOX(): 187 | #! [optcontract] 188 | contract = Contract() 189 | contract.symbol = "GOOG" 190 | contract.secType = "OPT" 191 | contract.exchange = "BOX" 192 | contract.currency = "USD" 193 | contract.lastTradeDateOrContractMonth = "20190315" 194 | contract.strike = 1180 195 | contract.right = "C" 196 | contract.multiplier = "100" 197 | #! [optcontract] 198 | return contract 199 | 200 | 201 | """ Option contracts require far more information since there are many 202 | contracts having the exact same attributes such as symbol, currency, 203 | strike, etc. This can be overcome by adding more details such as the 204 | trading class""" 205 | 206 | @staticmethod 207 | def OptionWithTradingClass(): 208 | #! [optcontract_tradingclass] 209 | contract = Contract() 210 | contract.symbol = "SANT" 211 | contract.secType = "OPT" 212 | contract.exchange = "MEFFRV" 213 | contract.currency = "EUR" 214 | contract.lastTradeDateOrContractMonth = "20190621" 215 | contract.strike = 7.5 216 | contract.right = "C" 217 | contract.multiplier = "100" 218 | contract.tradingClass = "SANEU" 219 | #! [optcontract_tradingclass] 220 | return contract 221 | 222 | 223 | """ Using the contract's own symbol (localSymbol) can greatly simplify a 224 | contract description """ 225 | 226 | @staticmethod 227 | def OptionWithLocalSymbol(): 228 | #! [optcontract_localsymbol] 229 | contract = Contract() 230 | #Watch out for the spaces within the local symbol! 231 | contract.localSymbol = "C DBK DEC 20 1600" 232 | contract.secType = "OPT" 233 | contract.exchange = "DTB" 234 | contract.currency = "EUR" 235 | #! [optcontract_localsymbol] 236 | return contract 237 | 238 | """ Dutch Warrants (IOPTs) can be defined using the local symbol or conid 239 | """ 240 | 241 | @staticmethod 242 | def DutchWarrant(): 243 | #! [ioptcontract] 244 | contract = Contract() 245 | contract.localSymbol = "B881G" 246 | contract.secType = "IOPT" 247 | contract.exchange = "SBF" 248 | contract.currency = "EUR" 249 | #! [ioptcontract] 250 | return contract 251 | 252 | """ Future contracts also require an expiration date but are less 253 | complicated than options.""" 254 | 255 | @staticmethod 256 | def SimpleFuture(): 257 | #! [futcontract] 258 | contract = Contract() 259 | contract.symbol = "ES" 260 | contract.secType = "FUT" 261 | contract.exchange = "GLOBEX" 262 | contract.currency = "USD" 263 | contract.lastTradeDateOrContractMonth = "201903" 264 | #! [futcontract] 265 | return contract 266 | 267 | 268 | """Rather than giving expiration dates we can also provide the local symbol 269 | attributes such as symbol, currency, strike, etc. """ 270 | 271 | @staticmethod 272 | def FutureWithLocalSymbol(): 273 | #! [futcontract_local_symbol] 274 | contract = Contract() 275 | contract.secType = "FUT" 276 | contract.exchange = "GLOBEX" 277 | contract.currency = "USD" 278 | contract.localSymbol = "ESU6" 279 | #! [futcontract_local_symbol] 280 | return contract 281 | 282 | 283 | @staticmethod 284 | def FutureWithMultiplier(): 285 | #! [futcontract_multiplier] 286 | contract = Contract() 287 | contract.symbol = "DAX" 288 | contract.secType = "FUT" 289 | contract.exchange = "DTB" 290 | contract.currency = "EUR" 291 | contract.lastTradeDateOrContractMonth = "201903" 292 | contract.multiplier = "5" 293 | #! [futcontract_multiplier] 294 | return contract 295 | 296 | 297 | """ Note the space in the symbol! """ 298 | 299 | @staticmethod 300 | def WrongContract(): 301 | contract = Contract() 302 | contract.symbol = " IJR " 303 | contract.conId = 9579976 304 | contract.secType = "STK" 305 | contract.exchange = "SMART" 306 | contract.currency = "USD" 307 | return contract 308 | 309 | @staticmethod 310 | def FuturesOnOptions(): 311 | #! [fopcontract] 312 | contract = Contract() 313 | contract.symbol = "ES" 314 | contract.secType = "FOP" 315 | contract.exchange = "GLOBEX" 316 | contract.currency = "USD" 317 | contract.lastTradeDateOrContractMonth = "20190315" 318 | contract.strike = 2900 319 | contract.right = "C" 320 | contract.multiplier = "50" 321 | #! [fopcontract] 322 | return contract 323 | 324 | 325 | """ It is also possible to define contracts based on their ISIN (IBKR STK 326 | sample). """ 327 | 328 | @staticmethod 329 | def ByISIN(): 330 | contract = Contract() 331 | contract.secIdType = "ISIN" 332 | contract.secId = "US45841N1072" 333 | contract.exchange = "SMART" 334 | contract.currency = "USD" 335 | contract.secType = "STK" 336 | return contract 337 | 338 | 339 | """ Or their conId (EUR.uSD sample). 340 | Note: passing a contract containing the conId can cause problems if one of 341 | the other provided attributes does not match 100% with what is in IB's 342 | database. This is particularly important for contracts such as Bonds which 343 | may change their description from one day to another. 344 | If the conId is provided, it is best not to give too much information as 345 | in the example below. """ 346 | 347 | @staticmethod 348 | def ByConId(): 349 | contract = Contract() 350 | contract.secType = "CASH" 351 | contract.conId = 12087792 352 | contract.exchange = "IDEALPRO" 353 | return contract 354 | 355 | 356 | """ Ambiguous contracts are great to use with reqContractDetails. This way 357 | you can query the whole option chain for an underlying. Bear in mind that 358 | there are pacing mechanisms in place which will delay any further responses 359 | from the TWS to prevent abuse. """ 360 | 361 | @staticmethod 362 | def OptionForQuery(): 363 | #! [optionforquery] 364 | contract = Contract() 365 | contract.symbol = "FISV" 366 | contract.secType = "OPT" 367 | contract.exchange = "SMART" 368 | contract.currency = "USD" 369 | #! [optionforquery] 370 | return contract 371 | 372 | 373 | @staticmethod 374 | def OptionComboContract(): 375 | #! [bagoptcontract] 376 | contract = Contract() 377 | contract.symbol = "DBK" 378 | contract.secType = "BAG" 379 | contract.currency = "EUR" 380 | contract.exchange = "DTB" 381 | 382 | leg1 = ComboLeg() 383 | leg1.conId = 317960956 #DBK JUN 21 2019 C 384 | leg1.ratio = 1 385 | leg1.action = "BUY" 386 | leg1.exchange = "DTB" 387 | 388 | leg2 = ComboLeg() 389 | leg2.conId = 334216780 #DBK MAR 15 2019 C 390 | leg2.ratio = 1 391 | leg2.action = "SELL" 392 | leg2.exchange = "DTB" 393 | 394 | contract.comboLegs = [] 395 | contract.comboLegs.append(leg1) 396 | contract.comboLegs.append(leg2) 397 | #! [bagoptcontract] 398 | return contract 399 | 400 | 401 | """ STK Combo contract 402 | Leg 1: 43645865 - IBKR's STK 403 | Leg 2: 9408 - McDonald's STK """ 404 | 405 | @staticmethod 406 | def StockComboContract(): 407 | #! [bagstkcontract] 408 | contract = Contract() 409 | contract.symbol = "IBKR,MCD" 410 | contract.secType = "BAG" 411 | contract.currency = "USD" 412 | contract.exchange = "SMART" 413 | 414 | leg1 = ComboLeg() 415 | leg1.conId = 43645865#IBKR STK 416 | leg1.ratio = 1 417 | leg1.action = "BUY" 418 | leg1.exchange = "SMART" 419 | 420 | leg2 = ComboLeg() 421 | leg2.conId = 9408#MCD STK 422 | leg2.ratio = 1 423 | leg2.action = "SELL" 424 | leg2.exchange = "SMART" 425 | 426 | contract.comboLegs = [] 427 | contract.comboLegs.append(leg1) 428 | contract.comboLegs.append(leg2) 429 | #! [bagstkcontract] 430 | return contract 431 | 432 | 433 | """ CBOE Volatility Index Future combo contract """ 434 | 435 | @staticmethod 436 | def FutureComboContract(): 437 | #! [bagfutcontract] 438 | contract = Contract() 439 | contract.symbol = "VIX" 440 | contract.secType = "BAG" 441 | contract.currency = "USD" 442 | contract.exchange = "CFE" 443 | 444 | leg1 = ComboLeg() 445 | leg1.conId = 326501438 # VIX FUT 201903 446 | leg1.ratio = 1 447 | leg1.action = "BUY" 448 | leg1.exchange = "CFE" 449 | 450 | leg2 = ComboLeg() 451 | leg2.conId = 323072528 # VIX FUT 2019049 452 | leg2.ratio = 1 453 | leg2.action = "SELL" 454 | leg2.exchange = "CFE" 455 | 456 | contract.comboLegs = [] 457 | contract.comboLegs.append(leg1) 458 | contract.comboLegs.append(leg2) 459 | #! [bagfutcontract] 460 | return contract 461 | 462 | @staticmethod 463 | def SmartFutureComboContract(): 464 | #! [smartfuturespread] 465 | contract = Contract() 466 | contract.symbol = "WTI" # WTI,COIL spread. Symbol can be defined as first leg symbol ("WTI") or currency ("USD") 467 | contract.secType = "BAG" 468 | contract.currency = "USD" 469 | contract.exchange = "SMART" 470 | 471 | leg1 = ComboLeg() 472 | leg1.conId = 55928698 # WTI future June 2017 473 | leg1.ratio = 1 474 | leg1.action = "BUY" 475 | leg1.exchange = "IPE" 476 | 477 | leg2 = ComboLeg() 478 | leg2.conId = 55850663 # COIL future June 2017 479 | leg2.ratio = 1 480 | leg2.action = "SELL" 481 | leg2.exchange = "IPE" 482 | 483 | contract.comboLegs = [] 484 | contract.comboLegs.append(leg1) 485 | contract.comboLegs.append(leg2) 486 | #! [smartfuturespread] 487 | return contract 488 | 489 | @staticmethod 490 | def InterCmdtyFuturesContract(): 491 | #! [intcmdfutcontract] 492 | contract = Contract() 493 | contract.symbol = "CL.BZ" #symbol is 'local symbol' of intercommodity spread. 494 | contract.secType = "BAG" 495 | contract.currency = "USD" 496 | contract.exchange = "NYMEX" 497 | 498 | leg1 = ComboLeg() 499 | leg1.conId = 47207310 #CL Dec'16 @NYMEX 500 | leg1.ratio = 1 501 | leg1.action = "BUY" 502 | leg1.exchange = "NYMEX" 503 | 504 | leg2 = ComboLeg() 505 | leg2.conId = 47195961 #BZ Dec'16 @NYMEX 506 | leg2.ratio = 1 507 | leg2.action = "SELL" 508 | leg2.exchange = "NYMEX" 509 | 510 | contract.comboLegs = [] 511 | contract.comboLegs.append(leg1) 512 | contract.comboLegs.append(leg2) 513 | #! [intcmdfutcontract] 514 | return contract 515 | 516 | 517 | @staticmethod 518 | def NewsFeedForQuery(): 519 | #! [newsfeedforquery] 520 | contract = Contract() 521 | contract.secType = "NEWS" 522 | contract.exchange = "BRFG" #Briefing Trader 523 | #! [newsfeedforquery] 524 | return contract 525 | 526 | 527 | @staticmethod 528 | def BRFGbroadtapeNewsFeed(): 529 | #! [newscontractbt] 530 | contract = Contract() 531 | contract.symbol = "BRFG:BRFG_ALL" 532 | contract.secType = "NEWS" 533 | contract.exchange = "BRFG" 534 | #! [newscontractbt] 535 | return contract 536 | 537 | 538 | @staticmethod 539 | def DJNLbroadtapeNewsFeed(): 540 | #! [newscontractbz] 541 | contract = Contract() 542 | contract.symbol = "DJNL:DJNL_ALL" 543 | contract.secType = "NEWS" 544 | contract.exchange = "DJNL" 545 | #! [newscontractbz] 546 | return contract 547 | 548 | 549 | @staticmethod 550 | def DJTOPbroadtapeNewsFeed(): 551 | #! [newscontractfly] 552 | contract = Contract() 553 | contract.symbol = "DJTOP:ASIAPAC" 554 | contract.secType = "NEWS" 555 | contract.exchange = "DJTOP" 556 | #! [newscontractfly] 557 | return contract 558 | 559 | 560 | @staticmethod 561 | def BRFUPDNbroadtapeNewsFeed(): 562 | #! [newscontractmt] 563 | contract = Contract() 564 | contract.symbol = "BRFUPDN:BRF_ALL" 565 | contract.secType = "NEWS" 566 | contract.exchange = "BRFUPDN" 567 | #! [newscontractmt] 568 | return contract 569 | 570 | @staticmethod 571 | def ContFut(): 572 | #! [continuousfuturescontract] 573 | contract = Contract() 574 | contract.symbol = "ES" 575 | contract.secType = "CONTFUT" 576 | contract.exchange = "GLOBEX" 577 | #! [continuousfuturescontract] 578 | return contract 579 | 580 | @staticmethod 581 | def ContAndExpiringFut(): 582 | #! [contandexpiringfut] 583 | contract = Contract() 584 | contract.symbol = "ES" 585 | contract.secType = "FUT+CONTFUT" 586 | contract.exchange = "GLOBEX" 587 | #! [contandexpiringfut] 588 | return contract 589 | 590 | @staticmethod 591 | def JefferiesContract(): 592 | #! [jefferies_contract] 593 | contract = Contract() 594 | contract.symbol = "AAPL" 595 | contract.secType = "STK" 596 | contract.exchange = "JEFFALGO" 597 | contract.currency = "USD" 598 | #! [jefferies_contract] 599 | return contract 600 | 601 | @staticmethod 602 | def CSFBContract(): 603 | #! [csfb_contract] 604 | contract = Contract() 605 | contract.symbol = "IBKR" 606 | contract.secType = "STK" 607 | contract.exchange = "CSFBALGO" 608 | contract.currency = "USD" 609 | #! [csfb_contract] 610 | return contract 611 | 612 | @staticmethod 613 | def USStockCFD(): 614 | # ! [usstockcfd_conract] 615 | contract = Contract(); 616 | contract.symbol = "IBM"; 617 | contract.secType = "CFD"; 618 | contract.currency = "USD"; 619 | contract.exchange = "SMART"; 620 | # ! [usstockcfd_conract] 621 | return contract; 622 | 623 | @staticmethod 624 | def EuropeanStockCFD(): 625 | # ! [europeanstockcfd_contract] 626 | contract = Contract(); 627 | contract.symbol = "BMW"; 628 | contract.secType = "CFD"; 629 | contract.currency = "EUR"; 630 | contract.exchange = "SMART"; 631 | # ! [europeanstockcfd_contract] 632 | return contract; 633 | 634 | @staticmethod 635 | def CashCFD(): 636 | # ! [cashcfd_contract] 637 | contract = Contract(); 638 | contract.symbol = "EUR"; 639 | contract.secType = "CFD"; 640 | contract.currency = "USD"; 641 | contract.exchange = "SMART"; 642 | # ! [cashcfd_contract] 643 | return contract; 644 | 645 | @staticmethod 646 | def QBAlgoContract(): 647 | # ! [qbalgo_contract] 648 | contract = Contract() 649 | contract.symbol = "ES"; 650 | contract.secType = "FUT"; 651 | contract.exchange = "QBALGO"; 652 | contract.currency = "USD"; 653 | contract.lastTradeDateOrContractMonth = "202003"; 654 | # ! [qbalgo_contract] 655 | return contract; 656 | 657 | def Test(): 658 | from ibapi.utils import ExerciseStaticMethods 659 | ExerciseStaticMethods(ContractSamples) 660 | 661 | 662 | if "__main__" == __name__: 663 | Test() 664 | 665 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/examples/Testbed/FaAllocationSamples.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | from ibapi.object_implem import Object 7 | 8 | 9 | class FaAllocationSamples(Object): 10 | #! [faonegroup] 11 | FaOneGroup = "".join(("" 12 | , "" 13 | , "" 14 | , "Equal_Quantity" 15 | , "" 16 | #Replace with your own accountIds 17 | , "DU119915" 18 | , "DU119916" 19 | , "" 20 | , "EqualQuantity" 21 | , "" 22 | , "")) 23 | #! [faonegroup] 24 | 25 | #! [fatwogroups] 26 | FaTwoGroups = "".join(("" 27 | ,"" 28 | , "" 29 | , "Equal_Quantity" 30 | , "" 31 | #Replace with your own accountIds 32 | , "DU119915" 33 | , "DU119916" 34 | , "" 35 | , "EqualQuantity" 36 | , "" 37 | , "" 38 | , "Pct_Change" 39 | , "" 40 | #Replace with your own accountIds 41 | , "DU119915" 42 | , "DU119916" 43 | , "" 44 | , "PctChange" 45 | , "" 46 | , "")) 47 | #! [fatwogroups] 48 | 49 | 50 | #! [faoneprofile] 51 | FaOneProfile = "".join(("" 52 | , "" 53 | , "" 54 | , "Percent_60_40" 55 | , "1" 56 | , "" 57 | , "" 58 | #Replace with your own accountIds 59 | , "DU119915" 60 | , "60.0" 61 | , "" 62 | , "" 63 | #Replace with your own accountIds 64 | , "DU119916" 65 | , "40.0" 66 | , "" 67 | , "" 68 | , "" 69 | , "")) 70 | #! [faoneprofile] 71 | 72 | #! [fatwoprofiles] 73 | FaTwoProfiles = "".join(("" 74 | , "" 75 | , "" 76 | , "Percent_60_40" 77 | , "1" 78 | , "" 79 | , "" 80 | #Replace with your own accountIds 81 | , "DU119915" 82 | , "60.0" 83 | , "" 84 | , "" 85 | #Replace with your own accountIds 86 | , "DU119916" 87 | , "40.0" 88 | , "" 89 | , "" 90 | , "" 91 | , "" 92 | , "Ratios_2_1" 93 | , "1" 94 | , "" 95 | , "" 96 | #Replace with your own accountIds 97 | , "DU119915" 98 | , "2.0" 99 | , "" 100 | , "" 101 | #Replace with your own accountIds 102 | , "DU119916" 103 | , "1.0" 104 | , "" 105 | , "" 106 | , "" 107 | , "")) 108 | #! [fatwoprofiles] 109 | 110 | 111 | def Test(): 112 | print(FaAllocationSamples.FaOneGroup) 113 | print(FaAllocationSamples.FaTwoGroups) 114 | print(FaAllocationSamples.FaOneProfile) 115 | print(FaAllocationSamples.FaTwoProfiles) 116 | 117 | if "__main__" == __name__: 118 | Test() 119 | 120 | 121 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/examples/Testbed/ScannerSubscriptionSamples.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | 7 | from ibapi.object_implem import Object 8 | from ibapi.scanner import ScannerSubscription 9 | 10 | 11 | class ScannerSubscriptionSamples(Object): 12 | 13 | @staticmethod 14 | def HotUSStkByVolume(): 15 | #! [hotusvolume] 16 | #Hot US stocks by volume 17 | scanSub = ScannerSubscription() 18 | scanSub.instrument = "STK" 19 | scanSub.locationCode = "STK.US.MAJOR" 20 | scanSub.scanCode = "HOT_BY_VOLUME" 21 | #! [hotusvolume] 22 | return scanSub 23 | 24 | @staticmethod 25 | def TopPercentGainersIbis(): 26 | #! [toppercentgaineribis] 27 | # Top % gainers at IBIS 28 | scanSub = ScannerSubscription() 29 | scanSub.instrument = "STOCK.EU" 30 | scanSub.locationCode = "STK.EU.IBIS" 31 | scanSub.scanCode = "TOP_PERC_GAIN" 32 | #! [toppercentgaineribis] 33 | return scanSub 34 | 35 | @staticmethod 36 | def MostActiveFutSoffex(): 37 | #! [mostactivefutsoffex] 38 | # Most active futures at SOFFEX 39 | scanSub = ScannerSubscription() 40 | scanSub.instrument = "FUT.EU" 41 | scanSub.locationCode = "FUT.EU.SOFFEX" 42 | scanSub.scanCode = "MOST_ACTIVE" 43 | #! [mostactivefutsoffex] 44 | return scanSub 45 | 46 | @staticmethod 47 | def HighOptVolumePCRatioUSIndexes(): 48 | #! [highoptvolume] 49 | # High option volume P/C ratio US indexes 50 | scanSub = ScannerSubscription() 51 | scanSub.instrument = "IND.US" 52 | scanSub.locationCode = "IND.US" 53 | scanSub.scanCode = "HIGH_OPT_VOLUME_PUT_CALL_RATIO" 54 | #! [highoptvolume] 55 | return scanSub 56 | 57 | @staticmethod 58 | def ComplexOrdersAndTrades(): 59 | #! [combolatesttrade] 60 | # High option volume P/C ratio US indexes 61 | scanSub = ScannerSubscription() 62 | scanSub.instrument = "NATCOMB" 63 | scanSub.locationCode = "NATCOMB.OPT.US" 64 | scanSub.scanCode = "COMBO_LATEST_TRADE" 65 | #! [combolatesttrade] 66 | return scanSub 67 | 68 | def Test(): 69 | print(ScannerSubscriptionSamples.HotUSStkByVolume()) 70 | print(ScannerSubscriptionSamples.TopPercentGainersIbis()) 71 | print(ScannerSubscriptionSamples.MostActiveFutSoffex()) 72 | print(ScannerSubscriptionSamples.HighOptVolumePCRatioUSIndexes()) 73 | 74 | 75 | if "__main__" == __name__: 76 | Test() 77 | 78 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/ibapi/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | """ Package implementing the Python API for the TWS/IB Gateway """ 7 | 8 | VERSION = { 9 | 'major': 9, 10 | 'minor': 76, 11 | 'micro': 1} 12 | 13 | 14 | def get_version_string(): 15 | version = '{major}.{minor}.{micro}'.format(**VERSION) 16 | return version 17 | 18 | __version__ = get_version_string() 19 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/ibapi/account_summary_tags.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | class AccountSummaryTags: 7 | AccountType = "AccountType" 8 | NetLiquidation = "NetLiquidation" 9 | TotalCashValue = "TotalCashValue" 10 | SettledCash = "SettledCash" 11 | AccruedCash = "AccruedCash" 12 | BuyingPower = "BuyingPower" 13 | EquityWithLoanValue = "EquityWithLoanValue" 14 | PreviousEquityWithLoanValue = "PreviousEquityWithLoanValue" 15 | GrossPositionValue = "GrossPositionValue" 16 | ReqTEquity = "ReqTEquity" 17 | ReqTMargin = "ReqTMargin" 18 | SMA = "SMA" 19 | InitMarginReq = "InitMarginReq" 20 | MaintMarginReq = "MaintMarginReq" 21 | AvailableFunds = "AvailableFunds" 22 | ExcessLiquidity = "ExcessLiquidity" 23 | Cushion = "Cushion" 24 | FullInitMarginReq = "FullInitMarginReq" 25 | FullMaintMarginReq = "FullMaintMarginReq" 26 | FullAvailableFunds = "FullAvailableFunds" 27 | FullExcessLiquidity = "FullExcessLiquidity" 28 | LookAheadNextChange = "LookAheadNextChange" 29 | LookAheadInitMarginReq = "LookAheadInitMarginReq" 30 | LookAheadMaintMarginReq = "LookAheadMaintMarginReq" 31 | LookAheadAvailableFunds = "LookAheadAvailableFunds" 32 | LookAheadExcessLiquidity = "LookAheadExcessLiquidity" 33 | HighestSeverity = "HighestSeverity" 34 | DayTradesRemaining = "DayTradesRemaining" 35 | Leverage = "Leverage" 36 | 37 | AllTags = ",".join((AccountType, NetLiquidation, TotalCashValue, 38 | SettledCash, AccruedCash, BuyingPower, EquityWithLoanValue, 39 | PreviousEquityWithLoanValue, GrossPositionValue, ReqTEquity, 40 | ReqTMargin, SMA, InitMarginReq, MaintMarginReq, AvailableFunds, 41 | ExcessLiquidity , Cushion, FullInitMarginReq, FullMaintMarginReq, 42 | FullAvailableFunds, FullExcessLiquidity, 43 | LookAheadNextChange, LookAheadInitMarginReq, LookAheadMaintMarginReq, 44 | LookAheadAvailableFunds, LookAheadExcessLiquidity, HighestSeverity, 45 | DayTradesRemaining, Leverage)) 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/ibapi/comm.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | 7 | """ 8 | This module has tools for implementing the IB low level messaging. 9 | """ 10 | 11 | 12 | import struct 13 | import logging 14 | 15 | from ibapi.common import UNSET_INTEGER, UNSET_DOUBLE 16 | 17 | logger = logging.getLogger(__name__) 18 | 19 | 20 | def make_msg(text) -> bytes: 21 | """ adds the length prefix """ 22 | msg = struct.pack("!I%ds" % len(text), len(text), str.encode(text)) 23 | return msg 24 | 25 | 26 | def make_field(val) -> str: 27 | """ adds the NULL string terminator """ 28 | 29 | if val is None: 30 | raise ValueError("Cannot send None to TWS") 31 | 32 | # bool type is encoded as int 33 | if type(val) is bool: 34 | val = int(val) 35 | 36 | field = str(val) + '\0' 37 | return field 38 | 39 | 40 | def make_field_handle_empty(val) -> str: 41 | 42 | if val is None: 43 | raise ValueError("Cannot send None to TWS") 44 | 45 | if UNSET_INTEGER == val or UNSET_DOUBLE == val: 46 | val = "" 47 | 48 | return make_field(val) 49 | 50 | 51 | def read_msg(buf:bytes) -> tuple: 52 | """ first the size prefix and then the corresponding msg payload """ 53 | if len(buf) < 4: 54 | return (0, "", buf) 55 | size = struct.unpack("!I", buf[0:4])[0] 56 | logger.debug("read_msg: size: %d", size) 57 | if len(buf) - 4 >= size: 58 | text = struct.unpack("!%ds" % size, buf[4:4+size])[0] 59 | return (size, text, buf[4+size:]) 60 | else: 61 | return (size, "", buf) 62 | 63 | 64 | def read_fields(buf:bytes) -> tuple: 65 | 66 | if isinstance(buf, str): 67 | buf = buf.encode() 68 | 69 | """ msg payload is made of fields terminated/separated by NULL chars """ 70 | fields = buf.split(b"\0") 71 | 72 | return tuple(fields[0:-1]) #last one is empty; this may slow dow things though, TODO 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/ibapi/commission_report.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | from ibapi.object_implem import Object 7 | from ibapi import utils 8 | 9 | class CommissionReport(Object): 10 | 11 | def __init__(self): 12 | self.execId = "" 13 | self.commission = 0. 14 | self.currency = "" 15 | self.realizedPNL = 0. 16 | self.yield_ = 0. 17 | self.yieldRedemptionDate = 0 # YYYYMMDD format 18 | 19 | def __str__(self): 20 | return "ExecId: %s, Commission: %f, Currency: %s, RealizedPnL: %s, Yield: %s, YieldRedemptionDate: %d" % (self.execId, self.commission, 21 | self.currency, utils.floatToStr(self.realizedPNL), utils.floatToStr(self.yield_), self.yieldRedemptionDate) 22 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/ibapi/common.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | import sys 7 | 8 | from ibapi.enum_implem import Enum 9 | from ibapi.object_implem import Object 10 | 11 | 12 | NO_VALID_ID = -1 13 | MAX_MSG_LEN = 0xFFFFFF # 16Mb - 1byte 14 | 15 | UNSET_INTEGER = 2 ** 31 - 1 16 | UNSET_DOUBLE = sys.float_info.max 17 | UNSET_LONG = 2 ** 63 - 1 18 | 19 | TickerId = int 20 | OrderId = int 21 | TagValueList = list 22 | 23 | FaDataType = int 24 | FaDataTypeEnum = Enum("N/A", "GROUPS", "PROFILES", "ALIASES") 25 | 26 | MarketDataType = int 27 | MarketDataTypeEnum = Enum("N/A", "REALTIME", "FROZEN", "DELAYED", "DELAYED_FROZEN") 28 | 29 | Liquidities = int 30 | LiquiditiesEnum = Enum("None", "Added", "Remove", "RoudedOut") 31 | 32 | SetOfString = set 33 | SetOfFloat = set 34 | ListOfOrder = list 35 | ListOfFamilyCode = list 36 | ListOfContractDescription = list 37 | ListOfDepthExchanges = list 38 | ListOfNewsProviders = list 39 | SmartComponentMap = dict 40 | HistogramDataList = list 41 | ListOfPriceIncrements = list 42 | ListOfHistoricalTick = list 43 | ListOfHistoricalTickBidAsk = list 44 | ListOfHistoricalTickLast = list 45 | 46 | class BarData(Object): 47 | def __init__(self): 48 | self.date = "" 49 | self.open = 0. 50 | self.high = 0. 51 | self.low = 0. 52 | self.close = 0. 53 | self.volume = 0 54 | self.barCount = 0 55 | self.average = 0. 56 | 57 | def __str__(self): 58 | return "Date: %s, Open: %f, High: %f, Low: %f, Close: %f, Volume: %d, Average: %f, BarCount: %d" % (self.date, self.open, self.high, 59 | self.low, self.close, self.volume, self.average, self.barCount) 60 | 61 | class RealTimeBar(Object): 62 | def __init__(self, time = 0, endTime = -1, open_ = 0., high = 0., low = 0., close = 0., volume = 0., wap = 0., count = 0): 63 | self.time = time 64 | self.endTime = endTime 65 | self.open_ = open_ 66 | self.high = high 67 | self.low = low 68 | self.close = close 69 | self.volume = volume 70 | self.wap = wap 71 | self.count = count 72 | 73 | def __str__(self): 74 | return "Time: %d, Open: %f, High: %f, Low: %f, Close: %f, Volume: %d, WAP: %f, Count: %d" % (self.time, self.open_, self.high, 75 | self.low, self.close, self.volume, self.wap, self.count) 76 | 77 | class HistogramData(Object): 78 | def __init__(self): 79 | self.price = 0. 80 | self.count = 0 81 | 82 | def __str__(self): 83 | return "Price: %f, Count: %d" % (self.price, self.count) 84 | 85 | class NewsProvider(Object): 86 | def __init__(self): 87 | self.code = "" 88 | self.name = "" 89 | 90 | def __str__(self): 91 | return "Code: %s, Name: %s" % (self.code, self.name) 92 | 93 | class DepthMktDataDescription(Object): 94 | def __init__(self): 95 | self.exchange = "" 96 | self.secType = "" 97 | self.listingExch = "" 98 | self.serviceDataType = "" 99 | self.aggGroup = UNSET_INTEGER 100 | 101 | def __str__(self): 102 | if (self.aggGroup!= UNSET_INTEGER): 103 | aggGroup = self.aggGroup 104 | else: 105 | aggGroup = "" 106 | return "Exchange: %s, SecType: %s, ListingExchange: %s, ServiceDataType: %s, AggGroup: %s, " % (self.exchange, self.secType, self.listingExch,self.serviceDataType, aggGroup) 107 | 108 | class SmartComponent(Object): 109 | def __init__(self): 110 | self.bitNumber = 0 111 | self.exchange = "" 112 | self.exchangeLetter = "" 113 | 114 | def __str__(self): 115 | return "BitNumber: %d, Exchange: %s, ExchangeLetter: %s" % (self.bitNumber, self.exchange, self.exchangeLetter) 116 | 117 | class TickAttrib(Object): 118 | def __init__(self): 119 | self.canAutoExecute = False 120 | self.pastLimit = False 121 | self.preOpen = False 122 | 123 | def __str__(self): 124 | return "CanAutoExecute: %d, PastLimit: %d, PreOpen: %d" % (self.canAutoExecute, self.pastLimit, self.preOpen) 125 | 126 | class TickAttribBidAsk(Object): 127 | def __init__(self): 128 | self.bidPastLow = False 129 | self.askPastHigh = False 130 | 131 | def __str__(self): 132 | return "BidPastLow: %d, AskPastHigh: %d" % (self.bidPastLow, self.askPastHigh) 133 | 134 | class TickAttribLast(Object): 135 | def __init__(self): 136 | self.pastLimit = False 137 | self.unreported = False 138 | 139 | def __str__(self): 140 | return "PastLimit: %d, Unreported: %d" % (self.pastLimit, self.unreported) 141 | 142 | class FamilyCode(Object): 143 | def __init__(self): 144 | self.accountID = "" 145 | self.familyCodeStr = "" 146 | 147 | def __str__(self): 148 | return "AccountId: %s, FamilyCodeStr: %s" % (self.accountID, self.familyCodeStr) 149 | 150 | class PriceIncrement(Object): 151 | def __init__(self): 152 | self.lowEdge = 0. 153 | self.increment = 0. 154 | 155 | def __str__(self): 156 | return "LowEdge: %f, Increment: %f" % (self.lowEdge, self.increment) 157 | 158 | class HistoricalTick(Object): 159 | def __init__(self): 160 | self.time = 0 161 | self.price = 0. 162 | self.size = 0 163 | 164 | def __str__(self): 165 | return "Time: %d, Price: %f, Size: %d" % (self.time, self.price, self.size) 166 | 167 | class HistoricalTickBidAsk(Object): 168 | def __init__(self): 169 | self.time = 0 170 | self.tickAttribBidAsk = TickAttribBidAsk() 171 | self.priceBid = 0. 172 | self.priceAsk = 0. 173 | self.sizeBid = 0 174 | self.sizeAsk = 0 175 | 176 | def __str__(self): 177 | return "Time: %d, TickAttriBidAsk: %s, PriceBid: %f, PriceAsk: %f, SizeBid: %d, SizeAsk: %d" % (self.time, self.tickAttribBidAsk, self.priceBid, self.priceAsk, self.sizeBid, self.sizeAsk) 178 | 179 | class HistoricalTickLast(Object): 180 | def __init__(self): 181 | self.time = 0 182 | self.tickAttribLast = TickAttribLast() 183 | self.price = 0. 184 | self.size = 0 185 | self.exchange = "" 186 | self.specialConditions = "" 187 | 188 | def __str__(self): 189 | return "Time: %d, TickAttribLast: %s, Price: %f, Size: %d, Exchange: %s, SpecialConditions: %s" % (self.time, self.tickAttribLast, self.price, self.size, self.exchange, self.specialConditions) 190 | 191 | 192 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/ibapi/connection.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | 7 | """ 8 | Just a thin wrapper around a socket. 9 | It allows us to keep some other info along with it. 10 | """ 11 | 12 | 13 | import socket 14 | import threading 15 | import logging 16 | 17 | from ibapi.common import * # @UnusedWildImport 18 | from ibapi.errors import * # @UnusedWildImport 19 | 20 | 21 | #TODO: support SSL !! 22 | 23 | logger = logging.getLogger(__name__) 24 | 25 | 26 | class Connection: 27 | def __init__(self, host, port): 28 | self.host = host 29 | self.port = port 30 | self.socket = None 31 | self.wrapper = None 32 | self.lock = threading.Lock() 33 | 34 | 35 | def connect(self): 36 | try: 37 | self.socket = socket.socket() 38 | #TODO: list the exceptions you want to catch 39 | except socket.error: 40 | if self.wrapper: 41 | self.wrapper.error(NO_VALID_ID, FAIL_CREATE_SOCK.code(), FAIL_CREATE_SOCK.msg()) 42 | 43 | try: 44 | self.socket.connect((self.host, self.port)) 45 | except socket.error: 46 | if self.wrapper: 47 | self.wrapper.error(NO_VALID_ID, CONNECT_FAIL.code(), CONNECT_FAIL.msg()) 48 | 49 | self.socket.settimeout(1) #non-blocking 50 | 51 | 52 | def disconnect(self): 53 | self.lock.acquire() 54 | try: 55 | if self.socket is not None: 56 | logger.debug("disconnecting") 57 | self.socket.close() 58 | self.socket = None 59 | logger.debug("disconnected") 60 | if self.wrapper: 61 | self.wrapper.connectionClosed() 62 | finally: 63 | self.lock.release() 64 | 65 | 66 | def isConnected(self): 67 | return self.socket is not None 68 | 69 | 70 | def sendMsg(self, msg): 71 | 72 | logger.debug("acquiring lock") 73 | self.lock.acquire() 74 | logger.debug("acquired lock") 75 | if not self.isConnected(): 76 | logger.debug("sendMsg attempted while not connected, releasing lock") 77 | self.lock.release() 78 | return 0 79 | try: 80 | nSent = self.socket.send(msg) 81 | except socket.error: 82 | logger.debug("exception from sendMsg %s", sys.exc_info()) 83 | raise 84 | finally: 85 | logger.debug("releasing lock") 86 | self.lock.release() 87 | logger.debug("release lock") 88 | 89 | logger.debug("sendMsg: sent: %d", nSent) 90 | 91 | return nSent 92 | 93 | 94 | def recvMsg(self): 95 | if not self.isConnected(): 96 | logger.debug("recvMsg attempted while not connected, releasing lock") 97 | return b"" 98 | try: 99 | buf = self._recvAllMsg() 100 | # receiving 0 bytes outside a timeout means the connection is either 101 | # closed or broken 102 | if len(buf) == 0: 103 | logger.debug("socket either closed or broken, disconnecting") 104 | self.disconnect() 105 | except socket.timeout: 106 | logger.debug("socket timeout from recvMsg %s", sys.exc_info()) 107 | buf = b"" 108 | else: 109 | pass 110 | 111 | return buf 112 | 113 | 114 | def _recvAllMsg(self): 115 | cont = True 116 | allbuf = b"" 117 | 118 | while cont and self.socket is not None: 119 | buf = self.socket.recv(4096) 120 | allbuf += buf 121 | logger.debug("len %d raw:%s|", len(buf), buf) 122 | 123 | if len(buf) < 4096: 124 | cont = False 125 | 126 | return allbuf 127 | 128 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/ibapi/contract.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | 7 | """ 8 | SAME_POS = open/close leg value is same as combo 9 | OPEN_POS = open 10 | CLOSE_POS = close 11 | UNKNOWN_POS = unknown 12 | """ 13 | 14 | 15 | from ibapi.object_implem import Object 16 | 17 | 18 | (SAME_POS, OPEN_POS, CLOSE_POS, UNKNOWN_POS) = range(4) 19 | 20 | 21 | class ComboLeg(Object): 22 | def __init__(self): 23 | self.conId = 0 # type: int 24 | self.ratio = 0 # type: int 25 | self.action = "" # BUY/SELL/SSHORT 26 | self.exchange = "" 27 | self.openClose = 0 # type: int; LegOpenClose enum values 28 | # for stock legs when doing short sale 29 | self.shortSaleSlot = 0 30 | self.designatedLocation = "" 31 | self.exemptCode = -1 32 | 33 | 34 | def __str__(self): 35 | return ",".join(( 36 | str(self.conId), 37 | str(self.ratio), 38 | str(self.action), 39 | str(self.exchange), 40 | str(self.openClose), 41 | str(self.shortSaleSlot), 42 | str(self.designatedLocation), 43 | str(self.exemptCode))) 44 | 45 | 46 | class DeltaNeutralContract(Object): 47 | def __init__(self): 48 | self.conId = 0 # type: int 49 | self.delta = 0. # type: float 50 | self.price = 0. # type: float 51 | 52 | def __str__(self): 53 | return ",".join(( 54 | str(self.conId), 55 | str(self.delta), 56 | str(self.price))) 57 | 58 | 59 | class Contract(Object): 60 | def __init__(self): 61 | self.conId = 0 62 | self.symbol = "" 63 | self.secType = "" 64 | self.lastTradeDateOrContractMonth = "" 65 | self.strike = 0. # float !! 66 | self.right = "" 67 | self.multiplier = "" 68 | self.exchange = "" 69 | self.primaryExchange = "" # pick an actual (ie non-aggregate) exchange that the contract trades on. DO NOT SET TO SMART. 70 | self.currency = "" 71 | self.localSymbol = "" 72 | self.tradingClass = "" 73 | self.includeExpired = False 74 | self.secIdType = "" # CUSIP;SEDOL;ISIN;RIC 75 | self.secId = "" 76 | 77 | #combos 78 | self.comboLegsDescrip = "" # type: str; received in open order 14 and up for all combos 79 | self.comboLegs = None # type: list 80 | self.deltaNeutralContract = None 81 | 82 | 83 | def __str__(self): 84 | s = ",".join(( 85 | str(self.conId), 86 | str(self.symbol), 87 | str(self.secType), 88 | str(self.lastTradeDateOrContractMonth), 89 | str(self.strike), 90 | str(self.right), 91 | str(self.multiplier), 92 | str(self.exchange), 93 | str(self.primaryExchange), 94 | str(self.currency), 95 | str(self.localSymbol), 96 | str(self.tradingClass), 97 | str(self.includeExpired), 98 | str(self.secIdType), 99 | str(self.secId))) 100 | s += "combo:" + self.comboLegsDescrip 101 | 102 | if self.comboLegs: 103 | for leg in self.comboLegs: 104 | s += ";" + str(leg) 105 | 106 | if self.deltaNeutralContract: 107 | s += ";" + str(self.deltaNeutralContract) 108 | 109 | return s 110 | 111 | 112 | class ContractDetails(Object): 113 | def __init__(self): 114 | self.contract = Contract() 115 | self.marketName = "" 116 | self.minTick = 0. 117 | self.orderTypes = "" 118 | self.validExchanges = "" 119 | self.priceMagnifier = 0 120 | self.underConId = 0 121 | self.longName = "" 122 | self.contractMonth = "" 123 | self.industry = "" 124 | self.category = "" 125 | self.subcategory = "" 126 | self.timeZoneId = "" 127 | self.tradingHours = "" 128 | self.liquidHours = "" 129 | self.evRule = "" 130 | self.evMultiplier = 0 131 | self.mdSizeMultiplier = 0 132 | self.aggGroup = 0 133 | self.underSymbol = "" 134 | self.underSecType = "" 135 | self.marketRuleIds = "" 136 | self.secIdList = None 137 | self.realExpirationDate = "" 138 | self.lastTradeTime = "" 139 | # BOND values 140 | self.cusip = "" 141 | self.ratings = "" 142 | self.descAppend = "" 143 | self.bondType = "" 144 | self.couponType = "" 145 | self.callable = False 146 | self.putable = False 147 | self.coupon = 0 148 | self.convertible = False 149 | self.maturity = "" 150 | self.issueDate = "" 151 | self.nextOptionDate = "" 152 | self.nextOptionType = "" 153 | self.nextOptionPartial = False 154 | self.notes = "" 155 | 156 | def __str__(self): 157 | s = ",".join(( 158 | str(self.contract), 159 | str(self.marketName), 160 | str(self.minTick), 161 | str(self.orderTypes), 162 | str(self.validExchanges), 163 | str(self.priceMagnifier), 164 | str(self.underConId), 165 | str(self.longName), 166 | str(self.contractMonth), 167 | str(self.industry), 168 | str(self.category), 169 | str(self.subcategory), 170 | str(self.timeZoneId), 171 | str(self.tradingHours), 172 | str(self.liquidHours), 173 | str(self.evRule), 174 | str(self.evMultiplier), 175 | str(self.mdSizeMultiplier), 176 | str(self.underSymbol), 177 | str(self.underSecType), 178 | str(self.marketRuleIds), 179 | str(self.aggGroup), 180 | str(self.secIdList), 181 | str(self.realExpirationDate), 182 | str(self.cusip), 183 | str(self.ratings), 184 | str(self.descAppend), 185 | str(self.bondType), 186 | str(self.couponType), 187 | str(self.callable), 188 | str(self.putable), 189 | str(self.coupon), 190 | str(self.convertible), 191 | str(self.maturity), 192 | str(self.issueDate), 193 | str(self.nextOptionDate), 194 | str(self.nextOptionType), 195 | str(self.nextOptionPartial), 196 | str(self.notes))) 197 | return s 198 | 199 | 200 | class ContractDescription(Object): 201 | def __init__(self): 202 | self.contract = Contract() 203 | self.derivativeSecTypes = None # type: list of strings 204 | 205 | 206 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/ibapi/enum_implem.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | 7 | """ 8 | Simple enum implementation 9 | """ 10 | 11 | 12 | class Enum: 13 | def __init__(self, *args): 14 | self.idx2name = {} 15 | for (idx, name) in enumerate(args): 16 | setattr(self, name, idx) 17 | self.idx2name[idx] = name 18 | 19 | def to_str(self, idx): 20 | return self.idx2name.get(idx, "NOTFOUND") 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/ibapi/errors.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | 7 | """ 8 | This is the interface that will need to be overloaded by the customer so 9 | that his/her code can receive info from the TWS/IBGW. 10 | """ 11 | 12 | 13 | class CodeMsgPair: 14 | def __init__(self, code, msg): 15 | self.errorCode = code 16 | self.errorMsg = msg 17 | 18 | def code(self): 19 | return self.errorCode 20 | 21 | def msg(self): 22 | return self.errorMsg 23 | 24 | 25 | ALREADY_CONNECTED = CodeMsgPair(501, "Already connected.") 26 | CONNECT_FAIL = CodeMsgPair(502, 27 | """Couldn't connect to TWS. Confirm that \"Enable ActiveX and Socket EClients\" 28 | is enabled and connection port is the same as \"Socket Port\" on the 29 | TWS \"Edit->Global Configuration...->API->Settings\" menu. Live Trading ports: 30 | TWS: 7496; IB Gateway: 4001. Simulated Trading ports for new installations 31 | of version 954.1 or newer: TWS: 7497; IB Gateway: 4002""") 32 | UPDATE_TWS = CodeMsgPair(503, "The TWS is out of date and must be upgraded.") 33 | NOT_CONNECTED = CodeMsgPair(504, "Not connected") 34 | UNKNOWN_ID = CodeMsgPair(505, "Fatal Error: Unknown message id.") 35 | UNSUPPORTED_VERSION = CodeMsgPair(506, "Unsupported version") 36 | BAD_LENGTH = CodeMsgPair(507, "Bad message length") 37 | BAD_MESSAGE = CodeMsgPair(508, "Bad message") 38 | SOCKET_EXCEPTION = CodeMsgPair(509, "Exception caught while reading socket - ") 39 | FAIL_CREATE_SOCK = CodeMsgPair(520, "Failed to create socket") 40 | SSL_FAIL = CodeMsgPair(530, "SSL specific error: ") 41 | 42 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/ibapi/execution.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | 7 | 8 | from ibapi.object_implem import Object 9 | 10 | class Execution(Object): 11 | 12 | def __init__(self): 13 | self.execId = "" 14 | self.time = "" 15 | self.acctNumber = "" 16 | self.exchange = "" 17 | self.side = "" 18 | self.shares = 0. 19 | self.price = 0. 20 | self.permId = 0 21 | self.clientId = 0 22 | self.orderId = 0 23 | self.liquidation = 0 24 | self.cumQty = 0. 25 | self.avgPrice = 0. 26 | self.orderRef = "" 27 | self.evRule = "" 28 | self.evMultiplier = 0. 29 | self.modelCode = "" 30 | self.lastLiquidity = 0 31 | 32 | def __str__(self): 33 | return "ExecId: %s, Time: %s, Account: %s, Exchange: %s, Side: %s, Shares: %f, Price: %f, PermId: %d, " \ 34 | "ClientId: %d, OrderId: %d, Liquidation: %d, CumQty: %f, AvgPrice: %f, OrderRef: %s, EvRule: %s, " \ 35 | "EvMultiplier: %f, ModelCode: %s, LastLiquidity: %d" % (self.execId, self.time, self.acctNumber, 36 | self.exchange, self.side, self.shares, self.price, self.permId, self.clientId, self.orderId, self.liquidation, 37 | self.cumQty, self.avgPrice, self.orderRef, self.evRule, self.evMultiplier, self.modelCode, self.lastLiquidity) 38 | 39 | class ExecutionFilter(Object): 40 | 41 | # Filter fields 42 | def __init__(self): 43 | self.clientId = 0 44 | self.acctCode = "" 45 | self.time = "" 46 | self.symbol = "" 47 | self.secType = "" 48 | self.exchange = "" 49 | self.side = "" 50 | 51 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/ibapi/message.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | 7 | """ 8 | High level IB message info. 9 | """ 10 | 11 | 12 | # field types 13 | INT = 1 14 | STR = 2 15 | FLT = 3 16 | 17 | 18 | # incoming msg id's 19 | class IN: 20 | TICK_PRICE = 1 21 | TICK_SIZE = 2 22 | ORDER_STATUS = 3 23 | ERR_MSG = 4 24 | OPEN_ORDER = 5 25 | ACCT_VALUE = 6 26 | PORTFOLIO_VALUE = 7 27 | ACCT_UPDATE_TIME = 8 28 | NEXT_VALID_ID = 9 29 | CONTRACT_DATA = 10 30 | EXECUTION_DATA = 11 31 | MARKET_DEPTH = 12 32 | MARKET_DEPTH_L2 = 13 33 | NEWS_BULLETINS = 14 34 | MANAGED_ACCTS = 15 35 | RECEIVE_FA = 16 36 | HISTORICAL_DATA = 17 37 | BOND_CONTRACT_DATA = 18 38 | SCANNER_PARAMETERS = 19 39 | SCANNER_DATA = 20 40 | TICK_OPTION_COMPUTATION = 21 41 | TICK_GENERIC = 45 42 | TICK_STRING = 46 43 | TICK_EFP = 47 44 | CURRENT_TIME = 49 45 | REAL_TIME_BARS = 50 46 | FUNDAMENTAL_DATA = 51 47 | CONTRACT_DATA_END = 52 48 | OPEN_ORDER_END = 53 49 | ACCT_DOWNLOAD_END = 54 50 | EXECUTION_DATA_END = 55 51 | DELTA_NEUTRAL_VALIDATION = 56 52 | TICK_SNAPSHOT_END = 57 53 | MARKET_DATA_TYPE = 58 54 | COMMISSION_REPORT = 59 55 | POSITION_DATA = 61 56 | POSITION_END = 62 57 | ACCOUNT_SUMMARY = 63 58 | ACCOUNT_SUMMARY_END = 64 59 | VERIFY_MESSAGE_API = 65 60 | VERIFY_COMPLETED = 66 61 | DISPLAY_GROUP_LIST = 67 62 | DISPLAY_GROUP_UPDATED = 68 63 | VERIFY_AND_AUTH_MESSAGE_API = 69 64 | VERIFY_AND_AUTH_COMPLETED = 70 65 | POSITION_MULTI = 71 66 | POSITION_MULTI_END = 72 67 | ACCOUNT_UPDATE_MULTI = 73 68 | ACCOUNT_UPDATE_MULTI_END = 74 69 | SECURITY_DEFINITION_OPTION_PARAMETER = 75 70 | SECURITY_DEFINITION_OPTION_PARAMETER_END = 76 71 | SOFT_DOLLAR_TIERS = 77 72 | FAMILY_CODES = 78 73 | SYMBOL_SAMPLES = 79 74 | MKT_DEPTH_EXCHANGES = 80 75 | TICK_REQ_PARAMS = 81 76 | SMART_COMPONENTS = 82 77 | NEWS_ARTICLE = 83 78 | TICK_NEWS = 84 79 | NEWS_PROVIDERS = 85 80 | HISTORICAL_NEWS = 86 81 | HISTORICAL_NEWS_END = 87 82 | HEAD_TIMESTAMP = 88 83 | HISTOGRAM_DATA = 89 84 | HISTORICAL_DATA_UPDATE = 90 85 | REROUTE_MKT_DATA_REQ = 91 86 | REROUTE_MKT_DEPTH_REQ = 92 87 | MARKET_RULE = 93 88 | PNL = 94 89 | PNL_SINGLE = 95 90 | HISTORICAL_TICKS = 96 91 | HISTORICAL_TICKS_BID_ASK = 97 92 | HISTORICAL_TICKS_LAST = 98 93 | TICK_BY_TICK = 99 94 | ORDER_BOUND = 100 95 | COMPLETED_ORDER = 101 96 | COMPLETED_ORDERS_END = 102 97 | 98 | # outgoing msg id's 99 | class OUT: 100 | REQ_MKT_DATA = 1 101 | CANCEL_MKT_DATA = 2 102 | PLACE_ORDER = 3 103 | CANCEL_ORDER = 4 104 | REQ_OPEN_ORDERS = 5 105 | REQ_ACCT_DATA = 6 106 | REQ_EXECUTIONS = 7 107 | REQ_IDS = 8 108 | REQ_CONTRACT_DATA = 9 109 | REQ_MKT_DEPTH = 10 110 | CANCEL_MKT_DEPTH = 11 111 | REQ_NEWS_BULLETINS = 12 112 | CANCEL_NEWS_BULLETINS = 13 113 | SET_SERVER_LOGLEVEL = 14 114 | REQ_AUTO_OPEN_ORDERS = 15 115 | REQ_ALL_OPEN_ORDERS = 16 116 | REQ_MANAGED_ACCTS = 17 117 | REQ_FA = 18 118 | REPLACE_FA = 19 119 | REQ_HISTORICAL_DATA = 20 120 | EXERCISE_OPTIONS = 21 121 | REQ_SCANNER_SUBSCRIPTION = 22 122 | CANCEL_SCANNER_SUBSCRIPTION = 23 123 | REQ_SCANNER_PARAMETERS = 24 124 | CANCEL_HISTORICAL_DATA = 25 125 | REQ_CURRENT_TIME = 49 126 | REQ_REAL_TIME_BARS = 50 127 | CANCEL_REAL_TIME_BARS = 51 128 | REQ_FUNDAMENTAL_DATA = 52 129 | CANCEL_FUNDAMENTAL_DATA = 53 130 | REQ_CALC_IMPLIED_VOLAT = 54 131 | REQ_CALC_OPTION_PRICE = 55 132 | CANCEL_CALC_IMPLIED_VOLAT = 56 133 | CANCEL_CALC_OPTION_PRICE = 57 134 | REQ_GLOBAL_CANCEL = 58 135 | REQ_MARKET_DATA_TYPE = 59 136 | REQ_POSITIONS = 61 137 | REQ_ACCOUNT_SUMMARY = 62 138 | CANCEL_ACCOUNT_SUMMARY = 63 139 | CANCEL_POSITIONS = 64 140 | VERIFY_REQUEST = 65 141 | VERIFY_MESSAGE = 66 142 | QUERY_DISPLAY_GROUPS = 67 143 | SUBSCRIBE_TO_GROUP_EVENTS = 68 144 | UPDATE_DISPLAY_GROUP = 69 145 | UNSUBSCRIBE_FROM_GROUP_EVENTS = 70 146 | START_API = 71 147 | VERIFY_AND_AUTH_REQUEST = 72 148 | VERIFY_AND_AUTH_MESSAGE = 73 149 | REQ_POSITIONS_MULTI = 74 150 | CANCEL_POSITIONS_MULTI = 75 151 | REQ_ACCOUNT_UPDATES_MULTI = 76 152 | CANCEL_ACCOUNT_UPDATES_MULTI = 77 153 | REQ_SEC_DEF_OPT_PARAMS = 78 154 | REQ_SOFT_DOLLAR_TIERS = 79 155 | REQ_FAMILY_CODES = 80 156 | REQ_MATCHING_SYMBOLS = 81 157 | REQ_MKT_DEPTH_EXCHANGES = 82 158 | REQ_SMART_COMPONENTS = 83 159 | REQ_NEWS_ARTICLE = 84 160 | REQ_NEWS_PROVIDERS = 85 161 | REQ_HISTORICAL_NEWS = 86 162 | REQ_HEAD_TIMESTAMP = 87 163 | REQ_HISTOGRAM_DATA = 88 164 | CANCEL_HISTOGRAM_DATA = 89 165 | CANCEL_HEAD_TIMESTAMP = 90 166 | REQ_MARKET_RULE = 91 167 | REQ_PNL = 92 168 | CANCEL_PNL = 93 169 | REQ_PNL_SINGLE = 94 170 | CANCEL_PNL_SINGLE = 95 171 | REQ_HISTORICAL_TICKS = 96 172 | REQ_TICK_BY_TICK_DATA = 97 173 | CANCEL_TICK_BY_TICK_DATA = 98 174 | REQ_COMPLETED_ORDERS = 99 175 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/ibapi/news.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | # TWS New Bulletins constants 7 | NEWS_MSG = 1 # standard IB news bulleting message 8 | EXCHANGE_AVAIL_MSG = 2 # control message specifing that an exchange is available for trading 9 | EXCHANGE_UNAVAIL_MSG = 3 # control message specifing that an exchange is unavailable for trading 10 | 11 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/ibapi/object_implem.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | class Object(object): 7 | 8 | def __str__(self): 9 | return "Object" 10 | 11 | def __repr__(self): 12 | return str(id(self)) + ": " + self.__str__() 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/ibapi/order.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | 7 | from ibapi.common import UNSET_INTEGER, UNSET_DOUBLE 8 | from ibapi.object_implem import Object 9 | from ibapi.softdollartier import SoftDollarTier 10 | 11 | # enum Origin 12 | (CUSTOMER, FIRM, UNKNOWN) = range(3) 13 | 14 | # enum AuctionStrategy 15 | (AUCTION_UNSET, AUCTION_MATCH, 16 | AUCTION_IMPROVEMENT, AUCTION_TRANSPARENT) = range(4) 17 | 18 | 19 | class OrderComboLeg(Object): 20 | def __init__(self): 21 | self.price = UNSET_DOUBLE # type: float 22 | 23 | def __str__(self): 24 | return "%f" % self.price 25 | 26 | 27 | class Order(Object): 28 | def __init__(self): 29 | self.softDollarTier = SoftDollarTier("", "", "") 30 | # order identifier 31 | self.orderId = 0 32 | self.clientId = 0 33 | self.permId = 0 34 | 35 | # main order fields 36 | self.action = "" 37 | self.totalQuantity = 0 38 | self.orderType = "" 39 | self.lmtPrice = UNSET_DOUBLE 40 | self.auxPrice = UNSET_DOUBLE 41 | 42 | # extended order fields 43 | self.tif = "" # "Time in Force" - DAY, GTC, etc. 44 | self.activeStartTime = "" # for GTC orders 45 | self.activeStopTime = "" # for GTC orders 46 | self.ocaGroup = "" # one cancels all group name 47 | self.ocaType = 0 # 1 = CANCEL_WITH_BLOCK, 2 = REDUCE_WITH_BLOCK, 3 = REDUCE_NON_BLOCK 48 | self.orderRef = "" 49 | self.transmit = True # if false, order will be created but not transmited 50 | self.parentId = 0 # Parent order Id, to associate Auto STP or TRAIL orders with the original order. 51 | self.blockOrder = False 52 | self.sweepToFill = False 53 | self.displaySize = 0 54 | self.triggerMethod = 0 # 0=Default, 1=Double_Bid_Ask, 2=Last, 3=Double_Last, 4=Bid_Ask, 7=Last_or_Bid_Ask, 8=Mid-point 55 | self.outsideRth = False 56 | self.hidden = False 57 | self.goodAfterTime = "" # Format: 20060505 08:00:00 {time zone} 58 | self.goodTillDate = "" # Format: 20060505 08:00:00 {time zone} 59 | self.rule80A = "" # Individual = 'I', Agency = 'A', AgentOtherMember = 'W', IndividualPTIA = 'J', AgencyPTIA = 'U', AgentOtherMemberPTIA = 'M', IndividualPT = 'K', AgencyPT = 'Y', AgentOtherMemberPT = 'N' 60 | self.allOrNone = False 61 | self.minQty = UNSET_INTEGER #type: int 62 | self.percentOffset = UNSET_DOUBLE # type: float; REL orders only 63 | self.overridePercentageConstraints = False 64 | self.trailStopPrice = UNSET_DOUBLE # type: float 65 | self.trailingPercent = UNSET_DOUBLE # type: float; TRAILLIMIT orders only 66 | 67 | # financial advisors only 68 | self.faGroup = "" 69 | self.faProfile = "" 70 | self.faMethod = "" 71 | self.faPercentage = "" 72 | 73 | # institutional (ie non-cleared) only 74 | self.designatedLocation = "" #used only when shortSaleSlot=2 75 | self.openClose = "O" # O=Open, C=Close 76 | self.origin = CUSTOMER # 0=Customer, 1=Firm 77 | self.shortSaleSlot = 0 # type: int; 1 if you hold the shares, 2 if they will be delivered from elsewhere. Only for Action=SSHORT 78 | self.exemptCode = -1 79 | 80 | # SMART routing only 81 | self.discretionaryAmt = 0 82 | self.eTradeOnly = True 83 | self.firmQuoteOnly = True 84 | self.nbboPriceCap = UNSET_DOUBLE # type: float 85 | self.optOutSmartRouting = False 86 | 87 | # BOX exchange orders only 88 | self.auctionStrategy = AUCTION_UNSET # type: int; AUCTION_MATCH, AUCTION_IMPROVEMENT, AUCTION_TRANSPARENT 89 | self.startingPrice = UNSET_DOUBLE # type: float 90 | self.stockRefPrice = UNSET_DOUBLE # type: float 91 | self.delta = UNSET_DOUBLE # type: float 92 | 93 | # pegged to stock and VOL orders only 94 | self.stockRangeLower = UNSET_DOUBLE # type: float 95 | self.stockRangeUpper = UNSET_DOUBLE # type: float 96 | 97 | self.randomizePrice = False 98 | self.randomizeSize = False 99 | 100 | # VOLATILITY ORDERS ONLY 101 | self.volatility = UNSET_DOUBLE # type: float 102 | self.volatilityType = UNSET_INTEGER # type: int # 1=daily, 2=annual 103 | self.deltaNeutralOrderType = "" 104 | self.deltaNeutralAuxPrice = UNSET_DOUBLE # type: float 105 | self.deltaNeutralConId = 0 106 | self.deltaNeutralSettlingFirm = "" 107 | self.deltaNeutralClearingAccount = "" 108 | self.deltaNeutralClearingIntent = "" 109 | self.deltaNeutralOpenClose = "" 110 | self.deltaNeutralShortSale = False 111 | self.deltaNeutralShortSaleSlot = 0 112 | self.deltaNeutralDesignatedLocation = "" 113 | self.continuousUpdate = False 114 | self.referencePriceType = UNSET_INTEGER # type: int; 1=Average, 2 = BidOrAsk 115 | 116 | # COMBO ORDERS ONLY 117 | self.basisPoints = UNSET_DOUBLE # type: float; EFP orders only 118 | self.basisPointsType = UNSET_INTEGER # type: int; EFP orders only 119 | 120 | # SCALE ORDERS ONLY 121 | self.scaleInitLevelSize = UNSET_INTEGER # type: int 122 | self.scaleSubsLevelSize = UNSET_INTEGER # type: int 123 | self.scalePriceIncrement = UNSET_DOUBLE # type: float 124 | self.scalePriceAdjustValue = UNSET_DOUBLE # type: float 125 | self.scalePriceAdjustInterval = UNSET_INTEGER # type: int 126 | self.scaleProfitOffset = UNSET_DOUBLE # type: float 127 | self.scaleAutoReset = False 128 | self.scaleInitPosition = UNSET_INTEGER # type: int 129 | self.scaleInitFillQty = UNSET_INTEGER # type: int 130 | self.scaleRandomPercent = False 131 | self.scaleTable = "" 132 | 133 | # HEDGE ORDERS 134 | self.hedgeType = "" # 'D' - delta, 'B' - beta, 'F' - FX, 'P' - pair 135 | self.hedgeParam = "" # 'beta=X' value for beta hedge, 'ratio=Y' for pair hedge 136 | 137 | # Clearing info 138 | self.account = "" # IB account 139 | self.settlingFirm = "" 140 | self.clearingAccount = "" #True beneficiary of the order 141 | self.clearingIntent = "" # "" (Default), "IB", "Away", "PTA" (PostTrade) 142 | 143 | # ALGO ORDERS ONLY 144 | self.algoStrategy = "" 145 | 146 | self.algoParams = None #TagValueList 147 | self.smartComboRoutingParams = None #TagValueList 148 | 149 | self.algoId = "" 150 | 151 | # What-if 152 | self.whatIf = False 153 | 154 | # Not Held 155 | self.notHeld = False 156 | self.solicited = False 157 | 158 | # models 159 | self.modelCode = "" 160 | 161 | # order combo legs 162 | 163 | self.orderComboLegs = None # OrderComboLegListSPtr 164 | 165 | self.orderMiscOptions = None # TagValueList 166 | 167 | # VER PEG2BENCH fields: 168 | self.referenceContractId = 0 169 | self.peggedChangeAmount = 0. 170 | self.isPeggedChangeAmountDecrease = False 171 | self.referenceChangeAmount = 0. 172 | self.referenceExchangeId = "" 173 | self.adjustedOrderType = "" 174 | 175 | self.triggerPrice = UNSET_DOUBLE 176 | self.adjustedStopPrice = UNSET_DOUBLE 177 | self.adjustedStopLimitPrice = UNSET_DOUBLE 178 | self.adjustedTrailingAmount = UNSET_DOUBLE 179 | self.adjustableTrailingUnit = 0 180 | self.lmtPriceOffset = UNSET_DOUBLE 181 | 182 | self.conditions = [] # std::vector> 183 | self.conditionsCancelOrder = False 184 | self.conditionsIgnoreRth = False 185 | 186 | # ext operator 187 | self.extOperator = "" 188 | 189 | # native cash quantity 190 | self.cashQty = UNSET_DOUBLE 191 | 192 | self.mifid2DecisionMaker = "" 193 | self.mifid2DecisionAlgo = "" 194 | self.mifid2ExecutionTrader = "" 195 | self.mifid2ExecutionAlgo = "" 196 | 197 | self.dontUseAutoPriceForHedge = False 198 | 199 | self.isOmsContainer = False 200 | 201 | self.discretionaryUpToLimitPrice = False 202 | 203 | self.autoCancelDate = "" 204 | self.filledQuantity = UNSET_DOUBLE 205 | self.refFuturesConId = 0 206 | self.autoCancelParent = False 207 | self.shareholder = "" 208 | self.imbalanceOnly = False 209 | self.routeMarketableToBbo = False 210 | self.parentPermId = 0 211 | 212 | self.usePriceMgmtAlgo = None 213 | 214 | def __str__(self): 215 | s = "%s,%d,%s:" % (self.orderId, self.clientId, self.permId) 216 | 217 | s += " %s %s %d@%f" % ( 218 | self.orderType, 219 | self.action, 220 | self.totalQuantity, 221 | self.lmtPrice) 222 | 223 | s += " %s" % self.tif 224 | 225 | if self.orderComboLegs: 226 | s += " CMB(" 227 | for leg in self.orderComboLegs: 228 | s += str(leg) + "," 229 | s += ")" 230 | 231 | if self.conditions: 232 | s += " COND(" 233 | for cond in self.conditions: 234 | s += str(cond) + "," 235 | s += ")" 236 | 237 | return s 238 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/ibapi/order_condition.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | 7 | from ibapi import comm 8 | from ibapi.common import UNSET_DOUBLE 9 | from ibapi.object_implem import Object 10 | from ibapi.enum_implem import Enum 11 | from ibapi.utils import decode 12 | 13 | #TODO: add support for Rebate, P/L, ShortableShares conditions 14 | 15 | 16 | 17 | class OrderCondition(Object): 18 | Price = 1 19 | Time = 3 20 | Margin = 4 21 | Execution = 5 22 | Volume = 6 23 | PercentChange = 7 24 | 25 | def __init__(self, condType): 26 | self.condType = condType 27 | self.isConjunctionConnection = True 28 | 29 | def type(self): 30 | return self.condType 31 | 32 | def And(self): 33 | self.isConjunctionConnection = True 34 | return self 35 | 36 | def Or(self): 37 | self.isConjunctionConnection = False 38 | return self 39 | 40 | def decode(self, fields): 41 | connector = decode(str, fields) 42 | self.isConjunctionConnection = connector == "a" 43 | 44 | def make_fields(self): 45 | flds = [] 46 | flds.append(comm.make_field("a" if self.isConjunctionConnection else "o")) 47 | return flds 48 | 49 | def __str__(self): 50 | return "" if self.isConjunctionConnection else "" 51 | 52 | 53 | class ExecutionCondition(OrderCondition): 54 | 55 | def __init__(self, secType=None, exch=None, symbol=None): 56 | OrderCondition.__init__(self, OrderCondition.Execution) 57 | self.secType = secType 58 | self.exchange = exch 59 | self.symbol = symbol 60 | 61 | def decode(self, fields): 62 | OrderCondition.decode(self, fields) 63 | self.secType = decode(str, fields) 64 | self.exchange = decode(str, fields) 65 | self.symbol = decode(str, fields) 66 | 67 | def make_fields(self): 68 | flds = OrderCondition.make_fields(self) + \ 69 | [comm.make_field(self.secType), 70 | comm.make_field(self.exchange), 71 | comm.make_field(self.symbol)] 72 | return flds 73 | 74 | def __str__(self): 75 | return "trade occurs for " + self.symbol + " symbol on " + \ 76 | self.exchange + " exchange for " + self.secType + " security type" 77 | 78 | 79 | class OperatorCondition(OrderCondition): 80 | def __init__(self, condType=None, isMore=None): 81 | OrderCondition.__init__(self, condType) 82 | self.isMore = isMore 83 | 84 | def valueToString(self) -> str: 85 | raise NotImplementedError("abstractmethod!") 86 | 87 | def setValueFromString(self, text: str) -> None: 88 | raise NotImplementedError("abstractmethod!") 89 | 90 | def decode(self, fields): 91 | OrderCondition.decode(self, fields) 92 | self.isMore = decode(bool, fields) 93 | text = decode(str, fields) 94 | self.setValueFromString(text) 95 | 96 | def make_fields(self): 97 | flds = OrderCondition.make_fields(self) + \ 98 | [comm.make_field(self.isMore), 99 | comm.make_field(self.valueToString()), ] 100 | return flds 101 | 102 | def __str__(self): 103 | sb = ">= " if self.isMore else "<= " 104 | return " %s %s" % (sb, self.valueToString()) 105 | 106 | 107 | class MarginCondition(OperatorCondition): 108 | def __init__(self, isMore=None, percent=None): 109 | OperatorCondition.__init__(self, OrderCondition.Margin, isMore) 110 | self.percent = percent 111 | 112 | def decode(self, fields): 113 | OperatorCondition.decode(self, fields) 114 | 115 | def make_fields(self): 116 | flds = OperatorCondition.make_fields(self) 117 | return flds 118 | 119 | def valueToString(self) -> str: 120 | return str(self.percent) 121 | 122 | def setValueFromString(self, text: str) -> None: 123 | self.percent = float(text) 124 | 125 | def __str__(self): 126 | return "the margin cushion percent %s " % ( 127 | OperatorCondition.__str__(self)) 128 | 129 | 130 | class ContractCondition(OperatorCondition): 131 | def __init__(self, condType=None, conId=None, exch=None, isMore=None): 132 | OperatorCondition.__init__(self, condType, isMore) 133 | self.conId = conId 134 | self.exchange = exch 135 | 136 | def decode(self, fields): 137 | OperatorCondition.decode(self, fields) 138 | self.conId = decode(int, fields) 139 | self.exchange = decode(str, fields) 140 | 141 | def make_fields(self): 142 | flds = OperatorCondition.make_fields(self) + \ 143 | [comm.make_field(self.conId), 144 | comm.make_field(self.exchange), ] 145 | return flds 146 | 147 | def __str__(self): 148 | return "%s on %s is %s " % (self.conId, self.exchange, 149 | OperatorCondition.__str__(self)) 150 | 151 | 152 | class TimeCondition(OperatorCondition): 153 | def __init__(self, isMore=None, time=None): 154 | OperatorCondition.__init__(self, OrderCondition.Time, isMore) 155 | self.time = time 156 | 157 | def decode(self, fields): 158 | OperatorCondition.decode(self, fields) 159 | 160 | def make_fields(self): 161 | flds = OperatorCondition.make_fields(self) 162 | return flds 163 | 164 | def valueToString(self) -> str: 165 | return self.time 166 | 167 | def setValueFromString(self, text: str) -> None: 168 | self.time = text 169 | 170 | def __str__(self): 171 | return "time is %s " % (OperatorCondition.__str__(self)) 172 | 173 | 174 | class PriceCondition(ContractCondition): 175 | TriggerMethodEnum = Enum( 176 | "Default", # = 0, 177 | "DoubleBidAsk", # = 1, 178 | "Last", # = 2, 179 | "DoubleLast", # = 3, 180 | "BidAsk", # = 4, 181 | "N/A1", 182 | "N/A2", 183 | "LastBidAsk", #= 7, 184 | "MidPoint") #= 8 185 | 186 | def __init__(self, triggerMethod=None, conId=None, exch=None, isMore=None, 187 | price=None): 188 | ContractCondition.__init__(self, OrderCondition.Price, conId, exch, 189 | isMore) 190 | self.price = price 191 | self.triggerMethod = triggerMethod 192 | 193 | def decode(self, fields): 194 | ContractCondition.decode(self, fields) 195 | self.triggerMethod = decode(int, fields) 196 | 197 | def make_fields(self): 198 | flds = ContractCondition.make_fields(self) + \ 199 | [comm.make_field(self.triggerMethod), ] 200 | return flds 201 | 202 | def valueToString(self) -> str: 203 | return str(self.price) 204 | 205 | def setValueFromString(self, text: str) -> None: 206 | self.price = float(text) 207 | 208 | def __str__(self): 209 | return "%s price of %s " % ( 210 | PriceCondition.TriggerMethodEnum.to_str(self.triggerMethod), 211 | ContractCondition.__str__(self)) 212 | 213 | 214 | class PercentChangeCondition(ContractCondition): 215 | def __init__(self, conId=None, exch=None, isMore=None, 216 | changePercent=UNSET_DOUBLE): 217 | ContractCondition.__init__(self, OrderCondition.PercentChange, conId, 218 | exch, isMore) 219 | self.changePercent = changePercent 220 | 221 | def decode(self, fields): 222 | ContractCondition.decode(self, fields) 223 | 224 | def make_fields(self): 225 | flds = ContractCondition.make_fields(self) 226 | return flds 227 | 228 | def valueToString(self) -> str: 229 | return str(self.changePercent) 230 | 231 | def setValueFromString(self, text: str) -> None: 232 | self.changePercent = float(text) 233 | 234 | def __str__(self): 235 | return "percent change of %s " % ( 236 | ContractCondition.__str__(self)) 237 | 238 | 239 | class VolumeCondition(ContractCondition): 240 | def __init__(self, conId=None, exch=None, isMore=None, volume=None): 241 | ContractCondition.__init__(self, OrderCondition.Volume, conId, exch, 242 | isMore) 243 | self.volume = volume 244 | 245 | def decode(self, fields): 246 | ContractCondition.decode(self, fields) 247 | 248 | def make_fields(self): 249 | flds = ContractCondition.make_fields(self) 250 | return flds 251 | 252 | def valueToString(self) -> str: 253 | return str(self.volume) 254 | 255 | def setValueFromString(self, text: str) -> None: 256 | self.volume = int(text) 257 | 258 | def __str__(self): 259 | return "volume of %s " % ( 260 | ContractCondition.__str__(self)) 261 | 262 | 263 | def Create(condType): 264 | cond = None 265 | 266 | if OrderCondition.Execution == condType: 267 | cond = ExecutionCondition() 268 | elif OrderCondition.Margin == condType: 269 | cond = MarginCondition() 270 | elif OrderCondition.PercentChange == condType: 271 | cond = PercentChangeCondition() 272 | elif OrderCondition.Price == condType: 273 | cond = PriceCondition() 274 | elif OrderCondition.Time == condType: 275 | cond = TimeCondition() 276 | elif OrderCondition.Volume == condType: 277 | cond = VolumeCondition() 278 | 279 | return cond 280 | 281 | 282 | 283 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/ibapi/order_state.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | from ibapi.common import UNSET_DOUBLE 7 | 8 | 9 | class OrderState: 10 | 11 | def __init__(self): 12 | self.status= "" 13 | 14 | self.initMarginBefore= "" 15 | self.maintMarginBefore= "" 16 | self.equityWithLoanBefore= "" 17 | self.initMarginChange= "" 18 | self.maintMarginChange= "" 19 | self.equityWithLoanChange= "" 20 | self.initMarginAfter= "" 21 | self.maintMarginAfter= "" 22 | self.equityWithLoanAfter= "" 23 | 24 | self.commission = UNSET_DOUBLE # type: float 25 | self.minCommission = UNSET_DOUBLE # type: float 26 | self.maxCommission = UNSET_DOUBLE # type: float 27 | self.commissionCurrency = "" 28 | self.warningText = "" 29 | self.completedTime = "" 30 | self.completedStatus = "" 31 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/ibapi/orderdecoder.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | from ibapi import order_condition 7 | from ibapi.object_implem import Object 8 | from ibapi.utils import * # @UnusedWildImport 9 | from ibapi.server_versions import * # @UnusedWildImport 10 | from ibapi.order import OrderComboLeg 11 | from ibapi.contract import ComboLeg 12 | from ibapi.tag_value import TagValue 13 | from ibapi.wrapper import DeltaNeutralContract 14 | from ibapi.softdollartier import SoftDollarTier 15 | 16 | logger = logging.getLogger(__name__) 17 | 18 | class OrderDecoder(Object): 19 | def __init__(self, contract, order, orderState, version, serverVersion): 20 | self.contract = contract 21 | self.order = order 22 | self.orderState = orderState 23 | self.version = version 24 | self.serverVersion = serverVersion 25 | self.discoverParams() 26 | 27 | def decodeOrderId(self, fields): 28 | self.order.orderId = decode(int, fields) 29 | 30 | def decodeContractFields(self, fields): 31 | self.contract.conId = decode(int, fields) 32 | self.contract.symbol = decode(str, fields) 33 | self.contract.secType = decode(str, fields) 34 | self.contract.lastTradeDateOrContractMonth = decode(str, fields) 35 | self.contract.strike = decode(float, fields) 36 | self.contract.right = decode(str, fields) 37 | if self.version >= 32: 38 | self.contract.multiplier = decode(str, fields) 39 | self.contract.exchange = decode(str, fields) 40 | self.contract.currency = decode(str, fields) 41 | self.contract.localSymbol = decode(str, fields) 42 | if self.version >= 32: 43 | self.contract.tradingClass = decode(str, fields) 44 | 45 | def decodeAction(self, fields): 46 | self.order.action = decode(str, fields) 47 | 48 | def decodeTotalQuantity(self, fields): 49 | if self.serverVersion >= MIN_SERVER_VER_FRACTIONAL_POSITIONS: 50 | self.order.totalQuantity = decode(float, fields) 51 | else: 52 | self.order.totalQuantity = decode(int, fields) 53 | 54 | def decodeOrderType(self, fields): 55 | self.order.orderType = decode(str, fields) 56 | 57 | def decodeLmtPrice(self, fields): 58 | if self.version < 29: 59 | self.order.lmtPrice = decode(float, fields) 60 | else: 61 | self.order.lmtPrice = decode(float, fields, SHOW_UNSET) 62 | 63 | def decodeAuxPrice(self, fields): 64 | if self.version < 30: 65 | self.order.auxPrice = decode(float, fields) 66 | else: 67 | self.order.auxPrice = decode(float, fields, SHOW_UNSET) 68 | 69 | def decodeTIF(self, fields): 70 | self.order.tif = decode(str, fields) 71 | 72 | def decodeOcaGroup(self, fields): 73 | self.order.ocaGroup = decode(str, fields) 74 | 75 | def decodeAccount(self, fields): 76 | self.order.account = decode(str, fields) 77 | 78 | def decodeOpenClose(self, fields): 79 | self.order.openClose = decode(str, fields) 80 | 81 | def decodeOrigin(self, fields): 82 | self.order.origin = decode(int, fields) 83 | 84 | def decodeOrderRef(self, fields): 85 | self.order.orderRef = decode(str, fields) 86 | 87 | def decodeClientId(self, fields): 88 | self.order.clientId = decode(int, fields) 89 | 90 | def decodePermId(self, fields): 91 | self.order.permId = decode(int, fields) 92 | 93 | def decodeOutsideRth(self, fields): 94 | self.order.outsideRth = decode(bool, fields) 95 | 96 | def decodeHidden(self, fields): 97 | self.order.hidden = decode(bool, fields) 98 | 99 | def decodeDiscretionaryAmt(self, fields): 100 | self.order.discretionaryAmt = decode(float, fields) 101 | 102 | def decodeGoodAfterTime(self, fields): 103 | self.order.goodAfterTime = decode(str, fields) 104 | 105 | def skipSharesAllocation(self, fields): 106 | _sharesAllocation = decode(str, fields) # deprecated 107 | 108 | def decodeFAParams(self, fields): 109 | self.order.faGroup = decode(str, fields) 110 | self.order.faMethod = decode(str, fields) 111 | self.order.faPercentage = decode(str, fields) 112 | self.order.faProfile = decode(str, fields) 113 | 114 | def decodeModelCode(self, fields): 115 | if self.serverVersion >= MIN_SERVER_VER_MODELS_SUPPORT: 116 | self.order.modelCode = decode(str, fields) 117 | 118 | def decodeGoodTillDate(self, fields): 119 | self.order.goodTillDate = decode(str, fields) 120 | 121 | def decodeRule80A(self, fields): 122 | self.order.rule80A = decode(str, fields) 123 | 124 | def decodePercentOffset(self, fields): 125 | self.order.percentOffset = decode(float, fields, SHOW_UNSET) 126 | 127 | def decodeSettlingFirm(self, fields): 128 | self.order.settlingFirm = decode(str, fields) 129 | 130 | def decodeShortSaleParams(self, fields): 131 | self.order.shortSaleSlot = decode(int, fields) 132 | self.order.designatedLocation = decode(str, fields) 133 | if self.serverVersion == MIN_SERVER_VER_SSHORTX_OLD: 134 | decode(int, fields) 135 | elif self.version >= 23: 136 | self.order.exemptCode = decode(int, fields) 137 | 138 | def decodeAuctionStrategy(self, fields): 139 | self.order.auctionStrategy = decode(int, fields) 140 | 141 | def decodeBoxOrderParams(self, fields): 142 | self.order.startingPrice = decode(float, fields, SHOW_UNSET) 143 | self.order.stockRefPrice = decode(float, fields, SHOW_UNSET) 144 | self.order.delta = decode(float, fields, SHOW_UNSET) 145 | 146 | def decodePegToStkOrVolOrderParams(self, fields): 147 | self.order.stockRangeLower = decode(float, fields, SHOW_UNSET) 148 | self.order.stockRangeUpper = decode(float, fields, SHOW_UNSET) 149 | 150 | def decodeDisplaySize(self, fields): 151 | self.order.displaySize = decode(int, fields) 152 | 153 | def decodeBlockOrder(self, fields): 154 | self.order.blockOrder = decode(bool, fields) 155 | 156 | def decodeSweepToFill(self, fields): 157 | self.order.sweepToFill = decode(bool, fields) 158 | 159 | def decodeAllOrNone(self, fields): 160 | self.order.allOrNone = decode(bool, fields) 161 | 162 | def decodeMinQty(self, fields): 163 | self.order.minQty = decode(int, fields, SHOW_UNSET) 164 | 165 | def decodeOcaType(self, fields): 166 | self.order.ocaType = decode(int, fields) 167 | 168 | def decodeETradeOnly(self, fields): 169 | self.order.eTradeOnly = decode(bool, fields) 170 | 171 | def decodeFirmQuoteOnly(self, fields): 172 | self.order.firmQuoteOnly = decode(bool, fields) 173 | 174 | def decodeNbboPriceCap(self, fields): 175 | self.order.nbboPriceCap = decode(float, fields, SHOW_UNSET) 176 | 177 | def decodeParentId(self, fields): 178 | self.order.parentId = decode(int, fields) 179 | 180 | def decodeTriggerMethod(self, fields): 181 | self.order.triggerMethod = decode(int, fields) 182 | 183 | 184 | def decodeVolOrderParams(self, fields, readOpenOrderAttribs): 185 | self.order.volatility = decode(float, fields, SHOW_UNSET) 186 | self.order.volatilityType = decode(int, fields) 187 | self.order.deltaNeutralOrderType = decode(str, fields) 188 | self.order.deltaNeutralAuxPrice = decode(float, fields, SHOW_UNSET) 189 | 190 | if self.version >= 27 and self.order.deltaNeutralOrderType: 191 | self.order.deltaNeutralConId = decode(int, fields) 192 | if readOpenOrderAttribs: 193 | self.order.deltaNeutralSettlingFirm = decode(str, fields) 194 | self.order.deltaNeutralClearingAccount = decode(str, fields) 195 | self.order.deltaNeutralClearingIntent = decode(str, fields) 196 | 197 | if self.version >= 31 and self.order.deltaNeutralOrderType: 198 | if readOpenOrderAttribs: 199 | self.order.deltaNeutralOpenClose = decode(str, fields) 200 | self.order.deltaNeutralShortSale = decode(bool, fields) 201 | self.order.deltaNeutralShortSaleSlot = decode(int, fields) 202 | self.order.deltaNeutralDesignatedLocation = decode(str, fields) 203 | 204 | self.order.continuousUpdate = decode(bool, fields) 205 | self.order.referencePriceType = decode(int, fields) 206 | 207 | def decodeTrailParams(self, fields): 208 | self.order.trailStopPrice = decode(float, fields, SHOW_UNSET) 209 | if self.version >= 30: 210 | self.order.trailingPercent = decode(float, fields, SHOW_UNSET) 211 | 212 | def decodeBasisPoints(self, fields): 213 | self.order.basisPoints = decode(float, fields, SHOW_UNSET) 214 | self.order.basisPointsType = decode(int, fields, SHOW_UNSET) 215 | 216 | def decodeComboLegs(self, fields): 217 | self.contract.comboLegsDescrip = decode(str, fields) 218 | 219 | if self.version >= 29: 220 | comboLegsCount = decode(int, fields) 221 | 222 | if comboLegsCount > 0: 223 | self.contract.comboLegs = [] 224 | for _ in range(comboLegsCount): 225 | comboLeg = ComboLeg() 226 | comboLeg.conId = decode(int, fields) 227 | comboLeg.ratio = decode(int, fields) 228 | comboLeg.action = decode(str, fields) 229 | comboLeg.exchange = decode(str, fields) 230 | comboLeg.openClose = decode(int, fields) 231 | comboLeg.shortSaleSlot = decode(int, fields) 232 | comboLeg.designatedLocation = decode(str, fields) 233 | comboLeg.exemptCode = decode(int, fields) 234 | self.contract.comboLegs.append(comboLeg) 235 | 236 | orderComboLegsCount = decode(int, fields) 237 | if orderComboLegsCount > 0: 238 | self.order.orderComboLegs = [] 239 | for _ in range(orderComboLegsCount): 240 | orderComboLeg = OrderComboLeg() 241 | orderComboLeg.price = decode(float, fields, SHOW_UNSET) 242 | self.order.orderComboLegs.append(orderComboLeg) 243 | 244 | 245 | def decodeSmartComboRoutingParams(self, fields): 246 | if self.version >= 26: 247 | smartComboRoutingParamsCount = decode(int, fields) 248 | if smartComboRoutingParamsCount > 0: 249 | self.order.smartComboRoutingParams = [] 250 | for _ in range(smartComboRoutingParamsCount): 251 | tagValue = TagValue() 252 | tagValue.tag = decode(str, fields) 253 | tagValue.value = decode(str, fields) 254 | self.order.smartComboRoutingParams.append(tagValue) 255 | 256 | def decodeScaleOrderParams(self, fields): 257 | if self.version >= 20: 258 | self.order.scaleInitLevelSize = decode(int, fields, SHOW_UNSET) 259 | self.order.scaleSubsLevelSize = decode(int, fields, SHOW_UNSET) 260 | else: 261 | self.order.notSuppScaleNumComponents = decode(int, fields, SHOW_UNSET) 262 | self.order.scaleInitLevelSize = decode(int, fields, SHOW_UNSET) 263 | 264 | self.order.scalePriceIncrement = decode(float, fields, SHOW_UNSET) 265 | 266 | if self.version >= 28 and self.order.scalePriceIncrement != UNSET_DOUBLE \ 267 | and self.order.scalePriceIncrement > 0.0: 268 | self.order.scalePriceAdjustValue = decode(float, fields, SHOW_UNSET) 269 | self.order.scalePriceAdjustInterval = decode(int, fields, SHOW_UNSET) 270 | self.order.scaleProfitOffset = decode(float, fields, SHOW_UNSET) 271 | self.order.scaleAutoReset = decode(bool, fields) 272 | self.order.scaleInitPosition = decode(int, fields, SHOW_UNSET) 273 | self.order.scaleInitFillQty = decode(int, fields, SHOW_UNSET) 274 | self.order.scaleRandomPercent = decode(bool, fields) 275 | 276 | 277 | def decodeHedgeParams(self, fields): 278 | if self.version >= 24: 279 | self.order.hedgeType = decode(str, fields) 280 | if self.order.hedgeType: 281 | self.order.hedgeParam = decode(str, fields) 282 | 283 | def decodeOptOutSmartRouting(self, fields): 284 | if self.version >= 25: 285 | self.order.optOutSmartRouting = decode(bool, fields) 286 | 287 | def decodeClearingParams(self, fields): 288 | self.order.clearingAccount = decode(str, fields) 289 | self.order.clearingIntent = decode(str, fields) 290 | 291 | def decodeNotHeld(self, fields): 292 | if self.version >= 22: 293 | self.order.notHeld = decode(bool, fields) 294 | 295 | def decodeDeltaNeutral(self, fields): 296 | if self.version >= 20: 297 | deltaNeutralContractPresent = decode(bool, fields) 298 | if deltaNeutralContractPresent: 299 | self.contract.deltaNeutralContract = DeltaNeutralContract() 300 | self.contract.deltaNeutralContract.conId = decode(int, fields) 301 | self.contract.deltaNeutralContract.delta = decode(float, fields) 302 | self.contract.deltaNeutralContract.price = decode(float, fields) 303 | 304 | def decodeAlgoParams(self, fields): 305 | if self.version >= 21: 306 | self.order.algoStrategy = decode(str, fields) 307 | if self.order.algoStrategy: 308 | algoParamsCount = decode(int, fields) 309 | if algoParamsCount > 0: 310 | self.order.algoParams = [] 311 | for _ in range(algoParamsCount): 312 | tagValue = TagValue() 313 | tagValue.tag = decode(str, fields) 314 | tagValue.value = decode(str, fields) 315 | self.order.algoParams.append(tagValue) 316 | 317 | def decodeSolicited(self, fields): 318 | if self.version >= 33: 319 | self.order.solicited = decode(bool, fields) 320 | 321 | def decodeOrderStatus(self, fields): 322 | self.orderState.status = decode(str, fields) 323 | 324 | def decodeWhatIfInfoAndCommission(self, fields): 325 | self.order.whatIf = decode(bool, fields) 326 | OrderDecoder.decodeOrderStatus(self, fields) 327 | if self.serverVersion >= MIN_SERVER_VER_WHAT_IF_EXT_FIELDS: 328 | self.orderState.initMarginBefore = decode(str, fields) 329 | self.orderState.maintMarginBefore = decode(str, fields) 330 | self.orderState.equityWithLoanBefore = decode(str, fields) 331 | self.orderState.initMarginChange = decode(str, fields) 332 | self.orderState.maintMarginChange = decode(str, fields) 333 | self.orderState.equityWithLoanChange = decode(str, fields) 334 | 335 | self.orderState.initMarginAfter = decode(str, fields) 336 | self.orderState.maintMarginAfter = decode(str, fields) 337 | self.orderState.equityWithLoanAfter = decode(str, fields) 338 | 339 | self.orderState.commission = decode(float, fields, SHOW_UNSET) 340 | self.orderState.minCommission = decode(float, fields, SHOW_UNSET) 341 | self.orderState.maxCommission = decode(float, fields, SHOW_UNSET) 342 | self.orderState.commissionCurrency = decode(str, fields) 343 | self.orderState.warningText = decode(str, fields) 344 | 345 | def decodeVolRandomizeFlags(self, fields): 346 | if self.version >= 34: 347 | self.order.randomizeSize = decode(bool, fields) 348 | self.order.randomizePrice = decode(bool, fields) 349 | 350 | def decodePegToBenchParams(self, fields): 351 | if self.serverVersion >= MIN_SERVER_VER_PEGGED_TO_BENCHMARK: 352 | if self.order.orderType == "PEG BENCH": 353 | self.order.referenceContractId = decode(int, fields) 354 | self.order.isPeggedChangeAmountDecrease = decode(bool, fields) 355 | self.order.peggedChangeAmount = decode(float, fields) 356 | self.order.referenceChangeAmount = decode(float, fields) 357 | self.order.referenceExchangeId = decode(str, fields) 358 | 359 | def decodeConditions(self, fields): 360 | if self.serverVersion >= MIN_SERVER_VER_PEGGED_TO_BENCHMARK: 361 | conditionsSize = decode(int, fields) 362 | if conditionsSize > 0: 363 | self.order.conditions = [] 364 | for _ in range(conditionsSize): 365 | conditionType = decode(int, fields) 366 | condition = order_condition.Create(conditionType) 367 | condition.decode(fields) 368 | self.order.conditions.append(condition) 369 | 370 | self.order.conditionsIgnoreRth = decode(bool, fields) 371 | self.order.conditionsCancelOrder = decode(bool, fields) 372 | 373 | 374 | def decodeAdjustedOrderParams(self, fields): 375 | if self.serverVersion >= MIN_SERVER_VER_PEGGED_TO_BENCHMARK: 376 | self.order.adjustedOrderType = decode(str, fields) 377 | self.order.triggerPrice = decode(float, fields) 378 | OrderDecoder.decodeStopPriceAndLmtPriceOffset(self, fields) 379 | self.order.adjustedStopPrice = decode(float, fields) 380 | self.order.adjustedStopLimitPrice = decode(float, fields) 381 | self.order.adjustedTrailingAmount = decode(float, fields) 382 | self.order.adjustableTrailingUnit = decode(int, fields) 383 | 384 | def decodeStopPriceAndLmtPriceOffset(self, fields): 385 | self.order.trailStopPrice = decode(float, fields) 386 | self.order.lmtPriceOffset = decode(float, fields) 387 | 388 | def decodeSoftDollarTier(self, fields): 389 | if self.serverVersion >= MIN_SERVER_VER_SOFT_DOLLAR_TIER: 390 | name = decode(str, fields) 391 | value = decode(str, fields) 392 | displayName = decode(str, fields) 393 | self.order.softDollarTier = SoftDollarTier(name, value, displayName) 394 | 395 | def decodeCashQty(self, fields): 396 | if self.serverVersion >= MIN_SERVER_VER_CASH_QTY: 397 | self.order.cashQty = decode(float,fields) 398 | 399 | def decodeDontUseAutoPriceForHedge(self, fields): 400 | if self.serverVersion >= MIN_SERVER_VER_AUTO_PRICE_FOR_HEDGE: 401 | self.order.dontUseAutoPriceForHedge = decode(bool,fields) 402 | 403 | def decodeIsOmsContainers(self, fields): 404 | if self.serverVersion >= MIN_SERVER_VER_ORDER_CONTAINER: 405 | self.order.isOmsContainer = decode(bool, fields) 406 | 407 | def decodeDiscretionaryUpToLimitPrice(self, fields): 408 | if self.serverVersion >= MIN_SERVER_VER_D_PEG_ORDERS: 409 | self.order.discretionaryUpToLimitPrice = decode(bool, fields) 410 | 411 | def decodeAutoCancelDate(self, fields): 412 | self.order.autoCancelDate = decode(str, fields) 413 | 414 | def decodeFilledQuantity(self, fields): 415 | self.order.filledQuantity = decode(float, fields) 416 | 417 | def decodeRefFuturesConId(self, fields): 418 | self.order.refFuturesConId = decode(int, fields) 419 | 420 | def decodeAutoCancelParent(self, fields): 421 | self.order.autoCancelParent = decode(bool, fields) 422 | 423 | def decodeShareholder(self, fields): 424 | self.order.shareholder = decode(str, fields) 425 | 426 | def decodeImbalanceOnly(self, fields): 427 | self.order.imbalanceOnly = decode(bool, fields) 428 | 429 | def decodeRouteMarketableToBbo(self, fields): 430 | self.order.routeMarketableToBbo = decode(bool, fields) 431 | 432 | def decodeParentPermId(self, fields): 433 | self.order.parentPermId = decode(int, fields) 434 | 435 | def decodeCompletedTime(self, fields): 436 | self.orderState.completedTime = decode(str, fields) 437 | 438 | def decodeCompletedStatus(self, fields): 439 | self.orderState.completedStatus = decode(str, fields) 440 | 441 | def decodeUsePriceMgmtAlgo(self, fields): 442 | if self.serverVersion >= MIN_SERVER_VER_PRICE_MGMT_ALGO: 443 | self.order.usePriceMgmtAlgo = decode(bool, fields) 444 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/ibapi/reader.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | 7 | """ 8 | The EReader runs in a separate threads and is responsible for receiving the 9 | incoming messages. 10 | It will read the packets from the wire, use the low level IB messaging to 11 | remove the size prefix and put the rest in a Queue. 12 | """ 13 | 14 | import logging 15 | from threading import Thread 16 | 17 | from ibapi import comm 18 | 19 | 20 | logger = logging.getLogger(__name__) 21 | 22 | 23 | class EReader(Thread): 24 | def __init__(self, conn, msg_queue): 25 | super().__init__() 26 | self.conn = conn 27 | self.msg_queue = msg_queue 28 | 29 | def run(self): 30 | try: 31 | buf = b"" 32 | while self.conn.isConnected(): 33 | 34 | data = self.conn.recvMsg() 35 | logger.debug("reader loop, recvd size %d", len(data)) 36 | buf += data 37 | 38 | while len(buf) > 0: 39 | (size, msg, buf) = comm.read_msg(buf) 40 | #logger.debug("resp %s", buf.decode('ascii')) 41 | logger.debug("size:%d msg.size:%d msg:|%s| buf:%s|", size, 42 | len(msg), buf, "|") 43 | 44 | if msg: 45 | self.msg_queue.put(msg) 46 | else: 47 | logger.debug("more incoming packet(s) are needed ") 48 | break 49 | 50 | logger.debug("EReader thread finished") 51 | except: 52 | logger.exception('unhandled exception in EReader thread') 53 | 54 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/ibapi/scanner.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | 7 | from ibapi.object_implem import Object 8 | from ibapi.common import UNSET_INTEGER, UNSET_DOUBLE 9 | 10 | 11 | class ScanData(Object): 12 | def __init__(self, contract = None, rank = 0, distance = "", benchmark = "", projection = "", legsStr = ""): 13 | self.contract = contract 14 | self.rank = rank 15 | self.distance = distance 16 | self.benchmark = benchmark 17 | self.projection = projection 18 | self.legsStr = legsStr 19 | 20 | def __str__(self): 21 | return "Rank: %d, Symbol: %s, SecType: %s, Currency: %s, Distance: %s, Benchmark: %s, Projection: %s, Legs String: %s" % (self.rank, 22 | self.contract.symbol, self.contract.secType, self.contract.currency, self.distance, 23 | self.benchmark, self.projection, self.legsStr) 24 | 25 | NO_ROW_NUMBER_SPECIFIED = -1 26 | 27 | class ScannerSubscription(Object): 28 | 29 | def __init__(self): 30 | self.numberOfRows = NO_ROW_NUMBER_SPECIFIED 31 | self.instrument = "" 32 | self.locationCode = "" 33 | self.scanCode = "" 34 | self.abovePrice = UNSET_DOUBLE 35 | self.belowPrice = UNSET_DOUBLE 36 | self.aboveVolume = UNSET_INTEGER 37 | self.marketCapAbove = UNSET_DOUBLE 38 | self.marketCapBelow = UNSET_DOUBLE 39 | self.moodyRatingAbove = "" 40 | self.moodyRatingBelow = "" 41 | self.spRatingAbove = "" 42 | self.spRatingBelow = "" 43 | self.maturityDateAbove = "" 44 | self.maturityDateBelow = "" 45 | self.couponRateAbove = UNSET_DOUBLE 46 | self.couponRateBelow = UNSET_DOUBLE 47 | self.excludeConvertible = False 48 | self.averageOptionVolumeAbove = UNSET_INTEGER 49 | self.scannerSettingPairs = "" 50 | self.stockTypeFilter = "" 51 | 52 | 53 | def __str__(self): 54 | s = "Instrument: %s, LocationCode: %s, ScanCode: %s" % (self.instrument, self.locationCode, self.scanCode) 55 | 56 | return s 57 | 58 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/ibapi/server_versions.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | """ 7 | The known server versions. 8 | """ 9 | 10 | #MIN_SERVER_VER_REAL_TIME_BARS = 34 11 | #MIN_SERVER_VER_SCALE_ORDERS = 35 12 | #MIN_SERVER_VER_SNAPSHOT_MKT_DATA = 35 13 | #MIN_SERVER_VER_SSHORT_COMBO_LEGS = 35 14 | #MIN_SERVER_VER_WHAT_IF_ORDERS = 36 15 | #MIN_SERVER_VER_CONTRACT_CONID = 37 16 | MIN_SERVER_VER_PTA_ORDERS = 39 17 | MIN_SERVER_VER_FUNDAMENTAL_DATA = 40 18 | MIN_SERVER_VER_DELTA_NEUTRAL = 40 19 | MIN_SERVER_VER_CONTRACT_DATA_CHAIN = 40 20 | MIN_SERVER_VER_SCALE_ORDERS2 = 40 21 | MIN_SERVER_VER_ALGO_ORDERS = 41 22 | MIN_SERVER_VER_EXECUTION_DATA_CHAIN = 42 23 | MIN_SERVER_VER_NOT_HELD = 44 24 | MIN_SERVER_VER_SEC_ID_TYPE = 45 25 | MIN_SERVER_VER_PLACE_ORDER_CONID = 46 26 | MIN_SERVER_VER_REQ_MKT_DATA_CONID = 47 27 | MIN_SERVER_VER_REQ_CALC_IMPLIED_VOLAT = 49 28 | MIN_SERVER_VER_REQ_CALC_OPTION_PRICE = 50 29 | MIN_SERVER_VER_SSHORTX_OLD = 51 30 | MIN_SERVER_VER_SSHORTX = 52 31 | MIN_SERVER_VER_REQ_GLOBAL_CANCEL = 53 32 | MIN_SERVER_VER_HEDGE_ORDERS = 54 33 | MIN_SERVER_VER_REQ_MARKET_DATA_TYPE = 55 34 | MIN_SERVER_VER_OPT_OUT_SMART_ROUTING = 56 35 | MIN_SERVER_VER_SMART_COMBO_ROUTING_PARAMS = 57 36 | MIN_SERVER_VER_DELTA_NEUTRAL_CONID = 58 37 | MIN_SERVER_VER_SCALE_ORDERS3 = 60 38 | MIN_SERVER_VER_ORDER_COMBO_LEGS_PRICE = 61 39 | MIN_SERVER_VER_TRAILING_PERCENT = 62 40 | MIN_SERVER_VER_DELTA_NEUTRAL_OPEN_CLOSE = 66 41 | MIN_SERVER_VER_POSITIONS = 67 42 | MIN_SERVER_VER_ACCOUNT_SUMMARY = 67 43 | MIN_SERVER_VER_TRADING_CLASS = 68 44 | MIN_SERVER_VER_SCALE_TABLE = 69 45 | MIN_SERVER_VER_LINKING = 70 46 | MIN_SERVER_VER_ALGO_ID = 71 47 | MIN_SERVER_VER_OPTIONAL_CAPABILITIES = 72 48 | MIN_SERVER_VER_ORDER_SOLICITED = 73 49 | MIN_SERVER_VER_LINKING_AUTH = 74 50 | MIN_SERVER_VER_PRIMARYEXCH = 75 51 | MIN_SERVER_VER_RANDOMIZE_SIZE_AND_PRICE = 76 52 | MIN_SERVER_VER_FRACTIONAL_POSITIONS = 101 53 | MIN_SERVER_VER_PEGGED_TO_BENCHMARK = 102 54 | MIN_SERVER_VER_MODELS_SUPPORT = 103 55 | MIN_SERVER_VER_SEC_DEF_OPT_PARAMS_REQ = 104 56 | MIN_SERVER_VER_EXT_OPERATOR = 105 57 | MIN_SERVER_VER_SOFT_DOLLAR_TIER = 106 58 | MIN_SERVER_VER_REQ_FAMILY_CODES = 107 59 | MIN_SERVER_VER_REQ_MATCHING_SYMBOLS = 108 60 | MIN_SERVER_VER_PAST_LIMIT = 109 61 | MIN_SERVER_VER_MD_SIZE_MULTIPLIER = 110 62 | MIN_SERVER_VER_CASH_QTY = 111 63 | MIN_SERVER_VER_REQ_MKT_DEPTH_EXCHANGES = 112 64 | MIN_SERVER_VER_TICK_NEWS = 113 65 | MIN_SERVER_VER_REQ_SMART_COMPONENTS = 114 66 | MIN_SERVER_VER_REQ_NEWS_PROVIDERS = 115 67 | MIN_SERVER_VER_REQ_NEWS_ARTICLE = 116 68 | MIN_SERVER_VER_REQ_HISTORICAL_NEWS = 117 69 | MIN_SERVER_VER_REQ_HEAD_TIMESTAMP = 118 70 | MIN_SERVER_VER_REQ_HISTOGRAM = 119 71 | MIN_SERVER_VER_SERVICE_DATA_TYPE = 120 72 | MIN_SERVER_VER_AGG_GROUP = 121 73 | MIN_SERVER_VER_UNDERLYING_INFO = 122 74 | MIN_SERVER_VER_CANCEL_HEADTIMESTAMP = 123 75 | MIN_SERVER_VER_SYNT_REALTIME_BARS = 124 76 | MIN_SERVER_VER_CFD_REROUTE = 125 77 | MIN_SERVER_VER_MARKET_RULES = 126 78 | MIN_SERVER_VER_PNL = 127 79 | MIN_SERVER_VER_NEWS_QUERY_ORIGINS = 128 80 | MIN_SERVER_VER_UNREALIZED_PNL = 129 81 | MIN_SERVER_VER_HISTORICAL_TICKS = 130 82 | MIN_SERVER_VER_MARKET_CAP_PRICE = 131 83 | MIN_SERVER_VER_PRE_OPEN_BID_ASK = 132 84 | MIN_SERVER_VER_REAL_EXPIRATION_DATE = 134 85 | MIN_SERVER_VER_REALIZED_PNL = 135 86 | MIN_SERVER_VER_LAST_LIQUIDITY = 136 87 | MIN_SERVER_VER_TICK_BY_TICK = 137 88 | MIN_SERVER_VER_DECISION_MAKER = 138 89 | MIN_SERVER_VER_MIFID_EXECUTION = 139 90 | MIN_SERVER_VER_TICK_BY_TICK_IGNORE_SIZE = 140 91 | MIN_SERVER_VER_AUTO_PRICE_FOR_HEDGE = 141 92 | MIN_SERVER_VER_WHAT_IF_EXT_FIELDS = 142 93 | MIN_SERVER_VER_SCANNER_GENERIC_OPTS = 143 94 | MIN_SERVER_VER_API_BIND_ORDER = 144 95 | MIN_SERVER_VER_ORDER_CONTAINER = 145 96 | MIN_SERVER_VER_SMART_DEPTH = 146 97 | MIN_SERVER_VER_REMOVE_NULL_ALL_CASTING = 147 98 | MIN_SERVER_VER_D_PEG_ORDERS = 148 99 | MIN_SERVER_VER_MKT_DEPTH_PRIM_EXCHANGE = 149 100 | MIN_SERVER_VER_COMPLETED_ORDERS = 150 101 | MIN_SERVER_VER_PRICE_MGMT_ALGO = 151 102 | 103 | # 100+ messaging */ 104 | # 100 = enhanced handshake, msg length prefixes 105 | 106 | MIN_CLIENT_VER = 100 107 | MAX_CLIENT_VER = MIN_SERVER_VER_PRICE_MGMT_ALGO 108 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/ibapi/softdollartier.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | 7 | from ibapi.object_implem import Object 8 | 9 | 10 | class SoftDollarTier(Object): 11 | def __init__(self, name = "", val = "", displayName = ""): 12 | self.name = name 13 | self.val = val 14 | self.displayName = displayName 15 | 16 | def __str__(self): 17 | return "Name: %s, Value: %s, DisplayName: %s" % (self.name, self.val, self.displayName) 18 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/ibapi/tag_value.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | """ 7 | Simple class mapping a tag to a value. Both of them are strings. 8 | They are used in a list to convey extra info with the requests. 9 | """ 10 | 11 | from ibapi.object_implem import Object 12 | 13 | 14 | class TagValue(Object): 15 | def __init__(self, tag:str=None, value:str=None): 16 | self.tag = str(tag) 17 | self.value = str(value) 18 | 19 | def __str__(self): 20 | # this is not only used for Python dump but when encoding to send 21 | # so don't change it lightly ! 22 | return "%s=%s;" % (self.tag, self.value) 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/ibapi/ticktype.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | 7 | """ 8 | TickType type 9 | """ 10 | 11 | from ibapi.enum_implem import Enum 12 | 13 | 14 | # TickType 15 | TickType = int 16 | TickTypeEnum = Enum("BID_SIZE", 17 | "BID", 18 | "ASK", 19 | "ASK_SIZE", 20 | "LAST", 21 | "LAST_SIZE", 22 | "HIGH", 23 | "LOW", 24 | "VOLUME", 25 | "CLOSE", 26 | "BID_OPTION_COMPUTATION", 27 | "ASK_OPTION_COMPUTATION", 28 | "LAST_OPTION_COMPUTATION", 29 | "MODEL_OPTION", 30 | "OPEN", 31 | "LOW_13_WEEK", 32 | "HIGH_13_WEEK", 33 | "LOW_26_WEEK", 34 | "HIGH_26_WEEK", 35 | "LOW_52_WEEK", 36 | "HIGH_52_WEEK", 37 | "AVG_VOLUME", 38 | "OPEN_INTEREST", 39 | "OPTION_HISTORICAL_VOL", 40 | "OPTION_IMPLIED_VOL", 41 | "OPTION_BID_EXCH", 42 | "OPTION_ASK_EXCH", 43 | "OPTION_CALL_OPEN_INTEREST", 44 | "OPTION_PUT_OPEN_INTEREST", 45 | "OPTION_CALL_VOLUME", 46 | "OPTION_PUT_VOLUME", 47 | "INDEX_FUTURE_PREMIUM", 48 | "BID_EXCH", 49 | "ASK_EXCH", 50 | "AUCTION_VOLUME", 51 | "AUCTION_PRICE", 52 | "AUCTION_IMBALANCE", 53 | "MARK_PRICE", 54 | "BID_EFP_COMPUTATION", 55 | "ASK_EFP_COMPUTATION", 56 | "LAST_EFP_COMPUTATION", 57 | "OPEN_EFP_COMPUTATION", 58 | "HIGH_EFP_COMPUTATION", 59 | "LOW_EFP_COMPUTATION", 60 | "CLOSE_EFP_COMPUTATION", 61 | "LAST_TIMESTAMP", 62 | "SHORTABLE", 63 | "FUNDAMENTAL_RATIOS", 64 | "RT_VOLUME", 65 | "HALTED", 66 | "BID_YIELD", 67 | "ASK_YIELD", 68 | "LAST_YIELD", 69 | "CUST_OPTION_COMPUTATION", 70 | "TRADE_COUNT", 71 | "TRADE_RATE", 72 | "VOLUME_RATE", 73 | "LAST_RTH_TRADE", 74 | "RT_HISTORICAL_VOL", 75 | "IB_DIVIDENDS", 76 | "BOND_FACTOR_MULTIPLIER", 77 | "REGULATORY_IMBALANCE", 78 | "NEWS_TICK", 79 | "SHORT_TERM_VOLUME_3_MIN", 80 | "SHORT_TERM_VOLUME_5_MIN", 81 | "SHORT_TERM_VOLUME_10_MIN", 82 | "DELAYED_BID", 83 | "DELAYED_ASK", 84 | "DELAYED_LAST", 85 | "DELAYED_BID_SIZE", 86 | "DELAYED_ASK_SIZE", 87 | "DELAYED_LAST_SIZE", 88 | "DELAYED_HIGH", 89 | "DELAYED_LOW", 90 | "DELAYED_VOLUME", 91 | "DELAYED_CLOSE", 92 | "DELAYED_OPEN", 93 | "RT_TRD_VOLUME", 94 | "CREDITMAN_MARK_PRICE", 95 | "CREDITMAN_SLOW_MARK_PRICE", 96 | "DELAYED_BID_OPTION", 97 | "DELAYED_ASK_OPTION", 98 | "DELAYED_LAST_OPTION", 99 | "DELAYED_MODEL_OPTION", 100 | "LAST_EXCH", 101 | "LAST_REG_TIME", 102 | "FUTURES_OPEN_INTEREST", 103 | "AVG_OPT_VOLUME", 104 | "DELAYED_LAST_TIMESTAMP", 105 | "SHORTABLE_SHARES", 106 | "NOT_SET") 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/ibapi/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | 7 | """ 8 | Collection of misc tools 9 | """ 10 | 11 | 12 | import sys 13 | import logging 14 | import inspect 15 | 16 | from ibapi.common import UNSET_INTEGER, UNSET_DOUBLE, UNSET_LONG 17 | 18 | 19 | logger = logging.getLogger(__name__) 20 | 21 | 22 | # I use this just to visually emphasize it's a wrapper overriden method 23 | def iswrapper(fn): 24 | return fn 25 | 26 | 27 | class BadMessage(Exception): 28 | def __init__(self, text): 29 | self.text = text 30 | 31 | 32 | class LogFunction(object): 33 | def __init__(self, text, logLevel): 34 | self.text = text 35 | self.logLevel = logLevel 36 | 37 | def __call__(self, fn): 38 | def newFn(origSelf, *args, **kwargs): 39 | if logger.getLogger().isEnabledFor(self.logLevel): 40 | argNames = [argName for argName in inspect.getfullargspec(fn)[0] if argName != 'self'] 41 | logger.log(self.logLevel, 42 | "{} {} {} kw:{}".format(self.text, fn.__name__, 43 | [nameNarg for nameNarg in zip(argNames, args) if nameNarg[1] is not origSelf], kwargs)) 44 | fn(origSelf, *args) 45 | return newFn 46 | 47 | 48 | def current_fn_name(parent_idx = 0): 49 | #depth is 1 bc this is already a fn, so we need the caller 50 | return sys._getframe(1 + parent_idx).f_code.co_name 51 | 52 | 53 | def setattr_log(self, var_name, var_value): 54 | #import code; code.interact(local=locals()) 55 | logger.debug("%s %s %s=|%s|", self.__class__, id(self), var_name, var_value) 56 | super(self.__class__, self).__setattr__(var_name, var_value) 57 | 58 | 59 | SHOW_UNSET = True 60 | def decode(the_type, fields, show_unset = False): 61 | try: 62 | s = next(fields) 63 | except StopIteration: 64 | raise BadMessage("no more fields") 65 | 66 | logger.debug("decode %s %s", the_type, s) 67 | 68 | if the_type is str: 69 | if type(s) is str: 70 | return s 71 | elif type(s) is bytes: 72 | return s.decode(errors='backslashreplace') 73 | else: 74 | raise TypeError("unsupported incoming type " + type(s) + " for desired type 'str") 75 | 76 | orig_type = the_type 77 | if the_type is bool: 78 | the_type = int 79 | 80 | if show_unset: 81 | if s is None or len(s) == 0: 82 | if the_type is float: 83 | n = UNSET_DOUBLE 84 | elif the_type is int: 85 | n = UNSET_INTEGER 86 | else: 87 | raise TypeError("unsupported desired type for empty value" + the_type) 88 | else: 89 | n = the_type(s) 90 | else: 91 | n = the_type(s or 0) 92 | 93 | if orig_type is bool: 94 | n = False if n == 0 else True 95 | 96 | return n 97 | 98 | 99 | 100 | def ExerciseStaticMethods(klass): 101 | 102 | import types 103 | #import code; code.interact(local=dict(globals(), **locals())) 104 | for (_, var) in inspect.getmembers(klass): 105 | #print(name, var, type(var)) 106 | if type(var) == types.FunctionType: 107 | print("Exercising: %s:" % var) 108 | print(var()) 109 | print() 110 | 111 | def floatToStr(val): 112 | return str(val) if val != UNSET_DOUBLE else ""; 113 | 114 | def longToStr(val): 115 | return str(val) if val != UNSET_LONG else ""; 116 | 117 | 118 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | # from distutils.core import setup 6 | from setuptools import setup 7 | from ibapi import get_version_string 8 | 9 | import sys 10 | 11 | 12 | if sys.version_info < (3, 1): 13 | sys.exit("Only Python 3.1 and greater is supported") 14 | 15 | setup( 16 | name="ibapi", 17 | version=get_version_string(), 18 | packages=["ibapi"], 19 | url="https://interactivebrokers.github.io/tws-api", 20 | license="IB API Non-Commercial License or the IB API Commercial License", 21 | author="IBG LLC", 22 | author_email="dnastase@interactivebrokers.com", 23 | description="Python IB API", 24 | ) 25 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from ibapi.order_condition import * 4 | 5 | 6 | class ConditionOrderTestCase(unittest.TestCase): 7 | conds = [ 8 | VolumeCondition(8314, "SMART", True, 1000000).And(), 9 | PercentChangeCondition(1111, "AMEX", True, 0.25).Or(), 10 | PriceCondition( 11 | PriceCondition.TriggerMethodEnum.DoubleLast, 2222, "NASDAQ", False, 4.75 12 | ).And(), 13 | TimeCondition(True, "20170101 09:30:00").And(), 14 | MarginCondition(False, 200000).Or(), 15 | ExecutionCondition("STK", "SMART", "AMD"), 16 | ] 17 | 18 | for cond in conds: 19 | print(cond, OrderCondition.__str__(cond)) 20 | 21 | 22 | if "__main__" == __name__: 23 | unittest.main() 24 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/tests/manual.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | import sys 7 | import socket 8 | import struct 9 | import array 10 | import datetime 11 | import inspect 12 | import time 13 | import argparse 14 | 15 | import os.path 16 | 17 | from ibapi.wrapper import EWrapper 18 | import ibapi.decoder 19 | import ibapi.wrapper 20 | from ibapi.common import * 21 | from ibapi.ticktype import TickType, TickTypeEnum 22 | from ibapi.comm import * 23 | from ibapi.message import IN, OUT 24 | from ibapi.client import EClient 25 | from ibapi.connection import Connection 26 | from ibapi.reader import EReader 27 | from ibapi.utils import * 28 | from ibapi.execution import ExecutionFilter 29 | from ibapi.scanner import ScannerSubscription 30 | from ibapi.order_condition import * 31 | from ibapi.contract import * 32 | from ibapi.order import * 33 | from ibapi.order_state import * 34 | 35 | #import pdb; pdb.set_trace() 36 | #import code; code.interact(local=locals()) 37 | #import code; code.interact(local=dict(globals(), **locals())) 38 | 39 | class TestApp(EClient, EWrapper): 40 | def __init__(self): 41 | EClient.__init__(self, self) 42 | self.nextValidOrderId = None 43 | self.permId2ord = {} 44 | 45 | @iswrapper 46 | def nextValidId(self, orderId:int): 47 | super().nextValidId(orderId) 48 | logging.debug("setting nextValidOrderId: %d", orderId) 49 | self.nextValidOrderId = orderId 50 | 51 | def placeOneOrder(self): 52 | con = Contract() 53 | con.symbol = "AMD" 54 | con.secType = "STK" 55 | con.currency = "USD" 56 | con.exchange = "SMART" 57 | order = Order() 58 | order.action = "BUY" 59 | order.orderType = "LMT" 60 | order.tif = "GTC" 61 | order.totalQuantity = 3 62 | order.lmtPrice = 1.23 63 | self.placeOrder(self.nextOrderId(), con, order) 64 | 65 | def cancelOneOrder(self): 66 | pass 67 | 68 | def nextOrderId(self): 69 | id = self.nextValidOrderId 70 | self.nextValidOrderId += 1 71 | return id 72 | 73 | @iswrapper 74 | def error(self, *args): 75 | super().error(*args) 76 | print(current_fn_name(), vars()) 77 | 78 | @iswrapper 79 | def winError(self, *args): 80 | super().error(*args) 81 | print(current_fn_name(), vars()) 82 | 83 | @iswrapper 84 | def openOrder(self, orderId:OrderId, contract:Contract, order:Order, 85 | orderState:OrderState): 86 | super().openOrder(orderId, contract, order, orderState) 87 | print(current_fn_name(), vars()) 88 | 89 | order.contract = contract 90 | self.permId2ord[order.permId] = order 91 | 92 | @iswrapper 93 | def openOrderEnd(self, *args): 94 | super().openOrderEnd() 95 | logging.debug("Received %d openOrders", len(self.permId2ord)) 96 | 97 | @iswrapper 98 | def orderStatus(self, orderId:OrderId , status:str, filled:float, 99 | remaining:float, avgFillPrice:float, permId:int, 100 | parentId:int, lastFillPrice:float, clientId:int, 101 | whyHeld:str): 102 | super().orderStatus(orderId, status, filled, remaining, 103 | avgFillPrice, permId, parentId, lastFillPrice, clientId, whyHeld) 104 | 105 | @iswrapper 106 | def tickPrice(self, tickerId: TickerId , tickType: TickType, price: float, attrib): 107 | super().tickPrice(tickerId, tickType, price, attrib) 108 | print(current_fn_name(), tickerId, TickTypeEnum.to_str(tickType), price, attrib, file=sys.stderr) 109 | 110 | 111 | @iswrapper 112 | def tickSize(self, tickerId: TickerId, tickType: TickType, size: int): 113 | super().tickSize(tickerId, tickType, size) 114 | print(current_fn_name(), tickerId, TickTypeEnum.to_str(tickType), size, file=sys.stderr) 115 | 116 | @iswrapper 117 | def scannerParameters(self, xml:str): 118 | open('scanner.xml', 'w').write(xml) 119 | 120 | def main(): 121 | 122 | cmdLineParser = argparse.ArgumentParser("api tests") 123 | #cmdLineParser.add_option("-c", action="store_true", dest="use_cache", default = False, help = "use the cache") 124 | #cmdLineParser.add_option("-f", action="store", type="string", dest="file", default="", help="the input file") 125 | cmdLineParser.add_argument("-p", "--port", action="store", type=int, 126 | dest="port", default = 4005, help="The TCP port to use") 127 | args = cmdLineParser.parse_args() 128 | print("Using args", args) 129 | 130 | import logging 131 | logging.debug("Using args %s", args) 132 | #print(args) 133 | 134 | logging.debug("now is %s", datetime.datetime.now()) 135 | logging.getLogger().setLevel(logging.ERROR) 136 | 137 | #enable logging when member vars are assigned 138 | import utils 139 | from order import Order 140 | Order.__setattr__ = utils.setattr_log 141 | from contract import Contract,DeltaNeutralContract 142 | Contract.__setattr__ = utils.setattr_log 143 | DeltaNeutralContract.__setattr__ = utils.setattr_log 144 | from tag_value import TagValue 145 | TagValue.__setattr__ = utils.setattr_log 146 | TimeCondition.__setattr__ = utils.setattr_log 147 | ExecutionCondition.__setattr__ = utils.setattr_log 148 | MarginCondition.__setattr__ = utils.setattr_log 149 | PriceCondition.__setattr__ = utils.setattr_log 150 | PercentChangeCondition.__setattr__ = utils.setattr_log 151 | VolumeCondition.__setattr__ = utils.setattr_log 152 | 153 | #from inspect import signature as sig 154 | #import code; code.interact(local=dict(globals(), **locals())) 155 | #sys.exit(1) 156 | 157 | app = TestApp() 158 | app.connect("127.0.0.1", args.port, 0) 159 | 160 | app.reqCurrentTime() 161 | app.reqManagedAccts() 162 | app.reqAccountSummary(reqId = 2, groupName = "All", 163 | tags = "NetLiquidation") 164 | 165 | app.reqAllOpenOrders() 166 | 167 | contract = Contract() 168 | contract.symbol = "AMD" 169 | contract.secType = "STK" 170 | contract.currency = "USD" 171 | contract.exchange = "SMART" 172 | #app.reqMarketDataType(1) 173 | #app.reqMktData(1001, contract, "", snapshot=True) 174 | #app.cancelMktData(1001) 175 | #app.reqExecutions(2001, ExecutionFilter()) 176 | #app.reqContractDetails(3001, contract) 177 | #app.reqPositions() 178 | #app.reqIds(2) 179 | 180 | #app.reqMktDepth(4001, contract, 5, "") 181 | #app.cancelMktDepth(4001) 182 | 183 | #app.reqNewsBulletins(allMsgs=True) 184 | #app.cancelNewsBulletins() 185 | #app.requestFA(FaDataTypeEnum.GROUPS) 186 | 187 | #app.reqHistoricalData(5001, contract, "20161215 16:00:00", "2 D", 188 | # "1 hour", "TRADES", 0, 1, []) 189 | #app.cancelHistoricalData(5001) 190 | 191 | #app.reqFundamentalData(6001, contract, "ReportSnapshot") 192 | #app.cancelFundamentalData(6001) 193 | #app.queryDisplayGroups(7001) 194 | #app.subscribeToGroupEvents(7002, 1) 195 | #app.unsubscribeFromGroupEvents(7002) 196 | 197 | #app.reqScannerParameters() 198 | ss = ScannerSubscription() 199 | ss.instrument = "STK" 200 | ss.locationCode = "STK.US" 201 | ss.scanCode = "TOP_PERC_LOSE" 202 | #app.reqScannerSubscription(8001, ss, []) 203 | #app.cancelScannerSubscription(8001) 204 | #app.reqRealTimeBars(9001, contract, 5, "TRADES", 0, []) 205 | #app.cancelRealTimeBars(9001) 206 | #app.reqSecDefOptParams(10001, "AMD", "", "STK", 4391) 207 | #app.reqSoftDollarTiers(11001) 208 | #app.reqFamilyCodes() 209 | #app.reqMatchingSymbols(12001, "AMD") 210 | 211 | contract = Contract() 212 | contract.symbol = "AMD" 213 | contract.secType = "OPT" 214 | contract.exchange = "SMART" 215 | contract.currency = "USD" 216 | contract.lastTradeDateOrContractMonth = "20170120" 217 | contract.strike = 10 218 | contract.right = "C" 219 | contract.multiplier = "100" 220 | #Often, contracts will also require a trading class to rule out ambiguities 221 | contract.tradingClass = "AMD" 222 | #app.calculateImpliedVolatility(13001, contract, 1.3, 10.85) 223 | #app.calculateOptionPrice(13002, contract, 0.65, 10.85) 224 | 225 | app.run() 226 | 227 | 228 | if __name__ == "__main__": 229 | main() 230 | 231 | 232 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/tests/test_account_summary_tags.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | import unittest 7 | from ibapi.account_summary_tags import AccountSummaryTags 8 | 9 | 10 | class AccountSummaryTagsTestCase(unittest.TestCase): 11 | def setUp(self): 12 | pass 13 | 14 | 15 | def tearDown(self): 16 | pass 17 | 18 | 19 | def test_all_tags(self): 20 | print(AccountSummaryTags.AllTags) 21 | 22 | 23 | if "__main__" == __name__: 24 | unittest.main() 25 | 26 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/tests/test_comm.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | import unittest 7 | import struct 8 | from ibapi import comm 9 | 10 | 11 | class CommTestCase(unittest.TestCase): 12 | def setUp(self): 13 | pass 14 | 15 | 16 | def tearDown(self): 17 | pass 18 | 19 | 20 | def test_make_msg(self): 21 | text = "ABCD" 22 | msg = comm.make_msg(text) 23 | 24 | size = struct.unpack("!I", msg[0:4])[0] 25 | 26 | self.assertEqual(size, len(text), "msg size not good") 27 | self.assertEqual(msg[4:].decode(), text, "msg payload not good") 28 | 29 | 30 | def test_make_field(self): 31 | text = "ABCD" 32 | field = comm.make_field(text) 33 | 34 | self.assertEqual(field[-1], "\0", "terminator not good") 35 | self.assertEqual(len(field[0:-1]), len(text), "payload size not good") 36 | self.assertEqual(field[0:-1], text, "payload not good") 37 | 38 | 39 | def test_read_msg(self): 40 | text = "ABCD" 41 | msg = comm.make_msg(text) 42 | 43 | (size, text2, rest) = comm.read_msg(msg) 44 | 45 | self.assertEqual(size, len(text), "msg size not good") 46 | self.assertEqual(text2.decode(), text, "msg payload not good") 47 | self.assertEqual(len(rest), 0, "there should be no remainder msg") 48 | 49 | 50 | def test_readFields(self): 51 | text1 = "ABCD" 52 | text2 = "123" 53 | 54 | msg = comm.make_msg(comm.make_field(text1) + comm.make_field(text2)) 55 | 56 | (size, text, rest) = comm.read_msg(msg) 57 | fields = comm.read_fields(text) 58 | 59 | self.assertEqual(len(fields), 2, "incorrect number of fields") 60 | self.assertEqual(fields[0].decode(), text1) 61 | self.assertEqual(fields[1].decode(), text2) 62 | 63 | 64 | if "__main__" == __name__: 65 | unittest.main() 66 | 67 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/tests/test_enum_implem.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | import unittest 7 | 8 | from ibapi.enum_implem import Enum 9 | 10 | 11 | class EnumTestCase(unittest.TestCase): 12 | def setUp(self): 13 | pass 14 | 15 | 16 | def tearDown(self): 17 | pass 18 | 19 | 20 | def test_enum(self): 21 | e = Enum("ZERO", "ONE", "TWO") 22 | print(e.ZERO) 23 | print(e.to_str(e.ZERO)) 24 | 25 | 26 | if "__main__" == __name__: 27 | unittest.main() 28 | 29 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/tests/test_order_conditions.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | import unittest 7 | 8 | from ibapi.order_condition import * 9 | 10 | 11 | 12 | class ConditionOrderTestCase(unittest.TestCase): 13 | conds = [ 14 | VolumeCondition(8314, "SMART", True, 1000000).And(), 15 | PercentChangeCondition(1111, "AMEX", True, 0.25).Or(), 16 | PriceCondition( 17 | PriceCondition.TriggerMethodEnum.DoubleLast, 18 | 2222, "NASDAQ", False, 4.75).And(), 19 | TimeCondition(True, "20170101 09:30:00").And(), 20 | MarginCondition(False, 200000).Or(), 21 | ExecutionCondition("STK", "SMART", "AMD") 22 | ] 23 | 24 | for cond in conds: 25 | print(cond, OrderCondition.__str__(cond)) 26 | 27 | 28 | if "__main__" == __name__: 29 | unittest.main() 30 | 31 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/tests/test_utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2019 Interactive Brokers LLC. All rights reserved. This code is subject to the terms 3 | and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable. 4 | """ 5 | 6 | import unittest 7 | 8 | from ibapi.enum_implem import Enum 9 | from ibapi.utils import setattr_log 10 | 11 | 12 | class UtilsTestCase(unittest.TestCase): 13 | def setUp(self): 14 | pass 15 | 16 | 17 | def tearDown(self): 18 | pass 19 | 20 | 21 | def test_enum(self): 22 | e = Enum("ZERO", "ONE", "TWO") 23 | print(e.ZERO) 24 | print(e.to_str(e.ZERO)) 25 | 26 | 27 | def test_setattr_log(self): 28 | class A: 29 | def __init__(self): 30 | self.n = 5 31 | 32 | A.__setattr__ = setattr_log 33 | a = A() 34 | print(a.n) 35 | a.n = 6 36 | print(a.n) 37 | 38 | 39 | def test_polymorphism(self): 40 | class A: 41 | def __init__(self): 42 | self.n = 5 43 | def m(self): 44 | self.n += 1 45 | class B(A): 46 | def m(self): 47 | self.n += 2 48 | 49 | o = B() 50 | #import code; code.interact(local=locals()) 51 | 52 | 53 | if "__main__" == __name__: 54 | unittest.main() 55 | 56 | -------------------------------------------------------------------------------- /src/market_watcher/ib_client/tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py33, py34, py35 3 | 4 | [testenv] 5 | deps = pytest 6 | commands = py.test {posargs} 7 | -------------------------------------------------------------------------------- /src/market_watcher/market_watcher_cli.py: -------------------------------------------------------------------------------- 1 | import click 2 | from click.utils import echo 3 | 4 | from market_watcher.version import VERSION 5 | from market_watcher.config import context 6 | from market_watcher.common import get_terget_stocks 7 | from market_watcher.common import get_email_config, get_slack_config 8 | from market_watcher.common import MarketWatcherEngine 9 | from market_watcher.notifier import EmailNotifier, SlackNotifier 10 | 11 | 12 | @click.group() 13 | def cli(): 14 | """MarketWatcher cli commands.""" 15 | echo( 16 | """ 17 | ______ _ _ _ _ _ 18 | | ___ \ | | _ | || || | _ | | 19 | | | _ | | ____ ____| | _ ____| |_| || || | ____| |_ ____| | _ ____ ____ 20 | | || || |/ _ |/ ___) | / ) _ ) _) ||_|| |/ _ | _)/ ___) || \ / _ )/ ___) 21 | | || || ( ( | | | | |< ( (/ /| |_| |___| ( ( | | |_( (___| | | ( (/ /| | 22 | |_||_||_|\_||_|_| |_| \_)____)\___)______|\_||_|\___)____)_| |_|\____)_| 23 | 24 | MarketWatcher tool for finding investiments opportunities on Interacive Brokers 25 | for volatility trading on equity market using long and short options strategy. 26 | """ # noqa 27 | ) 28 | echo(f"version: v{VERSION}") 29 | echo("\n\n\n") 30 | 31 | 32 | @cli.command() 33 | def test_slack(): 34 | """Sends dummy messages to test if Slack app has been configured properly.""" 35 | 36 | try: 37 | echo("Testing Slack options-trading bot...") 38 | 39 | config = get_slack_config() 40 | slack_notifier = SlackNotifier(config) 41 | 42 | echo("Sending message to #options-long-straddle channel...") 43 | slack_notifier.send_message(config["long url"], "MarketWatcher: Test long!") 44 | 45 | echo("Sending message to #options-short-straddle channel...") 46 | slack_notifier.send_message(config["short url"], "MarketWatcher: Test short!") 47 | except Exception as e: 48 | echo("Slack testing failed!") 49 | echo(e) 50 | 51 | 52 | @cli.command() 53 | @click.option( 54 | "--notifier", 55 | default="all", 56 | help="Available options: email, slack, all", 57 | ) 58 | def config(notifier): 59 | """Lists congiguration for slack and email notifiers.""" 60 | 61 | if notifier == "all" or notifier == "slack": 62 | config = get_slack_config() 63 | for env in config: 64 | echo(f"{env}: {config[env]}") 65 | 66 | if notifier == "all" or notifier == "email": 67 | config = get_email_config() 68 | for env in config: 69 | echo(f"{env}: {config[env]}") 70 | 71 | 72 | @cli.command() 73 | @click.option( 74 | "--stocks", 75 | default=r"src/market_watcher/research/target_stocks.yaml", 76 | help="Yaml file containing target stocks for long and short straddle option strategy..", 77 | ) 78 | def start(stocks): 79 | """Starts the MarketWatcher.""" 80 | echo("Starting MarketWatcher...") 81 | 82 | try: 83 | context.running = True 84 | echo("MarketWatcher started.") 85 | 86 | echo(f"Reading target stocks from file: {stocks}") 87 | target_stocks = get_terget_stocks(stocks) 88 | 89 | notifiers = [] 90 | 91 | if context.state["email"]: 92 | echo("Instantiating email notifier...") 93 | notifiers.append(EmailNotifier(get_email_config())) 94 | 95 | if context.state["slack"]: 96 | echo("Instantiating slack notifier...") 97 | notifiers.append(SlackNotifier(get_slack_config())) 98 | 99 | echo("Instantiating MarketWatcher and running the engine.") 100 | market_watcher_engine = MarketWatcherEngine( 101 | target_stocks=target_stocks, notifiers=notifiers 102 | ) 103 | market_watcher_engine.search_for_intestment_opportunities() 104 | except ValueError as e: 105 | echo(e) 106 | 107 | 108 | @cli.command() 109 | def stop(): 110 | """Stops the MarketWatcher.""" 111 | echo("Stopping MarketWatcher...") 112 | 113 | try: 114 | context.running = False 115 | echo("MarketWatcher stopped.") 116 | except ValueError as e: 117 | echo(e) 118 | -------------------------------------------------------------------------------- /src/market_watcher/notifier.py: -------------------------------------------------------------------------------- 1 | import smtplib 2 | import ssl 3 | from datetime import datetime 4 | 5 | from email.mime.multipart import MIMEMultipart 6 | from email.mime.text import MIMEText 7 | 8 | from slack_sdk.webhook import WebhookClient 9 | 10 | from market_watcher.common import STRATEGIES 11 | 12 | 13 | class Notifier(object): 14 | def __init__(self): 15 | pass 16 | 17 | def notify(self, investment_data): 18 | raise NotImplementedError 19 | 20 | def _long_straddle_message(self, data): 21 | sorted_data = { 22 | ticker: pnl 23 | for ticker, pnl in sorted( 24 | data.items(), key=lambda x: abs(x[1]), reverse=True 25 | ) 26 | } 27 | formatted_data = self._formatted_data(sorted_data) 28 | return "\n".join(["Long straddle opportunities:", formatted_data]) 29 | 30 | def _short_straddle_message(self, data): 31 | sorted_data = { 32 | ticker: pnl 33 | for ticker, pnl in sorted( 34 | data.items(), key=lambda x: abs(x[1]), reverse=False 35 | ) 36 | } 37 | formatted_data = self._formatted_data(sorted_data) 38 | return "\n".join(["Short straddle opportunities:", formatted_data]) 39 | 40 | def _formatted_data(self, data): 41 | formatted_data = [ 42 | f"{ticker}: {round(abs(pnl*100),3)}% {self._direction(pnl)}" 43 | for ticker, pnl in data.items() 44 | ] 45 | return "\n".join(formatted_data) 46 | 47 | def _direction(self, number): 48 | if number > 0: 49 | return "(UP)" 50 | elif number < 0: 51 | return "(DOWN)" 52 | else: 53 | return "" 54 | 55 | 56 | class EmailNotifier(Notifier): 57 | def __init__(self, config): 58 | super(EmailNotifier, self).__init__() 59 | self.__hostname = config["hostname"] 60 | self.__port = int(config["port"]) 61 | self.__username = config["username"] 62 | self.__password = config["password"] 63 | self.__sender = config["sender"] 64 | self.__recipients = config["recipients"] 65 | 66 | def send(self, message): 67 | context = ssl.create_default_context() 68 | with smtplib.SMTP_SSL(self.__hostname, self.__port, context=context) as server: 69 | if self.__username: 70 | server.login(self.__username, self.__password) 71 | server.sendmail(self.__sender, self.__recipients, message) 72 | print(f"Successfully sent email to {self.__recipients}.") 73 | 74 | def notify(self, investment_data): 75 | now = datetime.now() 76 | current_datetime = now.strftime("%Y-%m-%d %H:%M:%S") 77 | 78 | long_straddle_message = self._long_straddle_message( 79 | investment_data[STRATEGIES.LONG_STRADDLE.value] 80 | ) 81 | short_straddle_message = self._short_straddle_message( 82 | investment_data[STRATEGIES.SHORT_STRADDLE.value] 83 | ) 84 | 85 | message = MIMEMultipart() 86 | message["Sender"] = self.__sender 87 | message["To"] = self.__recipients 88 | message["Subject"] = f"Investment opportunities report - {current_datetime}" 89 | text = "\n\n".join([long_straddle_message, short_straddle_message]) 90 | 91 | message.attach(MIMEText(text, "plain")) 92 | self.send(message.as_string()) 93 | 94 | 95 | class SlackNotifier(Notifier): 96 | def __init__(self, config): 97 | super(SlackNotifier, self).__init__() 98 | self.__long_url = config["long url"] 99 | self.__short_url = config["short url"] 100 | 101 | def notify(self, investment_data): 102 | if investment_data[STRATEGIES.LONG_STRADDLE.value]: 103 | long_straddle_message = self._long_straddle_message( 104 | investment_data[STRATEGIES.LONG_STRADDLE.value] 105 | ) 106 | self.send_message(self.__long_url, long_straddle_message) 107 | 108 | if investment_data[STRATEGIES.SHORT_STRADDLE.value]: 109 | short_straddle_message = self._short_straddle_message( 110 | investment_data[STRATEGIES.SHORT_STRADDLE.value] 111 | ) 112 | self.send_message(self.__short_url, short_straddle_message) 113 | 114 | def send_message(self, url, text): 115 | """Sends message to Slack channel using webhook.""" 116 | 117 | webhook = WebhookClient(url) 118 | response = webhook.send(text=text) 119 | assert response.status_code == 200 120 | assert response.body == "ok" 121 | -------------------------------------------------------------------------------- /src/market_watcher/research/target_stocks.yaml: -------------------------------------------------------------------------------- 1 | ABEO: 2 | strategy: long straddle 3 | ACRX: 4 | strategy: long straddle 5 | ADVM: 6 | strategy: long straddle 7 | AMC: 8 | strategy: long straddle 9 | AVRO: 10 | strategy: long straddle 11 | BDTX: 12 | strategy: long straddle 13 | BTU: 14 | strategy: long straddle 15 | CCXI: 16 | strategy: long straddle 17 | CRTX: 18 | strategy: long straddle 19 | CVM: 20 | strategy: long straddle 21 | DDD: 22 | strategy: long straddle 23 | DKS: 24 | strategy: long straddle 25 | DY: 26 | strategy: long straddle 27 | ESGC: 28 | strategy: long straddle 29 | ETNB: 30 | strategy: long straddle 31 | ETON: 32 | strategy: long straddle 33 | EVFM: 34 | strategy: long straddle 35 | EXPR: 36 | strategy: long straddle 37 | FOSL: 38 | strategy: long straddle 39 | GME: 40 | strategy: long straddle 41 | INBX: 42 | strategy: long straddle 43 | JYNT: 44 | strategy: long straddle 45 | KERN: 46 | strategy: long straddle 47 | LCI: 48 | strategy: long straddle 49 | LJPC: 50 | strategy: long straddle 51 | LNTH: 52 | strategy: long straddle 53 | MESA: 54 | strategy: long straddle 55 | MMAC: 56 | strategy: long straddle 57 | NGM: 58 | strategy: long straddle 59 | NMRD: 60 | strategy: long straddle 61 | NNBR: 62 | strategy: long straddle 63 | NYMX: 64 | strategy: long straddle 65 | OMI: 66 | strategy: long straddle 67 | PDLB: 68 | strategy: long straddle 69 | PRVB: 70 | strategy: long straddle 71 | REX: 72 | strategy: long straddle 73 | SKY: 74 | strategy: long straddle 75 | SLCA: 76 | strategy: long straddle 77 | SRGA: 78 | strategy: long straddle 79 | SYRS: 80 | strategy: long straddle 81 | TELL: 82 | strategy: long straddle 83 | TITN: 84 | strategy: long straddle 85 | UEC: 86 | strategy: long straddle 87 | WBT: 88 | strategy: long straddle 89 | WKHS: 90 | strategy: long straddle 91 | WLLAW: 92 | strategy: long straddle 93 | WLLBW: 94 | strategy: long straddle 95 | WRAP: 96 | strategy: long straddle 97 | YEXT: 98 | strategy: long straddle 99 | PIRS: 100 | strategy: long straddle 101 | ADM: 102 | strategy: short straddle 103 | AEP: 104 | strategy: short straddle 105 | AGNC: 106 | strategy: short straddle 107 | AJRD: 108 | strategy: short straddle 109 | ALSK: 110 | strategy: short straddle 111 | ANTM: 112 | strategy: short straddle 113 | APH: 114 | strategy: short straddle 115 | ATO: 116 | strategy: short straddle 117 | BPYU: 118 | strategy: short straddle 119 | BR: 120 | strategy: short straddle 121 | CAG: 122 | strategy: short straddle 123 | CATM: 124 | strategy: short straddle 125 | CBB: 126 | strategy: short straddle 127 | CI: 128 | strategy: short straddle 129 | CL: 130 | strategy: short straddle 131 | CLGX: 132 | strategy: short straddle 133 | CNBKA: 134 | strategy: short straddle 135 | FFG: 136 | strategy: short straddle 137 | FRTA: 138 | strategy: short straddle 139 | GGG: 140 | strategy: short straddle 141 | GRA: 142 | strategy: short straddle 143 | HSY: 144 | strategy: short straddle 145 | IBM: 146 | strategy: short straddle 147 | JNJ: 148 | strategy: short straddle 149 | KO: 150 | strategy: short straddle 151 | LDOS: 152 | strategy: short straddle 153 | LMNX: 154 | strategy: short straddle 155 | LTC: 156 | strategy: short straddle 157 | MDLZ: 158 | strategy: short straddle 159 | MGLN: 160 | strategy: short straddle 161 | MMC: 162 | strategy: short straddle 163 | MMS: 164 | strategy: short straddle 165 | NAV: 166 | strategy: short straddle 167 | NUAN: 168 | strategy: short straddle 169 | ORBC: 170 | strategy: short straddle 171 | PM: 172 | strategy: short straddle 173 | PNM: 174 | strategy: short straddle 175 | PROS: 176 | strategy: short straddle 177 | PTVCB: 178 | strategy: short straddle 179 | SO: 180 | strategy: short straddle 181 | SPWH: 182 | strategy: short straddle 183 | STAY: 184 | strategy: short straddle 185 | STND: 186 | strategy: short straddle 187 | TPCO: 188 | strategy: short straddle 189 | TTMI: 190 | strategy: short straddle 191 | UNH: 192 | strategy: short straddle 193 | WIFI: 194 | strategy: short straddle 195 | WTRE: 196 | strategy: short straddle 197 | YUM: 198 | strategy: short straddle 199 | GFN: 200 | strategy: short straddle 201 | 202 | -------------------------------------------------------------------------------- /src/market_watcher/research/target_stocks_short.yaml: -------------------------------------------------------------------------------- 1 | AAPL: 2 | strategy: short straddle 3 | KO: 4 | strategy: short straddle 5 | TSLA: 6 | strategy: long straddle 7 | WBT: 8 | strategy: long straddle 9 | PIRS: 10 | strategy: long straddle 11 | YUM: 12 | strategy: short straddle -------------------------------------------------------------------------------- /src/market_watcher/state.json: -------------------------------------------------------------------------------- 1 | {"running": false, "email": true, "slack": true} -------------------------------------------------------------------------------- /src/market_watcher/version.py: -------------------------------------------------------------------------------- 1 | VERSION = "0.1.0" 2 | -------------------------------------------------------------------------------- /src/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | from market_watcher.version import VERSION 4 | 5 | 6 | setup( 7 | name="market_watcher", 8 | version=VERSION, 9 | packages=find_packages(), 10 | include_package_data=True, 11 | install_requires=[ 12 | "Click", 13 | ], 14 | entry_points={ 15 | "console_scripts": [ 16 | "market_watcher_cli = market_watcher.market_watcher_cli:cli", 17 | ], 18 | }, 19 | ) 20 | -------------------------------------------------------------------------------- /tests/test_intestment_opportunity.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from market_watcher.common import MarketWatcherEngine 4 | 5 | 6 | def mock_threashold_config(): 7 | """Mocks trategy alert configuration.""" 8 | pnl_threashold = {} 9 | pnl_threashold["LONG_THRESHOLD"] = 5 10 | pnl_threashold["SHORT_THRESHOLD"] = 0.5 11 | return pnl_threashold 12 | 13 | 14 | def mock_get_daily_pnls(): 15 | """Mock fetching of daily P&Ls.""" 16 | return { 17 | "ABEO": 7.59, 18 | "ACRX": 4, 19 | "TPCO": 0.3, 20 | "TTMI": 0.6, 21 | } 22 | 23 | 24 | @pytest.fixture 25 | def mock_target_stocks(): 26 | """Mocks target stocks.""" 27 | print("mock_target_stocks") 28 | return { 29 | "ABEO": {"strategy": "long straddle"}, 30 | "ACRX": {"strategy": "long straddle"}, 31 | "TPCO": {"strategy": "short straddle"}, 32 | "TTMI": {"strategy": "short straddle"}, 33 | } 34 | 35 | 36 | def test_threashold(monkeypatch, mock_target_stocks): 37 | """Tests if MarketWatcherEngine correctly reports stocks.""" 38 | 39 | # Mock fetching data from Yahoo Finance 40 | monkeypatch.setattr( 41 | "market_watcher.common.MarketWatcherEngine.get_daily_pnls", 42 | lambda dummy: mock_get_daily_pnls(), 43 | ) 44 | 45 | # Mock alert threashold config 46 | monkeypatch.setattr( 47 | "market_watcher.common.get_pnl_threashold_config", 48 | lambda: mock_threashold_config(), 49 | ) 50 | 51 | engine = MarketWatcherEngine(mock_target_stocks) 52 | investment_opportunities = engine.process_latest_market_movements() 53 | 54 | assert "ABEO" in investment_opportunities["long straddle"] 55 | assert "TPCO" in investment_opportunities["short straddle"] 56 | assert "ACRX" not in investment_opportunities["long straddle"] 57 | assert "TTMI" not in investment_opportunities["short straddle"] 58 | --------------------------------------------------------------------------------