├── .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 | [](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 |
--------------------------------------------------------------------------------