├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── app ├── __init__.py ├── connection.py ├── database.py ├── feeds.py ├── globals.py ├── handlers.py ├── ib │ ├── __init__.py │ ├── ext │ │ ├── AnyWrapper.py │ │ ├── AnyWrapperMsgGenerator.py │ │ ├── ComboLeg.py │ │ ├── CommissionReport.py │ │ ├── Contract.py │ │ ├── ContractDetails.py │ │ ├── EClientErrors.py │ │ ├── EClientSocket.py │ │ ├── EReader.py │ │ ├── EWrapper.py │ │ ├── EWrapperMsgGenerator.py │ │ ├── Execution.py │ │ ├── ExecutionFilter.py │ │ ├── MarketDataType.py │ │ ├── Order.py │ │ ├── OrderComboLeg.py │ │ ├── OrderState.py │ │ ├── ScannerSubscription.py │ │ ├── TagValue.py │ │ ├── TickType.py │ │ ├── UnderComp.py │ │ ├── Util.py │ │ └── __init__.py │ ├── lib │ │ ├── __init__.py │ │ ├── logger.py │ │ └── overloading.py │ ├── opt │ │ ├── __init__.py │ │ ├── connection.py │ │ ├── dispatcher.py │ │ ├── message.py │ │ ├── messagetools.py │ │ ├── receiver.py │ │ └── sender.py │ ├── sym │ │ └── __init__.py │ └── wiki │ │ ├── GettingStarted.md │ │ ├── ThreadAndGUINotes.md │ │ └── ibPyOptional.md ├── main.py ├── parsers.py ├── sync.py ├── utils.py └── uwsgi.ini ├── docker-compose.yml ├── etc └── nginx.conf ├── ibheadless ├── Dockerfile ├── ibg.xml └── start.sh ├── proxy.Dockerfile └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | notes.md 2 | etc/ibrest.* 3 | .idea 4 | /app/ibrest.* 5 | env-file 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # This IBREST image relies on an IB Gateway accessible at port 4003 (intead of 4001). Use ibheadless for that and link. 2 | # Assume our ibheadless container is called `ibgw` 3 | # ...so be sure to `link` this container to ibheadless accordingly 4 | 5 | # To build docker image: 6 | # docker build -t ibrest . 7 | 8 | # To run docker image, use: 9 | # `docker run -d --restart=always --name ibrest --link ibgw -e "ID_SECRET_KEY=mysecret" -p 443:443 ibrest` 10 | 11 | # To run while developing, map your local app folder to /app as a volume on the container: 12 | # `docker run -d --restart=always --name ibrest --link ibgw -e "ID_SECRET_KEY=mysecret" -p 443:443 -v /home/jhaury/ibrest:/app ibrest` 13 | # or maybe 14 | # `docker run -d --restart=always --name ibrest --link ibgw -e "ID_SECRET_KEY=mysecret" -p 443:443 -v /home/jhaury/ibrest/app:/app ibrest` 15 | 16 | # If running TWS on the same machine and want to run a Conatiner which connects to it with default 7497 port: 17 | # docker run --name ibrest --env-file=env-file -p 443:443 ibrest 18 | # Where your env-file has IBREST_PORT, IBREST_HOST, IBGW_PORT_4003_TCP_ADDR, IBGW_PORT_4003_TCP_PORT and IBGW_CLIENT_ID as needed 19 | 20 | FROM python:2.7-alpine 21 | MAINTAINER Jason Haury "jason.haury@gmail.com" 22 | RUN pip install --upgrade pip 23 | COPY requirements.txt / 24 | RUN pip install -r /requirements.txt 25 | 26 | # for production, do this: 27 | COPY ./app /app 28 | # for development, do this instead: 29 | #VOLUME /app 30 | 31 | WORKDIR /app 32 | 33 | # To enable HTTPS, we need to copy certs 34 | # be sure to create your certs! 35 | #COPY ./etc/ibrest.crt . 36 | #COPY ./etc/ibrest.key . 37 | 38 | RUN apk --update add openssl 39 | RUN openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./ibrest.key -out ./ibrest.crt -new -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=www.example.com" 40 | 41 | CMD [ "python", "./main.py" ] 42 | EXPOSE 443 43 | 44 | 45 | # Be sure to set environment params: IBGW_HOST and IBGW_PORT for how to connect to ibgw if you aren't linking per the "run" examples 46 | 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jason Haury 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 | # IBREST 2 | REST API for use with [Interactive Brokers TWS and IB Gateway](https://www.interactivebrokers.com/en/index.php?f=5041&ns=T) 3 | 4 | ## Summary 5 | By using [Flask-RESTful](http://flask-restful-cn.readthedocs.org/en/0.3.4/) (and therefore [Flask](http://flask.pocoo.org/)), a web-based API is created which then uses [IbPy](https://github.com/blampe/IbPy) to connect to an instance of TWS or IB Gateway, and interact with Interactive Brokers. This documentation will generally use "TWS" to mean "TWS or IBGateway" 6 | 7 | ## Intents 8 | ### Better Firewalling 9 | This API should run on the same machine with TWS so that TWS can be set to only allow connections from the local host. This provides a nice security feature, and lets IP access then be controlled by more advanced firewall software (ie to allow wildcards or IP ranges to access this REST API and therefore the TWS instance it interfaces with). 10 | 11 | ### Google App Engine 12 | This API layer between your algorithm code and the IbPy API code is intended for use on Google App Engine where an algoritm may be operating within the GAE system, with TWS running on a Compute Engine VM (ie [Brokertron](http://www.brokertron.com/)). TWS does not support wildcard IP address (which would be a security hole anyways), and GAE uses many IPs when making outbound connections from one's App (making it neigh impossible to list all possible IPs in TWS' whitelist). However, this project will aim to stay generalized enough so that it can be used outside of GAE. 13 | 14 | For running flask as a Docker container, consider [this tutorial](http://containertutorials.com/docker-compose/flask-simple-app.html) or use this existing [docker-flask image](https://hub.docker.com/r/p0bailey/docker-flask/). 15 | 16 | TODO: Consider using following GAE features: 17 | 18 | 1. [Logging messages to GAE logger](https://cloud.google.com/logging/docs/agent/installation) 19 | 2. [Storing IB messages to DataStore](https://cloud.google.com/datastore/docs/getstarted/start_python/) 20 | 3. [Using Task Queue to get requests from GAE](https://cloud.google.com/appengine/docs/java/taskqueue/rest/about_auth). IB allows 8 `client_ids`, which will impose a limit of 8 "simultaneous" tasks at a time with IBREST, unless some kind of task queuing happens (ie [Celery](http://flask.pocoo.org/docs/0.10/patterns/celery/)) 21 | 22 | ### Synchronous 23 | The [IB API](https://www.interactivebrokers.com/en/software/api/api.htm) is designed to be asynchronous, which adds labor 24 | to writing code to interface with it. As IB message exchanges are pretty fast (a couple seconds at most), it's within 25 | time margins for use with HTTP requests (~60sec timeouts). Thus, the exposed RESTful API opens a user-friendly 26 | synchronous interface with IB. 27 | 28 | ### Pythonic 29 | Use [IbPyOptional](https://code.google.com/p/ibpy/wiki/IbPyOptional) (`ib.opt` module) maximally. 30 | 31 | ## REST API 32 | As IBREST is built with IbPy, and IbPy is based on the IB Java API, then IBREST will aim to use maximally similar language as found in those APIs' documentation. The Java API is broken into two main layers: 33 | 34 | 1. [EClientSocket](https://www.interactivebrokers.com/en/software/api/apiguide/java/java_eclientsocket_methods.htm) - the connection to TWS for _sending_ messages to IB. 35 | 2. [EWrapper](https://www.interactivebrokers.com/en/software/api/apiguide/java/java_ewrapper_methods.htm) - the message processing logic for messages _returned_ by IB. Some messages are streamed in at intervals (ie subscriptions) and will not be exposed as a REST URI. Such are marked `Unexposed data feed` below. 36 | 37 | TODO: Consider creating an RSS feed endpoint for such "Unexposed data feed" data. 38 | 39 | **NOTE:** As noted in [Synchronous], TWS only allows 8 connections (client ID's 0-7, where 0 has some special privileges). 40 | An IBREST client only uses one of these allowed connections. To increase throughput, you can create a "proxy" (see 41 | `proxy.Dockerfile`) to front 8 IBREST instances, thereby reducing latency in quick, successive calls. 42 | 43 | While Python objects are named `with_under_scores`, IbPy and its corresponding Java code uses camelCase. The IBREST source code will use camelCase to imply a direct correlation with IbPy. For obejcts which only pertain to IBREST inner logic, under_scored names will be used. 44 | 45 | All endpoints return JSON formatted data using keys and values consistent with IbPy and IB Java APIs (case sensitive). 46 | 47 | For security, HTTPS is used by default. To create your own cert and key, try: 48 | `openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ibrest.key -out ibrest.crt` 49 | 50 | ### Endpoint Groups 51 | The documentation for each of these layers contains these sections, after which IBREST will create endpoints groups when applicable. An endpoint may provide either a synchronous reponse or an atom feed. 52 | 53 | IB Documentation | REST endpoint | Sync or Feed 54 | ---------------- | ------------- | -------------------- 55 | Connection and Server | NA: Handled by Flask configuration 56 | Market Data | /market/data | Feed 57 | Orders| /order | Sync 58 | Account and Portfolio | /account | Sync 59 | Contract Details | /contract | Sync [TBD] 60 | Executions | /executions | Sync 61 | Market Depth | /market/depth | Feed [TBD] 62 | News Bulletins | /news | Feed [TBD] 63 | Financial Advisors | /financial | Feed [TBD] 64 | Historical Data | /historical | Sync [TBD] 65 | Market Scanners | /market/scanners | Feed [TBD] 66 | Real Time Bars| /bars | Feed [TBD] 67 | Fundamental Data | /fundamental | Feed [TBD] 68 | Display Groups| /displaygroups | Feed [TBD] 69 | 70 | 71 | ### Synchronous Response Endpoint Details 72 | These endpoints return a since single response. 73 | 74 | #### GET /order 75 | A GET request retrieves a details for all open orders via `reqAllOpenOrders`. 76 | 77 | #### POST /order 78 | A POST request will generate a `placeOrder()` EClient call, then wait for the order to be filled . 79 | 80 | #### DELETE /order 81 | A DELETE request will call `cancelOrder()`. 82 | 83 | #### GET /account/updates 84 | A GET request to `/account/updates` will use `reqAccountUpdates()` to return messages received from `updateAccountValue/AccountTime()` and `updatePortfolio` EWrapper messages, as triggered by `accountDownloadEnd()`. 85 | 86 | #### GET /account/summary 87 | A GET request to `/account/summary` will use `reqAccountSummary()` to return messages received from `updateSummary()` EWrapper message as triggered by `accountSummaryEnd()`. 88 | 89 | #### GET /account/positions 90 | A GET request to `/account/positions` will use `reqPostions()` to return messages received from `position()` EWrapper message as triggered by `positionEnd()`. 91 | 92 | ### Atom Feed Endpoint Details 93 | These endpoints are used for atom feeds. They must be subscribed to or unsubscribed from. They are not yet implemented 94 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | """ Needs documentation 2 | """ 3 | __author__ = 'Jason Haury' -------------------------------------------------------------------------------- /app/connection.py: -------------------------------------------------------------------------------- 1 | """ Needs documentation 2 | """ 3 | import time 4 | # from app import log 5 | import logging 6 | import globals as g 7 | #from flask import g 8 | import utils 9 | import handlers 10 | from flask import current_app 11 | 12 | __author__ = 'Jason Haury' 13 | 14 | log = logging.getLogger(__name__) 15 | # log = utils.setup_logger(log) 16 | #log.setLevel(logging.WARN) 17 | 18 | # TODO use single clientId. Update sync.py for all /order endpoints 19 | def get_client(): 20 | """ Creates a client connection to be used with orders 21 | """ 22 | # Get client ID from our non-order pool list in memory 23 | timeout = g.timeout 24 | while g.clientId_in_use: 25 | log.debug('Waiting for clientId to become available...({})'.format(timeout)) 26 | time.sleep(0.5) 27 | timeout -= 1 28 | 29 | client = g.client_connection 30 | 31 | # Enable logging if we're in debug mode 32 | if current_app.debug is True: 33 | client.enableLogging() 34 | 35 | # Reconnect if needed 36 | if not client.isConnected(): 37 | log.debug('Client {} not connected. Trying to reconnect...'.format(g.client_id)) 38 | client.disconnect() 39 | time.sleep(1) 40 | client.connect() 41 | # If we failed to reconnect, be sure to put our client ID back in the pool 42 | if client.isConnected() is False: 43 | raise Exception('Client cannot connect') 44 | return client 45 | 46 | 47 | def setup_client(client): 48 | """ Attach handlers to the clients 49 | """ 50 | #log.debug('setup_client {}'.format(client.clientId)) 51 | client.register(handlers.connection_handler, 'ManagedAccounts', 'NextValidId') 52 | client.register(handlers.history_handler, 'HistoricalData') 53 | client.register(handlers.order_handler, 'OpenOrder', 'OrderStatus', 'OpenOrderEnd') 54 | client.register(handlers.portfolio_positions_handler, 'Position', 'PositionEnd') 55 | client.register(handlers.account_summary_handler, 'AccountSummary', 'AccountSummaryEnd') 56 | client.register(handlers.account_update_handler, 'UpdateAccountTime', 'UpdateAccountValue', 'UpdatePortfolio', 57 | 'AccountDownloadEnd') 58 | client.register(handlers.contract_handler, 'ContractDetails') 59 | client.register(handlers.executions_handler, 'ExecDetails', 'ExecDetailsEnd', 'CommissionsReport') 60 | client.register(handlers.error_handler, 'Error') 61 | # Add handlers for feeds 62 | client.register(handlers.market_handler, 'TickSize', 'TickPrice') 63 | 64 | # For easier debugging, register all messages with the generic handler 65 | # client.registerAll(handlers.generic_handler) 66 | 67 | # Be sure we're in a disconnected state 68 | client.disconnect() 69 | 70 | 71 | def close_client(client): 72 | """ Put clientId back into pool but don't close connection 73 | """ 74 | if client is None: 75 | log.warn('Trying to close None client') 76 | return 77 | client_id = client.clientId 78 | g.clientId_in_use = False 79 | return client_id 80 | -------------------------------------------------------------------------------- /app/database.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import create_engine 2 | from sqlalchemy.orm import scoped_session, sessionmaker 3 | from sqlalchemy.ext.declarative import declarative_base 4 | from sqlalchemy import Column, Integer, String 5 | 6 | engine = create_engine('sqlite:///ibrest.db', convert_unicode=True) 7 | db_session = scoped_session(sessionmaker(autocommit=False, 8 | autoflush=False, 9 | bind=engine)) 10 | Base = declarative_base() 11 | Base.query = db_session.query_property() 12 | 13 | # --------------------------------------------------------------------- 14 | # MODELS 15 | # --------------------------------------------------------------------- 16 | class FilledOrders(Base): 17 | """ Once an order is filled, fill price, etc should be saved here """ 18 | __tablename__ = 'filled_orders' 19 | order_id = Column(Integer, primary_key=True) 20 | order_status = Column(String) 21 | 22 | def __init__(self, order_id=None, order_status=None): 23 | self.order_id = order_id 24 | self.order_status = order_status 25 | 26 | def __repr__(self): 27 | return ''.format(self.ib_id) 28 | 29 | 30 | class Commissions(Base): 31 | """ Once an order is filled, fill price, etc should be saved here """ 32 | __tablename__ = 'commissions' 33 | exec_id = Column(Integer, primary_key=True) 34 | commission_report = Column(String) 35 | 36 | def __init__(self, exec_id=None, commission_report=None): 37 | self.exec_id = exec_id 38 | self.commission_report = commission_report 39 | 40 | def __repr__(self): 41 | return ''.format(self.ib_id) 42 | 43 | 44 | 45 | def init_db(): 46 | # import all modules here that might define models so that 47 | # they will be registered properly on the metadata. Otherwise 48 | # you will have to import them first before calling init_db() 49 | Base.metadata.create_all(bind=engine) -------------------------------------------------------------------------------- /app/feeds.py: -------------------------------------------------------------------------------- 1 | """ In case of IB EClientSocket requests which generate continuous feeds of data, this module will generate atom feeds. 2 | 3 | This is accomplished by storing data in a Sqlite3 DB 4 | """ 5 | import logging 6 | import time 7 | from datetime import datetime, timedelta 8 | 9 | import connection 10 | import globals as g 11 | import utils 12 | from connection import get_client, close_client 13 | from ib.ext.Contract import Contract 14 | from sync import log 15 | from utils import make_contract 16 | 17 | __author__ = 'Jason Haury' 18 | log = logging.getLogger(__name__) 19 | 20 | 21 | # log = utils.setup_logger(log) 22 | def get_tickerId(): 23 | """ Returns next valid ticker ID in a way which won't overlap with recent orderIds in the error responses""" 24 | 25 | g.tickerId += 1 26 | # Since error messages key off `id` being both an orderId or tickerId depending on context, we may have overlapping 27 | # `id`'s between our feeds and sync functions. As a safety, keep our either much larger or much lower than our 28 | # orderId since we can't control orderId 29 | id_threshold = 10000 30 | if g.orderId < id_threshold: 31 | if g.tickerId < id_threshold: 32 | g.tickerId += id_threshold 33 | elif g.tickerId > id_threshold: 34 | # our orderId is >= id_threshold 35 | g.tickerId -= id_threshold 36 | return g.tickerId 37 | 38 | 39 | # --------------------------------------------------------------------- 40 | # MARKET DATA FUNCTIONS 41 | # --------------------------------------------------------------------- 42 | # TODO This needs to be a feed, not an endpoint. http://flask.pocoo.org/snippets/10/. 43 | def get_market_data(symbol, args): 44 | """ The m_symbol for the contract is all our API takes from user (for now). 45 | User must have appropriate IB subscriptions. 46 | https://www.interactivebrokers.com/en/software/api/apiguide/java/reqmktdata.htm 47 | """ 48 | # TODO consider taking more args to get our market data with: filter (price, size, optionComputation, etc) and desired length of data. Also, tick lists 49 | log.debug('Getting market data for {}'.format(symbol)) 50 | # Connect to TWS 51 | client = get_client() 52 | if client.isConnected() is False: 53 | return {'error': 'Not connected to TWS'} 54 | log.debug('Creating Contract for symbol {}'.format(symbol)) 55 | contract = make_contract(str(symbol), args) # , prim_exch='NASDAQ') 56 | 57 | our_tickerId = get_tickerId() 58 | g.market_resp[our_tickerId] = [] 59 | log.info('Requesting market data') 60 | client.reqMktData(our_tickerId, contract, '', False) 61 | timeout = g.timeout 62 | while len(g.market_resp[our_tickerId]) < 5 and client.isConnected() is True and timeout > 0: 63 | log.info("Waiting for responses on {}...".format(client)) 64 | time.sleep(0.25) 65 | timeout -= 1 66 | client.cancelMktData(our_tickerId) 67 | log.debug('Disconnected Market client {}'.format(close_client(client))) 68 | return g.market_resp.pop(our_tickerId) 69 | 70 | 71 | # --------------------------------------------------------------------- 72 | # HISTORY FUNCTIONS 73 | # --------------------------------------------------------------------- 74 | # TODO move this to sync.py since it is not really a feed (it provides a `finished` message) 75 | def get_history(symbol, args): 76 | """ Args may be any of those in reqHistoricalData() 77 | https://www.interactivebrokers.com/en/software/api/apiguide/java/reqhistoricaldata.htm 78 | """ 79 | log.debug('history symbol {}, args: {}'.format(symbol, args)) 80 | 81 | client = connection.get_client() 82 | if client is None: 83 | return g.error_resp[-2] 84 | elif client.isConnected() is False: 85 | return g.error_resp[-1] 86 | 87 | # Populate contract with appropriate 88 | contract = utils.make_contract(str(symbol), args) 89 | 90 | # log.debug('contract: {}'.format(contract)) 91 | 92 | our_tickerId = get_tickerId() 93 | g.history_resp[our_tickerId] = dict() 94 | g.error_resp[our_tickerId] = None 95 | # endtime = (datetime.now() - timedelta(minutes=15)).strftime('%Y%m%d %H:%M:%S') 96 | log.debug('requesting historical data') 97 | req_dict = dict(tickerId=our_tickerId, 98 | contract=contract, 99 | endDateTime=str(args.get('endDateTime', datetime.now().strftime('%Y%m%d %H:%M:%S'))), 100 | durationStr=str(args.get('durationStr', '1 D')), 101 | barSizeSetting=str(args.get('barSizeSetting', '1 min')), 102 | whatToShow=args.get('whatToShow', 'TRADES'), 103 | useRTH=int(args.get('useRTH', 0)), 104 | formatDate=int(args.get('formatDate', 2)) 105 | ) 106 | log.debug('req_dict {}'.format(req_dict)) 107 | client.reqHistoricalData(**req_dict) 108 | 109 | """ 110 | durationStr='60 S', 111 | barSizeSetting='1 min', 112 | whatToShow='TRADES', 113 | useRTH=0, 114 | formatDate=1) 115 | """ 116 | log.debug('waiting for historical data)') 117 | timeout = g.timeout 118 | while not any( 119 | ["finished" in h for h in g.history_resp[our_tickerId].keys()]) and client.isConnected() and timeout > 0: 120 | log.debug("Waiting for History responses on client {}...".format(client.clientId)) 121 | if g.error_resp.get(our_tickerId, None) is not None: 122 | # An errorCode of 366 seen on IBGW logs is often meaningless since it shows up for every history call several 123 | # seconds after it's already found data and receive the "finished" message. If data 124 | # did exist, then it would be returned before the error message was generated 125 | connection.close_client(client) 126 | return g.error_resp[our_tickerId] 127 | elif client.isConnected() is False: 128 | return {'errorMsg': 'Connection lost'} 129 | time.sleep(0.25) 130 | timeout -= 1 131 | # log.debug('history: {}'.format(g.history_resp)) 132 | log.debug('closing historical data stream') 133 | client.cancelHistoricalData(our_tickerId) 134 | connection.close_client(client) 135 | return g.history_resp.pop(our_tickerId) 136 | -------------------------------------------------------------------------------- /app/globals.py: -------------------------------------------------------------------------------- 1 | """ Needs documentation 2 | """ 3 | import os 4 | from ib.opt import ibConnection 5 | import itsdangerous 6 | __author__ = 'Jason Haury' 7 | 8 | 9 | # --------------------------------------------------------------------- 10 | # CONFIGURATION 11 | # --------------------------------------------------------------------- 12 | # Use environment variables 13 | ibgw_host = os.getenv('IBGW_PORT_4003_TCP_ADDR', os.getenv('IBGW_HOST', '127.0.0.1')) 14 | ibgw_port = int(os.getenv('IBGW_PORT_4003_TCP_PORT', os.getenv('IBGW_PORT', '4003'))) # Use 7496 for TWS 15 | client_id = int(os.getenv('IBGW_CLIENT_ID', 0)) # Use a unique value for each IBREST instance you connect to same TWS 16 | 17 | # Beacon globals 18 | id_secret_key = os.getenv('ID_SECRET_KEY', None) 19 | serializer = itsdangerous.JSONWebSignatureSerializer(id_secret_key, salt='beacon') if id_secret_key else None 20 | beacon_last_token = None 21 | beacon_current_token = None 22 | current_ip = None 23 | 24 | timeout = 20 # Max loops 25 | 26 | # Mutables 27 | managedAccounts = set() 28 | clientId_in_use = False 29 | client_connection = ibConnection(ibgw_host, ibgw_port, client_id) 30 | getting_order_id = False 31 | orderId = 0 32 | tickerId = 0 33 | 34 | 35 | # --------------------------------------------------------------------- 36 | # SYNCHRONOUS RESPONSES 37 | # --------------------------------------------------------------------- 38 | # Responses. Global dicts to use for our responses as updated by Message handlers, keyed by clientId 39 | portfolio_positions_resp = {client_id: dict()} 40 | account_summary_resp = {client_id: dict(accountSummaryEnd=False)} 41 | account_update_resp = dict(accountDownloadEnd=False, updateAccountValue=dict(), updatePortfolio=[]) 42 | # Track errors keyed in "id" which is the orderId or tickerId (or -1 for connection errors) 43 | error_resp = {-1: {"errorCode": 502, "errorMsg": "Couldn't connect to TWS. Confirm that \"Enable ActiveX and Socket " 44 | "Clients\" is enabled on the TWS \"Configure->API\" menu.", "id": -1}, 45 | -2: {"errorCode": None, "errorMsg": "Too many requests. Client ID not available in time. Try request later", "id": -2}} 46 | 47 | # Store contractDetails messages 48 | contract_resp = dict(contractDetailsEnd=False, contractDetails=dict(), bondContractDetails=dict()) 49 | # When getting order info, we want it for all clients, and don't care so much if multiple requests try to populate this 50 | order_resp = dict(openOrderEnd=False, openOrder=[], orderStatus=[]) 51 | # When placing/deleting orders, we care about what orderId is used. Key off orderId. 52 | order_resp_by_order = dict() 53 | # Recent Executions 54 | executions_resp = dict(execDetailsEnd=False, execDetails=[], commissionReport=dict()) 55 | 56 | 57 | # --------------------------------------------------------------------- 58 | # FEED RESPONSE BUFFERS 59 | # --------------------------------------------------------------------- 60 | # Globals to use for feed responses 61 | market_resp = dict() # market feed 62 | # Dict of history responses keyed off of reqId (tickerId) 63 | history_resp = dict() 64 | 65 | -------------------------------------------------------------------------------- /app/handlers.py: -------------------------------------------------------------------------------- 1 | """ Needs documentation 2 | """ 3 | import globals as g 4 | # import os 5 | import json 6 | # import sync 7 | from ib.ext.Contract import Contract 8 | from ib.ext.Order import Order 9 | from ib.ext.OrderState import OrderState 10 | from ib.ext.ContractDetails import ContractDetails 11 | from ib.ext.Execution import Execution 12 | from ib.ext.CommissionReport import CommissionReport 13 | from database import FilledOrders, Commissions, db_session 14 | import logging 15 | 16 | __author__ = 'Jason Haury' 17 | 18 | log = logging.getLogger(__name__) 19 | log.setLevel(logging.DEBUG) 20 | 21 | 22 | 23 | # --------------------------------------------------------------------- 24 | # SHARED FUNCTIONS 25 | # --------------------------------------------------------------------- 26 | def msg_to_dict(msg): 27 | """ Converts a message to a dict 28 | """ 29 | d = dict() 30 | for i in msg.items(): 31 | if isinstance(i[1], (Contract, Order, OrderState, ContractDetails, Execution, CommissionReport)): 32 | d[i[0]] = i[1].__dict__ 33 | else: 34 | d[i[0]] = i[1] 35 | return d 36 | 37 | 38 | # --------------------------------------------------------------------- 39 | # SYNCHRONOUS RESPONSE MESSAGE HANDLERS 40 | # --------------------------------------------------------------------- 41 | def connection_handler(msg): 42 | """ Handles messages from when we connect to TWS 43 | """ 44 | if msg.typeName == 'nextValidId': 45 | g.orderId = max(int(msg.orderId), g.orderId) 46 | # log.info('Connection lock released. OrderId set to {}'.format(g.orderId)) 47 | # g.getting_order_id = False # Unlock place_order() to now be called again. 48 | log.info('Updated orderID: {}'.format(g.orderId)) 49 | elif msg.typeName == 'managedAccounts': 50 | g.managedAccounts = set(msg.accountsList.split(',')) 51 | log.debug('Updated managed accounts: {}'.format(g.managedAccounts)) 52 | 53 | 54 | def account_summary_handler(msg): 55 | """ Update our global Account Summary data response dict 56 | """ 57 | if msg.typeName == 'accountSummary': 58 | # account = msg_to_dict(msg) 59 | g.account_summary_resp[int(msg.reqId)][msg.tag] = msg.value 60 | elif msg.typeName == 'accountSummaryEnd': 61 | g.account_summary_resp[int(msg.reqId)]['accountSummaryEnd'] = True 62 | log.debug('SUMMARY: {})'.format(msg)) 63 | 64 | 65 | def account_update_handler(msg): 66 | """ Update our global Account Update data response dict 67 | """ 68 | if msg.typeName == 'updateAccountTime': 69 | g.account_update_resp[msg.typeName] = msg.updateAccountTime 70 | elif msg.typeName == 'updateAccountValue': 71 | account = msg_to_dict(msg) 72 | g.account_update_resp[msg.typeName][msg.key] = account 73 | elif msg.typeName == 'updatePortfolio': 74 | account = msg_to_dict(msg) 75 | g.account_update_resp[msg.typeName].append(account.copy()) 76 | elif msg.typeName == 'accountDownloadEnd': 77 | g.account_update_resp[msg.typeName] = True 78 | log.debug('UPDATE: {})'.format(msg)) 79 | 80 | 81 | def portfolio_positions_handler(msg): 82 | """ Update our global Portfolio Positions data response dict 83 | """ 84 | if msg.typeName == 'position': 85 | position = msg_to_dict(msg) 86 | g.portfolio_positions_resp['positions'].append(position.copy()) 87 | elif msg.typeName == 'positionEnd': 88 | g.portfolio_positions_resp['positionEnd'] = True 89 | log.debug('POSITION: {})'.format(msg)) 90 | 91 | 92 | def history_handler(msg): 93 | """ Update our global history data response dict 94 | """ 95 | history = msg_to_dict(msg) 96 | g.history_resp[int(history['reqId'])][msg.date] = history.copy() 97 | # log.debug('HISTORY: {})'.format(msg)) 98 | 99 | 100 | def order_handler(msg): 101 | """ Update our global Order data response dict 102 | """ 103 | if msg.typeName in ['orderStatus', 'openOrder']: 104 | d = msg_to_dict(msg) 105 | g.order_resp[msg.typeName].append(d.copy()) 106 | order_msg = g.order_resp_by_order.get(d['orderId'], dict(openOrder=dict(), orderStatus=dict())) 107 | order_msg[msg.typeName] = d.copy() 108 | g.order_resp_by_order[d['orderId']] = order_msg 109 | 110 | # Save all filled orders to SQLite DB 111 | if msg.typeName == 'orderStatus' and msg.status == 'Filled': 112 | filled_order = FilledOrders(msg.id, json.dumps(d)) 113 | db_session.merge(filled_order) 114 | db_session.commit() 115 | 116 | 117 | log.debug('ORDER: {}'.format(d)) 118 | elif msg.typeName == 'openOrderEnd': 119 | g.order_resp['openOrderEnd'] = True 120 | log.debug('ORDER: {})'.format(msg)) 121 | 122 | 123 | def contract_handler(msg): 124 | """ Update our global to keep the latest ContractDetails available for API returns. 125 | https://www.interactivebrokers.com/en/software/api/apiguide/java/contractdetails.htm 126 | 127 | """ 128 | if msg.typeName in ['contractDetails', 'bondContractDetails']: 129 | d = msg_to_dict(msg) 130 | g.contract_resp[msg.typeName][msg.reqId] = d[msg.typeName].copy() 131 | log.debug('CONTRACT: {}'.format(d)) 132 | elif msg.typeName == 'contractDetailsEnd': 133 | g.contract_resp['contractDetailsEnd'] = True 134 | log.debug('CONTRACT: {})'.format(msg)) 135 | 136 | 137 | def executions_handler(msg): 138 | """ Update our global to keep the latest execDetails available for API returns. 139 | https://www.interactivebrokers.com/en/software/api/apiguide/java/execdetails.htm 140 | 141 | """ 142 | if msg.typeName in ['execDetails', 'commissionReport']: 143 | d = msg_to_dict(msg) 144 | log.debug('Dictified msg: {}'.format(d)) 145 | if msg.typeName == 'execDetails': 146 | g.executions_resp[msg.typeName].append(dict(execution=d['execution'].copy(), contract=d['contract'].copy())) 147 | # Save all CommissionReports to SQLite DB 148 | elif msg.typeName == 'commissionReport': 149 | commission_report = Commissions(msg.m_execId, json.dumps(d)) 150 | db_session.merge(commission_report) 151 | db_session.commit() 152 | log.debug('EXECUTIONS: {}'.format(d)) 153 | elif msg.typeName == 'execDetailsEnd': 154 | g.executions_resp['execDetailsEnd'] = True 155 | log.debug('EXECUTIONS: {})'.format(msg)) 156 | 157 | 158 | def error_handler(msg): 159 | """ Update our global to keep the latest errors available for API returns. Error messages have an id attribute which 160 | maps to the orderId or tickerId of the request which generated the error. 161 | https://www.interactivebrokers.com/en/software/api/apiguide/java/error.htm 162 | 163 | IbPy provides and id of -1 for connection error messages 164 | """ 165 | g.error_resp[msg.id] = {i[0]: i[1] for i in msg.items()} 166 | log.error('ERROR: {}'.format(msg)) 167 | 168 | # TODO if clientId is already in use erroneously, attempt to recover, or generate new clientId 169 | # If our client connections get out of sync: 170 | 171 | 172 | def generic_handler(msg): 173 | log.debug('MESSAGE: {}, {})'.format(msg, msg.keys)) 174 | 175 | 176 | # --------------------------------------------------------------------- 177 | # FEED MESSAGE HANDLERS 178 | # --------------------------------------------------------------------- 179 | def market_handler(msg): 180 | """ Update our global Market data response dict 181 | """ 182 | resp = msg_to_dict(msg) 183 | g.market_resp[int(msg.tickerId)].append(resp.copy()) 184 | -------------------------------------------------------------------------------- /app/ib/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ## 5 | # IbPy package root. 6 | # 7 | ## 8 | 9 | # these values substituted during release build. 10 | api = "0" 11 | version = "0" 12 | revision = "r0" 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/ib/ext/AnyWrapper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ generated source for module AnyWrapper """ 3 | # 4 | # Original file copyright original author(s). 5 | # This file copyright Troy Melhase, troy@gci.net. 6 | # 7 | # WARNING: all changes to this file will be lost. 8 | 9 | from abc import ABCMeta, abstractmethod 10 | from ib.lib.overloading import overloaded 11 | # 12 | # * AnyWrapper.java 13 | # * 14 | # 15 | # package: com.ib.client 16 | class AnyWrapper(object): 17 | """ generated source for interface AnyWrapper """ 18 | __metaclass__ = ABCMeta 19 | @abstractmethod 20 | @overloaded 21 | def error(self, e): 22 | """ generated source for method error """ 23 | 24 | @abstractmethod 25 | @error.register(object, str) 26 | def error_0(self, strval): 27 | """ generated source for method error_0 """ 28 | 29 | @abstractmethod 30 | @error.register(object, int, int, str) 31 | def error_1(self, id, errorCode, errorMsg): 32 | """ generated source for method error_1 """ 33 | 34 | @abstractmethod 35 | def connectionClosed(self): 36 | """ generated source for method connectionClosed """ 37 | 38 | -------------------------------------------------------------------------------- /app/ib/ext/AnyWrapperMsgGenerator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ generated source for module AnyWrapperMsgGenerator """ 3 | # 4 | # Original file copyright original author(s). 5 | # This file copyright Troy Melhase, troy@gci.net. 6 | # 7 | # WARNING: all changes to this file will be lost. 8 | 9 | from ib.lib import classmethod_ as classmethod 10 | from ib.lib.overloading import overloaded 11 | # package: com.ib.client 12 | class AnyWrapperMsgGenerator(object): 13 | """ generated source for class AnyWrapperMsgGenerator """ 14 | @classmethod 15 | @overloaded 16 | def error(cls, ex): 17 | """ generated source for method error """ 18 | return "Error - " + ex 19 | 20 | @classmethod 21 | @error.register(object, str) 22 | def error_0(cls, strval): 23 | """ generated source for method error_0 """ 24 | return strval 25 | 26 | @classmethod 27 | @error.register(object, int, int, str) 28 | def error_1(cls, id, errorCode, errorMsg): 29 | """ generated source for method error_1 """ 30 | err = str(id) 31 | err += " | " 32 | err += str(errorCode) 33 | err += " | " 34 | err += errorMsg 35 | return err 36 | 37 | @classmethod 38 | def connectionClosed(cls): 39 | """ generated source for method connectionClosed """ 40 | return "Connection Closed" 41 | 42 | -------------------------------------------------------------------------------- /app/ib/ext/ComboLeg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ generated source for module ComboLeg """ 3 | # 4 | # Original file copyright original author(s). 5 | # This file copyright Troy Melhase, troy@gci.net. 6 | # 7 | # WARNING: all changes to this file will be lost. 8 | 9 | from ib.lib.overloading import overloaded 10 | from ib.ext.Util import Util 11 | # 12 | # * ComboLeg.java 13 | # * 14 | # 15 | # package: com.ib.client 16 | class ComboLeg(object): 17 | """ generated source for class ComboLeg """ 18 | SAME = 0 # open/close leg value is same as combo 19 | OPEN = 1 20 | CLOSE = 2 21 | UNKNOWN = 3 22 | 23 | m_conId = 0 24 | m_ratio = 0 25 | m_action = "" # BUY/SELL/SSHORT/SSHORTX 26 | m_exchange = "" 27 | m_openClose = 0 28 | 29 | # for stock legs when doing short sale 30 | m_shortSaleSlot = 0 # 1 = clearing broker, 2 = third party 31 | m_designatedLocation = "" 32 | m_exemptCode = 0 33 | 34 | @overloaded 35 | def __init__(self): 36 | """ generated source for method __init__ """ 37 | pass # self.__init__(0, 0, None, None, 0, 0, None, -1)# conId ratio action exchange openClose shortSaleSlot designatedLocation exemptCode 38 | 39 | @__init__.register(object, int, int, str, str, int) 40 | def __init___0(self, p_conId, p_ratio, p_action, p_exchange, p_openClose): 41 | """ generated source for method __init___0 """ 42 | pass # self.__init__(p_conId, p_ratio, p_action, p_exchange, p_openClose, 0, None, -1)# shortSaleSlot designatedLocation exemptCode 43 | 44 | @__init__.register(object, int, int, str, str, int, int, str) 45 | def __init___1(self, p_conId, p_ratio, p_action, p_exchange, p_openClose, p_shortSaleSlot, p_designatedLocation): 46 | """ generated source for method __init___1 """ 47 | pass #self.__init__(p_conId, p_ratio, p_action, p_exchange, p_openClose, p_shortSaleSlot, p_designatedLocation, -1)# exemptCode 48 | 49 | @__init__.register(object, int, int, str, str, int, int, str, int) 50 | def __init___2(self, p_conId, p_ratio, p_action, p_exchange, p_openClose, p_shortSaleSlot, p_designatedLocation, p_exemptCode): 51 | """ generated source for method __init___2 """ 52 | self.m_conId = p_conId 53 | self.m_ratio = p_ratio 54 | self.m_action = p_action 55 | self.m_exchange = p_exchange 56 | self.m_openClose = p_openClose 57 | self.m_shortSaleSlot = p_shortSaleSlot 58 | self.m_designatedLocation = p_designatedLocation 59 | self.m_exemptCode = p_exemptCode 60 | 61 | def __eq__(self, p_other): 62 | """ generated source for method equals """ 63 | if self is p_other: 64 | return True 65 | elif p_other is None: 66 | return False 67 | if (self.m_conId != p_other.m_conId) or (self.m_ratio != p_other.m_ratio) or (self.m_openClose != p_other.m_openClose) or (self.m_shortSaleSlot != p_other.m_shortSaleSlot) or (self.m_exemptCode != p_other.m_exemptCode): 68 | return False 69 | if (Util.StringCompareIgnCase(self.m_action, p_other.m_action) != 0) or (Util.StringCompareIgnCase(self.m_exchange, p_other.m_exchange) != 0) or (Util.StringCompareIgnCase(self.m_designatedLocation, p_other.m_designatedLocation) != 0): 70 | return False 71 | return True 72 | 73 | -------------------------------------------------------------------------------- /app/ib/ext/CommissionReport.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ generated source for module CommissionReport """ 3 | # 4 | # Original file copyright original author(s). 5 | # This file copyright Troy Melhase, troy@gci.net. 6 | # 7 | # WARNING: all changes to this file will be lost. 8 | 9 | # 10 | # * CommissionReport.java 11 | # * 12 | # 13 | # package: com.ib.client 14 | class CommissionReport(object): 15 | """ generated source for class CommissionReport """ 16 | m_execId = "" 17 | m_commission = float() 18 | m_currency = "" 19 | m_realizedPNL = float() 20 | m_yield = float() 21 | m_yieldRedemptionDate = 0 # YYYYMMDD format 22 | 23 | def __init__(self): 24 | """ generated source for method __init__ """ 25 | self.m_commission = 0 26 | self.m_realizedPNL = 0 27 | self.m_yield = 0 28 | self.m_yieldRedemptionDate = 0 29 | 30 | def __eq__(self, p_other): 31 | """ generated source for method equals """ 32 | l_bRetVal = False 33 | if p_other is None: 34 | l_bRetVal = False 35 | elif self is p_other: 36 | l_bRetVal = True 37 | else: 38 | l_bRetVal = self.m_execId == p_other.m_execId 39 | return l_bRetVal 40 | 41 | -------------------------------------------------------------------------------- /app/ib/ext/Contract.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ generated source for module Contract """ 3 | # 4 | # Original file copyright original author(s). 5 | # This file copyright Troy Melhase, troy@gci.net. 6 | # 7 | # WARNING: all changes to this file will be lost. 8 | 9 | from ib.lib.overloading import overloaded 10 | from ib.lib import Cloneable 11 | from ib.ext.Util import Util 12 | # 13 | # * Contract.java 14 | # * 15 | # 16 | # package: com.ib.client 17 | 18 | 19 | class Contract(Cloneable): 20 | """ generated source for class Contract """ 21 | m_conId = 0 22 | m_symbol = "" 23 | m_secType = "" 24 | m_expiry = "" 25 | m_strike = float() 26 | m_right = "" 27 | m_multiplier = "" 28 | m_exchange = "" 29 | 30 | m_currency = "" 31 | m_localSymbol = "" 32 | m_tradingClass = "" 33 | m_primaryExch = "" # pick a non-aggregate (ie not the SMART exchange) exchange that the contract trades on. DO NOT SET TO SMART. 34 | m_includeExpired = bool() # can not be set to true for orders. 35 | 36 | m_secIdType = "" # CUSIP;SEDOL;ISIN;RIC 37 | m_secId = "" 38 | 39 | # COMBOS 40 | m_comboLegsDescrip = "" # received in open order version 14 and up for all combos 41 | m_comboLegs = [] 42 | 43 | # delta neutral 44 | m_underComp = None 45 | 46 | @overloaded 47 | def __init__(self): 48 | """ generated source for method __init__ """ 49 | super(Contract, self).__init__() 50 | self.m_conId = 0 51 | self.m_strike = 0 52 | self.m_includeExpired = False 53 | 54 | def clone(self): 55 | """ generated source for method clone """ 56 | retval = super(Contract, self).clone() 57 | retval.m_comboLegs = self.m_comboLegs[:] 58 | return retval 59 | 60 | @__init__.register(object, int, str, str, str, float, str, str, str, str, str, str, list, str, bool, str, str) 61 | def __init___0(self, p_conId, p_symbol, p_secType, p_expiry, p_strike, p_right, p_multiplier, p_exchange, p_currency, p_localSymbol, p_tradingClass, p_comboLegs, p_primaryExch, p_includeExpired, p_secIdType, p_secId): 62 | """ generated source for method __init___0 """ 63 | super(Contract, self).__init__() 64 | self.m_conId = p_conId 65 | self.m_symbol = p_symbol 66 | self.m_secType = p_secType 67 | self.m_expiry = p_expiry 68 | self.m_strike = p_strike 69 | self.m_right = p_right 70 | self.m_multiplier = p_multiplier 71 | self.m_exchange = p_exchange 72 | self.m_currency = p_currency 73 | self.m_includeExpired = p_includeExpired 74 | self.m_localSymbol = p_localSymbol 75 | self.m_tradingClass = p_tradingClass 76 | self.m_comboLegs = p_comboLegs 77 | self.m_primaryExch = p_primaryExch 78 | self.m_secIdType = p_secIdType 79 | self.m_secId = p_secId 80 | 81 | def __eq__(self, p_other): 82 | """ generated source for method equals """ 83 | if self is p_other: 84 | return True 85 | if p_other is None or not (isinstance(p_other, (Contract, ))): 86 | return False 87 | l_theOther = p_other 88 | if self.m_conId != l_theOther.m_conId: 89 | return False 90 | if Util.StringCompare(self.m_secType, l_theOther.m_secType) != 0: 91 | return False 92 | if (Util.StringCompare(self.m_symbol, l_theOther.m_symbol) != 0) or (Util.StringCompare(self.m_exchange, l_theOther.m_exchange) != 0) or (Util.StringCompare(self.m_primaryExch, l_theOther.m_primaryExch) != 0) or (Util.StringCompare(self.m_currency, l_theOther.m_currency) != 0): 93 | return False 94 | if not Util.NormalizeString(self.m_secType) == "BOND": 95 | if self.m_strike != l_theOther.m_strike: 96 | return False 97 | if (Util.StringCompare(self.m_expiry, l_theOther.m_expiry) != 0) or (Util.StringCompare(self.m_right, l_theOther.m_right) != 0) or (Util.StringCompare(self.m_multiplier, l_theOther.m_multiplier) != 0) or (Util.StringCompare(self.m_localSymbol, l_theOther.m_localSymbol) != 0) or (Util.StringCompare(self.m_tradingClass, l_theOther.m_tradingClass) != 0): 98 | return False 99 | if Util.StringCompare(self.m_secIdType, l_theOther.m_secIdType) != 0: 100 | return False 101 | if Util.StringCompare(self.m_secId, l_theOther.m_secId) != 0: 102 | return False 103 | # compare combo legs 104 | if not Util.VectorEqualsUnordered(self.m_comboLegs, l_theOther.m_comboLegs): 105 | return False 106 | if self.m_underComp != l_theOther.m_underComp: 107 | if self.m_underComp is None or l_theOther.m_underComp is None: 108 | return False 109 | if not self.m_underComp == l_theOther.m_underComp: 110 | return False 111 | return True 112 | 113 | -------------------------------------------------------------------------------- /app/ib/ext/ContractDetails.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ generated source for module ContractDetails """ 3 | # 4 | # Original file copyright original author(s). 5 | # This file copyright Troy Melhase, troy@gci.net. 6 | # 7 | # WARNING: all changes to this file will be lost. 8 | 9 | from ib.lib.overloading import overloaded 10 | from ib.ext.Contract import Contract 11 | # 12 | # * ContractDetails.java 13 | # * 14 | # 15 | # package: com.ib.client 16 | 17 | 18 | class ContractDetails(object): 19 | """ generated source for class ContractDetails """ 20 | m_summary = None 21 | m_marketName = "" 22 | m_minTick = float() 23 | m_priceMagnifier = 0 24 | m_orderTypes = "" 25 | m_validExchanges = "" 26 | m_underConId = 0 27 | m_longName = "" 28 | m_contractMonth = "" 29 | m_industry = "" 30 | m_category = "" 31 | m_subcategory = "" 32 | m_timeZoneId = "" 33 | m_tradingHours = "" 34 | m_liquidHours = "" 35 | m_evRule = "" 36 | m_evMultiplier = float() 37 | 38 | m_secIdList = None # CUSIP/ISIN/etc. 39 | 40 | # BOND values 41 | m_cusip = "" 42 | m_ratings = "" 43 | m_descAppend = "" 44 | m_bondType = "" 45 | m_couponType = "" 46 | m_callable = False 47 | m_putable = False 48 | m_coupon = 0 49 | m_convertible = False 50 | m_maturity = "" 51 | m_issueDate = "" 52 | m_nextOptionDate = "" 53 | m_nextOptionType = "" 54 | m_nextOptionPartial = False 55 | m_notes = "" 56 | 57 | @overloaded 58 | def __init__(self): 59 | """ generated source for method __init__ """ 60 | self.m_summary = Contract() 61 | self.m_minTick = 0 62 | self.m_underConId = 0 63 | self.m_evMultiplier = 0 64 | 65 | @__init__.register(object, Contract, str, str, float, str, str, int, str, str, str, str, str, str, str, str, str, float) 66 | def __init___0(self, p_summary, p_marketName, p_minTick, p_orderTypes, p_validExchanges, p_underConId, p_longName, p_contractMonth, p_industry, p_category, p_subcategory, p_timeZoneId, p_tradingHours, p_liquidHours, p_evRule, p_evMultiplier): 67 | """ generated source for method __init___0 """ 68 | self.m_summary = p_summary 69 | self.m_marketName = p_marketName 70 | self.m_minTick = p_minTick 71 | self.m_orderTypes = p_orderTypes 72 | self.m_validExchanges = p_validExchanges 73 | self.m_underConId = p_underConId 74 | self.m_longName = p_longName 75 | self.m_contractMonth = p_contractMonth 76 | self.m_industry = p_industry 77 | self.m_category = p_category 78 | self.m_subcategory = p_subcategory 79 | self.m_timeZoneId = p_timeZoneId 80 | self.m_tradingHours = p_tradingHours 81 | self.m_liquidHours = p_liquidHours 82 | self.m_evRule = p_evRule 83 | self.m_evMultiplier = p_evMultiplier 84 | 85 | -------------------------------------------------------------------------------- /app/ib/ext/EClientErrors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ generated source for module EClientErrors """ 3 | # 4 | # Original file copyright original author(s). 5 | # This file copyright Troy Melhase, troy@gci.net. 6 | # 7 | # WARNING: all changes to this file will be lost. 8 | 9 | # 10 | # * EClientErrors.java 11 | # * 12 | # 13 | # package: com.ib.client 14 | class EClientErrors(object): 15 | """ generated source for class EClientErrors """ 16 | def __init__(self): 17 | """ generated source for method __init__ """ 18 | pass 19 | 20 | class CodeMsgPair(object): 21 | """ generated source for class CodeMsgPair """ 22 | # ///////////////////////////////////////////////////////////////// 23 | # Public members 24 | # ///////////////////////////////////////////////////////////////// 25 | m_errorCode = 0 26 | m_errorMsg = "" 27 | 28 | # ///////////////////////////////////////////////////////////////// 29 | # Get/Set methods 30 | # ///////////////////////////////////////////////////////////////// 31 | def code(self): 32 | """ generated source for method code """ 33 | return self.m_errorCode 34 | 35 | def msg(self): 36 | """ generated source for method msg """ 37 | return self.m_errorMsg 38 | 39 | # ///////////////////////////////////////////////////////////////// 40 | # Constructors 41 | # ///////////////////////////////////////////////////////////////// 42 | # 43 | # * 44 | # 45 | def __init__(self, i, errString): 46 | """ generated source for method __init__ """ 47 | self.m_errorCode = i 48 | self.m_errorMsg = errString 49 | 50 | NO_VALID_ID = -1 51 | 52 | NOT_CONNECTED = CodeMsgPair(504, "Not connected") 53 | UPDATE_TWS = CodeMsgPair(503, "The TWS is out of date and must be upgraded.") 54 | ALREADY_CONNECTED = CodeMsgPair(501, "Already connected.") 55 | CONNECT_FAIL = CodeMsgPair(502, "Couldn't connect to TWS. Confirm that \"Enable ActiveX and Socket Clients\" is enabled on the TWS \"Configure->API\" menu.") 56 | FAIL_SEND = CodeMsgPair(509, "Failed to send message - ") 57 | UNKNOWN_ID = CodeMsgPair(505, "Fatal Error: Unknown message id.") 58 | FAIL_SEND_REQMKT = CodeMsgPair(510, "Request Market Data Sending Error - ") 59 | FAIL_SEND_CANMKT = CodeMsgPair(511, "Cancel Market Data Sending Error - ") 60 | FAIL_SEND_ORDER = CodeMsgPair(512, "Order Sending Error - ") 61 | FAIL_SEND_ACCT = CodeMsgPair(513, "Account Update Request Sending Error -") 62 | FAIL_SEND_EXEC = CodeMsgPair(514, "Request For Executions Sending Error -") 63 | FAIL_SEND_CORDER = CodeMsgPair(515, "Cancel Order Sending Error -") 64 | FAIL_SEND_OORDER = CodeMsgPair(516, "Request Open Order Sending Error -") 65 | UNKNOWN_CONTRACT = CodeMsgPair(517, "Unknown contract. Verify the contract details supplied.") 66 | FAIL_SEND_REQCONTRACT = CodeMsgPair(518, "Request Contract Data Sending Error - ") 67 | FAIL_SEND_REQMKTDEPTH = CodeMsgPair(519, "Request Market Depth Sending Error - ") 68 | FAIL_SEND_CANMKTDEPTH = CodeMsgPair(520, "Cancel Market Depth Sending Error - ") 69 | FAIL_SEND_SERVER_LOG_LEVEL = CodeMsgPair(521, "Set Server Log Level Sending Error - ") 70 | FAIL_SEND_FA_REQUEST = CodeMsgPair(522, "FA Information Request Sending Error - ") 71 | FAIL_SEND_FA_REPLACE = CodeMsgPair(523, "FA Information Replace Sending Error - ") 72 | FAIL_SEND_REQSCANNER = CodeMsgPair(524, "Request Scanner Subscription Sending Error - ") 73 | FAIL_SEND_CANSCANNER = CodeMsgPair(525, "Cancel Scanner Subscription Sending Error - ") 74 | FAIL_SEND_REQSCANNERPARAMETERS = CodeMsgPair(526, "Request Scanner Parameter Sending Error - ") 75 | FAIL_SEND_REQHISTDATA = CodeMsgPair(527, "Request Historical Data Sending Error - ") 76 | FAIL_SEND_CANHISTDATA = CodeMsgPair(528, "Request Historical Data Sending Error - ") 77 | FAIL_SEND_REQRTBARS = CodeMsgPair(529, "Request Real-time Bar Data Sending Error - ") 78 | FAIL_SEND_CANRTBARS = CodeMsgPair(530, "Cancel Real-time Bar Data Sending Error - ") 79 | FAIL_SEND_REQCURRTIME = CodeMsgPair(531, "Request Current Time Sending Error - ") 80 | FAIL_SEND_REQFUNDDATA = CodeMsgPair(532, "Request Fundamental Data Sending Error - ") 81 | FAIL_SEND_CANFUNDDATA = CodeMsgPair(533, "Cancel Fundamental Data Sending Error - ") 82 | FAIL_SEND_REQCALCIMPLIEDVOLAT = CodeMsgPair(534, "Request Calculate Implied Volatility Sending Error - ") 83 | FAIL_SEND_REQCALCOPTIONPRICE = CodeMsgPair(535, "Request Calculate Option Price Sending Error - ") 84 | FAIL_SEND_CANCALCIMPLIEDVOLAT = CodeMsgPair(536, "Cancel Calculate Implied Volatility Sending Error - ") 85 | FAIL_SEND_CANCALCOPTIONPRICE = CodeMsgPair(537, "Cancel Calculate Option Price Sending Error - ") 86 | FAIL_SEND_REQGLOBALCANCEL = CodeMsgPair(538, "Request Global Cancel Sending Error - ") 87 | FAIL_SEND_REQMARKETDATATYPE = CodeMsgPair(539, "Request Market Data Type Sending Error - ") 88 | FAIL_SEND_REQPOSITIONS = CodeMsgPair(540, "Request Positions Sending Error - ") 89 | FAIL_SEND_CANPOSITIONS = CodeMsgPair(541, "Cancel Positions Sending Error - ") 90 | FAIL_SEND_REQACCOUNTDATA = CodeMsgPair(542, "Request Account Data Sending Error - ") 91 | FAIL_SEND_CANACCOUNTDATA = CodeMsgPair(543, "Cancel Account Data Sending Error - ") 92 | -------------------------------------------------------------------------------- /app/ib/ext/EWrapper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ generated source for module EWrapper """ 3 | # 4 | # Original file copyright original author(s). 5 | # This file copyright Troy Melhase, troy@gci.net. 6 | # 7 | # WARNING: all changes to this file will be lost. 8 | from abc import ABCMeta, abstractmethod 9 | 10 | from ib.ext.AnyWrapper import AnyWrapper 11 | # 12 | # * EWrapper.java 13 | # * 14 | # 15 | # package: com.ib.client 16 | class EWrapper(AnyWrapper): 17 | """ generated source for interface EWrapper """ 18 | __metaclass__ = ABCMeta 19 | # ///////////////////////////////////////////////////////////////////// 20 | # Interface methods 21 | # ///////////////////////////////////////////////////////////////////// 22 | @abstractmethod 23 | def tickPrice(self, tickerId, field, price, canAutoExecute): 24 | """ generated source for method tickPrice """ 25 | 26 | @abstractmethod 27 | def tickSize(self, tickerId, field, size): 28 | """ generated source for method tickSize """ 29 | 30 | @abstractmethod 31 | def tickOptionComputation(self, tickerId, field, impliedVol, delta, optPrice, pvDividend, gamma, vega, theta, undPrice): 32 | """ generated source for method tickOptionComputation """ 33 | 34 | @abstractmethod 35 | def tickGeneric(self, tickerId, tickType, value): 36 | """ generated source for method tickGeneric """ 37 | 38 | @abstractmethod 39 | def tickString(self, tickerId, tickType, value): 40 | """ generated source for method tickString """ 41 | 42 | @abstractmethod 43 | def tickEFP(self, tickerId, tickType, basisPoints, formattedBasisPoints, impliedFuture, holdDays, futureExpiry, dividendImpact, dividendsToExpiry): 44 | """ generated source for method tickEFP """ 45 | 46 | @abstractmethod 47 | def orderStatus(self, orderId, status, filled, remaining, avgFillPrice, permId, parentId, lastFillPrice, clientId, whyHeld): 48 | """ generated source for method orderStatus """ 49 | 50 | @abstractmethod 51 | def openOrder(self, orderId, contract, order, orderState): 52 | """ generated source for method openOrder """ 53 | 54 | @abstractmethod 55 | def openOrderEnd(self): 56 | """ generated source for method openOrderEnd """ 57 | 58 | @abstractmethod 59 | def updateAccountValue(self, key, value, currency, accountName): 60 | """ generated source for method updateAccountValue """ 61 | 62 | @abstractmethod 63 | def updatePortfolio(self, contract, position, marketPrice, marketValue, averageCost, unrealizedPNL, realizedPNL, accountName): 64 | """ generated source for method updatePortfolio """ 65 | 66 | @abstractmethod 67 | def updateAccountTime(self, timeStamp): 68 | """ generated source for method updateAccountTime """ 69 | 70 | @abstractmethod 71 | def accountDownloadEnd(self, accountName): 72 | """ generated source for method accountDownloadEnd """ 73 | 74 | @abstractmethod 75 | def nextValidId(self, orderId): 76 | """ generated source for method nextValidId """ 77 | 78 | @abstractmethod 79 | def contractDetails(self, reqId, contractDetails): 80 | """ generated source for method contractDetails """ 81 | 82 | @abstractmethod 83 | def bondContractDetails(self, reqId, contractDetails): 84 | """ generated source for method bondContractDetails """ 85 | 86 | @abstractmethod 87 | def contractDetailsEnd(self, reqId): 88 | """ generated source for method contractDetailsEnd """ 89 | 90 | @abstractmethod 91 | def execDetails(self, reqId, contract, execution): 92 | """ generated source for method execDetails """ 93 | 94 | @abstractmethod 95 | def execDetailsEnd(self, reqId): 96 | """ generated source for method execDetailsEnd """ 97 | 98 | @abstractmethod 99 | def updateMktDepth(self, tickerId, position, operation, side, price, size): 100 | """ generated source for method updateMktDepth """ 101 | 102 | @abstractmethod 103 | def updateMktDepthL2(self, tickerId, position, marketMaker, operation, side, price, size): 104 | """ generated source for method updateMktDepthL2 """ 105 | 106 | @abstractmethod 107 | def updateNewsBulletin(self, msgId, msgType, message, origExchange): 108 | """ generated source for method updateNewsBulletin """ 109 | 110 | @abstractmethod 111 | def managedAccounts(self, accountsList): 112 | """ generated source for method managedAccounts """ 113 | 114 | @abstractmethod 115 | def receiveFA(self, faDataType, xml): 116 | """ generated source for method receiveFA """ 117 | 118 | @abstractmethod 119 | def historicalData(self, reqId, date, open, high, low, close, volume, count, WAP, hasGaps): 120 | """ generated source for method historicalData """ 121 | 122 | @abstractmethod 123 | def scannerParameters(self, xml): 124 | """ generated source for method scannerParameters """ 125 | 126 | @abstractmethod 127 | def scannerData(self, reqId, rank, contractDetails, distance, benchmark, projection, legsStr): 128 | """ generated source for method scannerData """ 129 | 130 | @abstractmethod 131 | def scannerDataEnd(self, reqId): 132 | """ generated source for method scannerDataEnd """ 133 | 134 | @abstractmethod 135 | def realtimeBar(self, reqId, time, open, high, low, close, volume, wap, count): 136 | """ generated source for method realtimeBar """ 137 | 138 | @abstractmethod 139 | def currentTime(self, time): 140 | """ generated source for method currentTime """ 141 | 142 | @abstractmethod 143 | def fundamentalData(self, reqId, data): 144 | """ generated source for method fundamentalData """ 145 | 146 | @abstractmethod 147 | def deltaNeutralValidation(self, reqId, underComp): 148 | """ generated source for method deltaNeutralValidation """ 149 | 150 | @abstractmethod 151 | def tickSnapshotEnd(self, reqId): 152 | """ generated source for method tickSnapshotEnd """ 153 | 154 | @abstractmethod 155 | def marketDataType(self, reqId, marketDataType): 156 | """ generated source for method marketDataType """ 157 | 158 | @abstractmethod 159 | def commissionReport(self, commissionReport): 160 | """ generated source for method commissionReport """ 161 | 162 | @abstractmethod 163 | def position(self, account, contract, pos, avgCost): 164 | """ generated source for method position """ 165 | 166 | @abstractmethod 167 | def positionEnd(self): 168 | """ generated source for method positionEnd """ 169 | 170 | @abstractmethod 171 | def accountSummary(self, reqId, account, tag, value, currency): 172 | """ generated source for method accountSummary """ 173 | 174 | @abstractmethod 175 | def accountSummaryEnd(self, reqId): 176 | """ generated source for method accountSummaryEnd """ 177 | -------------------------------------------------------------------------------- /app/ib/ext/Execution.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ generated source for module Execution """ 3 | # 4 | # Original file copyright original author(s). 5 | # This file copyright Troy Melhase, troy@gci.net. 6 | # 7 | # WARNING: all changes to this file will be lost. 8 | 9 | from ib.lib.overloading import overloaded 10 | # 11 | # * Execution.java 12 | # * 13 | # 14 | # package: com.ib.client 15 | class Execution(object): 16 | """ generated source for class Execution """ 17 | m_orderId = 0 18 | m_clientId = 0 19 | m_execId = "" 20 | m_time = "" 21 | m_acctNumber = "" 22 | m_exchange = "" 23 | m_side = "" 24 | m_shares = 0 25 | m_price = float() 26 | m_permId = 0 27 | m_liquidation = 0 28 | m_cumQty = 0 29 | m_avgPrice = float() 30 | m_orderRef = "" 31 | m_evRule = "" 32 | m_evMultiplier = float() 33 | 34 | @overloaded 35 | def __init__(self): 36 | """ generated source for method __init__ """ 37 | self.m_orderId = 0 38 | self.m_clientId = 0 39 | self.m_shares = 0 40 | self.m_price = 0 41 | self.m_permId = 0 42 | self.m_liquidation = 0 43 | self.m_cumQty = 0 44 | self.m_avgPrice = 0 45 | self.m_evMultiplier = 0 46 | 47 | @__init__.register(object, int, int, str, str, str, str, str, int, float, int, int, int, float, str, str, float) 48 | def __init___0(self, p_orderId, p_clientId, p_execId, p_time, p_acctNumber, p_exchange, p_side, p_shares, p_price, p_permId, p_liquidation, p_cumQty, p_avgPrice, p_orderRef, p_evRule, p_evMultiplier): 49 | """ generated source for method __init___0 """ 50 | self.m_orderId = p_orderId 51 | self.m_clientId = p_clientId 52 | self.m_execId = p_execId 53 | self.m_time = p_time 54 | self.m_acctNumber = p_acctNumber 55 | self.m_exchange = p_exchange 56 | self.m_side = p_side 57 | self.m_shares = p_shares 58 | self.m_price = p_price 59 | self.m_permId = p_permId 60 | self.m_liquidation = p_liquidation 61 | self.m_cumQty = p_cumQty 62 | self.m_avgPrice = p_avgPrice 63 | self.m_orderRef = p_orderRef 64 | self.m_evRule = p_evRule 65 | self.m_evMultiplier = p_evMultiplier 66 | 67 | def __eq__(self, p_other): 68 | """ generated source for method equals """ 69 | l_bRetVal = False 70 | if p_other is None: 71 | l_bRetVal = False 72 | elif self is p_other: 73 | l_bRetVal = True 74 | else: 75 | l_theOther = p_other 76 | l_bRetVal = self.m_execId == l_theOther.m_execId 77 | return l_bRetVal 78 | 79 | -------------------------------------------------------------------------------- /app/ib/ext/ExecutionFilter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ generated source for module ExecutionFilter """ 3 | # 4 | # Original file copyright original author(s). 5 | # This file copyright Troy Melhase, troy@gci.net. 6 | # 7 | # WARNING: all changes to this file will be lost. 8 | 9 | from ib.lib.overloading import overloaded 10 | # 11 | # * ExecutionFilter.java 12 | # * 13 | # 14 | # package: com.ib.client 15 | class ExecutionFilter(object): 16 | """ generated source for class ExecutionFilter """ 17 | m_clientId = 0 # zero means no filtering on this field 18 | m_acctCode = "" 19 | m_time = "" 20 | m_symbol = "" 21 | m_secType = "" 22 | m_exchange = "" 23 | m_side = "" 24 | 25 | @overloaded 26 | def __init__(self): 27 | """ generated source for method __init__ """ 28 | self.m_clientId = 0 29 | 30 | @__init__.register(object, int, str, str, str, str, str, str) 31 | def __init___0(self, p_clientId, p_acctCode, p_time, p_symbol, p_secType, p_exchange, p_side): 32 | """ generated source for method __init___0 """ 33 | self.m_clientId = p_clientId 34 | self.m_acctCode = p_acctCode 35 | self.m_time = p_time 36 | self.m_symbol = p_symbol 37 | self.m_secType = p_secType 38 | self.m_exchange = p_exchange 39 | self.m_side = p_side 40 | 41 | def __eq__(self, p_other): 42 | """ generated source for method equals """ 43 | l_bRetVal = False 44 | if p_other is None: 45 | l_bRetVal = False 46 | elif self is p_other: 47 | l_bRetVal = True 48 | else: 49 | l_theOther = p_other 50 | l_bRetVal = (self.m_clientId == l_theOther.m_clientId and self.m_acctCode.lower() == l_theOther.m_acctCode.lower() and self.m_time.lower() == l_theOther.m_time.lower() and self.m_symbol.lower() == l_theOther.m_symbol.lower() and self.m_secType.lower() == l_theOther.m_secType.lower() and self.m_exchange.lower() == l_theOther.m_exchange.lower() and self.m_side.lower() == l_theOther.m_side.lower()) 51 | return l_bRetVal 52 | 53 | -------------------------------------------------------------------------------- /app/ib/ext/MarketDataType.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ generated source for module MarketDataType """ 3 | # 4 | # Original file copyright original author(s). 5 | # This file copyright Troy Melhase, troy@gci.net. 6 | # 7 | # WARNING: all changes to this file will be lost. 8 | 9 | # 10 | # * MarketDataType.java 11 | # * 12 | # 13 | # package: com.ib.client 14 | class MarketDataType(object): 15 | """ generated source for class MarketDataType """ 16 | # constants - market data types 17 | REALTIME = 1 18 | FROZEN = 2 19 | 20 | @classmethod 21 | def getField(cls, marketDataType): 22 | """ generated source for method getField """ 23 | if marketDataType==cls.REALTIME: 24 | return "Real-Time" 25 | elif marketDataType==cls.FROZEN: 26 | return "Frozen" 27 | else: 28 | return "Unknown" 29 | 30 | @classmethod 31 | def getFields(cls): 32 | """ generated source for method getFields """ 33 | totalFields = 2 34 | fields = [None]*totalFields 35 | i = 0 36 | while i < totalFields: 37 | fields[i] = MarketDataType.getField(i + 1) 38 | i += 1 39 | return fields 40 | 41 | -------------------------------------------------------------------------------- /app/ib/ext/Order.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ generated source for module Order """ 3 | # 4 | # Original file copyright original author(s). 5 | # This file copyright Troy Melhase, troy@gci.net. 6 | # 7 | # WARNING: all changes to this file will be lost. 8 | 9 | from ib.lib import Double, Integer 10 | from ib.ext.Util import Util 11 | # 12 | # * Order.java 13 | # * 14 | # 15 | # package: com.ib.client 16 | 17 | 18 | class Order(object): 19 | """ generated source for class Order """ 20 | CUSTOMER = 0 21 | FIRM = 1 22 | OPT_UNKNOWN = '?' 23 | OPT_BROKER_DEALER = 'b' 24 | OPT_CUSTOMER = 'c' 25 | OPT_FIRM = 'f' 26 | OPT_ISEMM = 'm' 27 | OPT_FARMM = 'n' 28 | OPT_SPECIALIST = 'y' 29 | AUCTION_MATCH = 1 30 | AUCTION_IMPROVEMENT = 2 31 | AUCTION_TRANSPARENT = 3 32 | EMPTY_STR = "" 33 | 34 | # main order fields 35 | m_orderId = 0 36 | m_clientId = 0 37 | m_permId = 0 38 | m_action = "" 39 | m_totalQuantity = 0 40 | m_orderType = "" 41 | m_lmtPrice = float() 42 | m_auxPrice = float() 43 | 44 | # extended order fields 45 | m_tif = "" # "Time in Force" - DAY, GTC, etc. 46 | m_activeStartTime = "" # GTC orders 47 | m_activeStopTime = "" # GTC orders 48 | m_ocaGroup = "" # one cancels all group name 49 | m_ocaType = 0 # 1 = CANCEL_WITH_BLOCK, 2 = REDUCE_WITH_BLOCK, 3 = REDUCE_NON_BLOCK 50 | m_orderRef = "" 51 | m_transmit = bool() # if false, order will be created but not transmited 52 | m_parentId = 0 # Parent order Id, to associate Auto STP or TRAIL orders with the original order. 53 | m_blockOrder = bool() 54 | m_sweepToFill = bool() 55 | m_displaySize = 0 56 | m_triggerMethod = 0 # 0=Default, 1=Double_Bid_Ask, 2=Last, 3=Double_Last, 4=Bid_Ask, 7=Last_or_Bid_Ask, 8=Mid-point 57 | m_outsideRth = bool() 58 | m_hidden = bool() 59 | m_goodAfterTime = "" # FORMAT: 20060505 08:00:00 {time zone} 60 | m_goodTillDate = "" # FORMAT: 20060505 08:00:00 {time zone} 61 | m_overridePercentageConstraints = bool() 62 | m_rule80A = "" # Individual = 'I', Agency = 'A', AgentOtherMember = 'W', IndividualPTIA = 'J', AgencyPTIA = 'U', AgentOtherMemberPTIA = 'M', IndividualPT = 'K', AgencyPT = 'Y', AgentOtherMemberPT = 'N' 63 | m_allOrNone = bool() 64 | m_minQty = 0 65 | m_percentOffset = float() # REL orders only; specify the decimal, e.g. .04 not 4 66 | m_trailStopPrice = float() # for TRAILLIMIT orders only 67 | m_trailingPercent = float() # specify the percentage, e.g. 3, not .03 68 | 69 | # Financial advisors only 70 | m_faGroup = "" 71 | m_faProfile = "" 72 | m_faMethod = "" 73 | m_faPercentage = "" 74 | 75 | # Institutional orders only 76 | m_openClose = "" # O=Open, C=Close 77 | m_origin = 0 # 0=Customer, 1=Firm 78 | m_shortSaleSlot = 0 # 1 if you hold the shares, 2 if they will be delivered from elsewhere. Only for Action="SSHORT 79 | m_designatedLocation = "" # set when slot=2 only. 80 | m_exemptCode = 0 81 | 82 | # SMART routing only 83 | m_discretionaryAmt = float() 84 | m_eTradeOnly = bool() 85 | m_firmQuoteOnly = bool() 86 | m_nbboPriceCap = float() 87 | m_optOutSmartRouting = bool() 88 | 89 | # BOX or VOL ORDERS ONLY 90 | m_auctionStrategy = 0 # 1=AUCTION_MATCH, 2=AUCTION_IMPROVEMENT, 3=AUCTION_TRANSPARENT 91 | 92 | 93 | # BOX ORDERS ONLY 94 | m_startingPrice = float() 95 | m_stockRefPrice = float() 96 | m_delta = float() 97 | 98 | # pegged to stock or VOL orders 99 | m_stockRangeLower = float() 100 | m_stockRangeUpper = float() 101 | 102 | # VOLATILITY ORDERS ONLY 103 | m_volatility = float() # enter percentage not decimal, e.g. 2 not .02 104 | m_volatilityType = 0 # 1=daily, 2=annual 105 | m_continuousUpdate = 0 106 | m_referencePriceType = 0 # 1=Bid/Ask midpoint, 2 = BidOrAsk 107 | m_deltaNeutralOrderType = "" 108 | m_deltaNeutralAuxPrice = float() 109 | m_deltaNeutralConId = 0 110 | m_deltaNeutralSettlingFirm = "" 111 | m_deltaNeutralClearingAccount = "" 112 | m_deltaNeutralClearingIntent = "" 113 | m_deltaNeutralOpenClose = "" 114 | m_deltaNeutralShortSale = bool() 115 | m_deltaNeutralShortSaleSlot = 0 116 | m_deltaNeutralDesignatedLocation = "" 117 | 118 | # COMBO ORDERS ONLY 119 | m_basisPoints = float() # EFP orders only, download only 120 | m_basisPointsType = 0 # EFP orders only, download only 121 | 122 | # SCALE ORDERS ONLY 123 | m_scaleInitLevelSize = 0 124 | m_scaleSubsLevelSize = 0 125 | m_scalePriceIncrement = float() 126 | m_scalePriceAdjustValue = float() 127 | m_scalePriceAdjustInterval = 0 128 | m_scaleProfitOffset = float() 129 | m_scaleAutoReset = bool() 130 | m_scaleInitPosition = 0 131 | m_scaleInitFillQty = 0 132 | m_scaleRandomPercent = bool() 133 | m_scaleTable = "" 134 | 135 | # HEDGE ORDERS ONLY 136 | m_hedgeType = "" # 'D' - delta, 'B' - beta, 'F' - FX, 'P' - pair 137 | m_hedgeParam = "" # beta value for beta hedge (in range 0-1), ratio for pair hedge 138 | 139 | # Clearing info 140 | m_account = "" # IB account 141 | m_settlingFirm = "" 142 | m_clearingAccount = "" # True beneficiary of the order 143 | m_clearingIntent = "" # "" (Default), "IB", "Away", "PTA" (PostTrade) 144 | 145 | # ALGO ORDERS ONLY 146 | m_algoStrategy = "" 147 | m_algoParams = None 148 | 149 | # What-if 150 | m_whatIf = bool() 151 | 152 | # Not Held 153 | m_notHeld = bool() 154 | 155 | # Smart combo routing params 156 | m_smartComboRoutingParams = None 157 | 158 | # order combo legs 159 | m_orderComboLegs = [] 160 | 161 | def __init__(self): 162 | """ generated source for method __init__ """ 163 | self.m_lmtPrice = Double.MAX_VALUE 164 | self.m_auxPrice = Double.MAX_VALUE 165 | self.m_activeStartTime = self.EMPTY_STR 166 | self.m_activeStopTime = self.EMPTY_STR 167 | self.m_outsideRth = False 168 | self.m_openClose = "O" 169 | self.m_origin = self.CUSTOMER 170 | self.m_transmit = True 171 | self.m_designatedLocation = self.EMPTY_STR 172 | self.m_exemptCode = -1 173 | self.m_minQty = Integer.MAX_VALUE 174 | self.m_percentOffset = Double.MAX_VALUE 175 | self.m_nbboPriceCap = Double.MAX_VALUE 176 | self.m_optOutSmartRouting = False 177 | self.m_startingPrice = Double.MAX_VALUE 178 | self.m_stockRefPrice = Double.MAX_VALUE 179 | self.m_delta = Double.MAX_VALUE 180 | self.m_stockRangeLower = Double.MAX_VALUE 181 | self.m_stockRangeUpper = Double.MAX_VALUE 182 | self.m_volatility = Double.MAX_VALUE 183 | self.m_volatilityType = Integer.MAX_VALUE 184 | self.m_deltaNeutralOrderType = self.EMPTY_STR 185 | self.m_deltaNeutralAuxPrice = Double.MAX_VALUE 186 | self.m_deltaNeutralConId = 0 187 | self.m_deltaNeutralSettlingFirm = self.EMPTY_STR 188 | self.m_deltaNeutralClearingAccount = self.EMPTY_STR 189 | self.m_deltaNeutralClearingIntent = self.EMPTY_STR 190 | self.m_deltaNeutralOpenClose = self.EMPTY_STR 191 | self.m_deltaNeutralShortSale = False 192 | self.m_deltaNeutralShortSaleSlot = 0 193 | self.m_deltaNeutralDesignatedLocation = self.EMPTY_STR 194 | self.m_referencePriceType = Integer.MAX_VALUE 195 | self.m_trailStopPrice = Double.MAX_VALUE 196 | self.m_trailingPercent = Double.MAX_VALUE 197 | self.m_basisPoints = Double.MAX_VALUE 198 | self.m_basisPointsType = Integer.MAX_VALUE 199 | self.m_scaleInitLevelSize = Integer.MAX_VALUE 200 | self.m_scaleSubsLevelSize = Integer.MAX_VALUE 201 | self.m_scalePriceIncrement = Double.MAX_VALUE 202 | self.m_scalePriceAdjustValue = Double.MAX_VALUE 203 | self.m_scalePriceAdjustInterval = Integer.MAX_VALUE 204 | self.m_scaleProfitOffset = Double.MAX_VALUE 205 | self.m_scaleAutoReset = False 206 | self.m_scaleInitPosition = Integer.MAX_VALUE 207 | self.m_scaleInitFillQty = Integer.MAX_VALUE 208 | self.m_scaleRandomPercent = False 209 | self.m_scaleTable = self.EMPTY_STR 210 | self.m_whatIf = False 211 | self.m_notHeld = False 212 | 213 | def __eq__(self, p_other): 214 | """ generated source for method equals """ 215 | if self is p_other: 216 | return True 217 | if p_other is None: 218 | return False 219 | l_theOther = p_other 220 | if self.m_permId == l_theOther.m_permId: 221 | return True 222 | if self.m_orderId != l_theOther.m_orderId or self.m_clientId != l_theOther.m_clientId or self.m_totalQuantity != l_theOther.m_totalQuantity or self.m_lmtPrice != l_theOther.m_lmtPrice or self.m_auxPrice != l_theOther.m_auxPrice or self.m_ocaType != l_theOther.m_ocaType or self.m_transmit != l_theOther.m_transmit or self.m_parentId != l_theOther.m_parentId or self.m_blockOrder != l_theOther.m_blockOrder or self.m_sweepToFill != l_theOther.m_sweepToFill or self.m_displaySize != l_theOther.m_displaySize or self.m_triggerMethod != l_theOther.m_triggerMethod or self.m_outsideRth != l_theOther.m_outsideRth or self.m_hidden != l_theOther.m_hidden or self.m_overridePercentageConstraints != l_theOther.m_overridePercentageConstraints or self.m_allOrNone != l_theOther.m_allOrNone or self.m_minQty != l_theOther.m_minQty or self.m_percentOffset != l_theOther.m_percentOffset or self.m_trailStopPrice != l_theOther.m_trailStopPrice or self.m_trailingPercent != l_theOther.m_trailingPercent or self.m_origin != l_theOther.m_origin or self.m_shortSaleSlot != l_theOther.m_shortSaleSlot or self.m_discretionaryAmt != l_theOther.m_discretionaryAmt or self.m_eTradeOnly != l_theOther.m_eTradeOnly or self.m_firmQuoteOnly != l_theOther.m_firmQuoteOnly or self.m_nbboPriceCap != l_theOther.m_nbboPriceCap or self.m_optOutSmartRouting != l_theOther.m_optOutSmartRouting or self.m_auctionStrategy != l_theOther.m_auctionStrategy or self.m_startingPrice != l_theOther.m_startingPrice or self.m_stockRefPrice != l_theOther.m_stockRefPrice or self.m_delta != l_theOther.m_delta or self.m_stockRangeLower != l_theOther.m_stockRangeLower or self.m_stockRangeUpper != l_theOther.m_stockRangeUpper or self.m_volatility != l_theOther.m_volatility or self.m_volatilityType != l_theOther.m_volatilityType or self.m_continuousUpdate != l_theOther.m_continuousUpdate or self.m_referencePriceType != l_theOther.m_referencePriceType or self.m_deltaNeutralAuxPrice != l_theOther.m_deltaNeutralAuxPrice or self.m_deltaNeutralConId != l_theOther.m_deltaNeutralConId or self.m_deltaNeutralShortSale != l_theOther.m_deltaNeutralShortSale or self.m_deltaNeutralShortSaleSlot != l_theOther.m_deltaNeutralShortSaleSlot or self.m_basisPoints != l_theOther.m_basisPoints or self.m_basisPointsType != l_theOther.m_basisPointsType or self.m_scaleInitLevelSize != l_theOther.m_scaleInitLevelSize or self.m_scaleSubsLevelSize != l_theOther.m_scaleSubsLevelSize or self.m_scalePriceIncrement != l_theOther.m_scalePriceIncrement or self.m_scalePriceAdjustValue != l_theOther.m_scalePriceAdjustValue or self.m_scalePriceAdjustInterval != l_theOther.m_scalePriceAdjustInterval or self.m_scaleProfitOffset != l_theOther.m_scaleProfitOffset or self.m_scaleAutoReset != l_theOther.m_scaleAutoReset or self.m_scaleInitPosition != l_theOther.m_scaleInitPosition or self.m_scaleInitFillQty != l_theOther.m_scaleInitFillQty or self.m_scaleRandomPercent != l_theOther.m_scaleRandomPercent or self.m_whatIf != l_theOther.m_whatIf or self.m_notHeld != l_theOther.m_notHeld or self.m_exemptCode != l_theOther.m_exemptCode: 223 | return False 224 | if Util.StringCompare(self.m_action, l_theOther.m_action) != 0 or Util.StringCompare(self.m_orderType, l_theOther.m_orderType) != 0 or Util.StringCompare(self.m_tif, l_theOther.m_tif) != 0 or Util.StringCompare(self.m_activeStartTime, l_theOther.m_activeStartTime) != 0 or Util.StringCompare(self.m_activeStopTime, l_theOther.m_activeStopTime) != 0 or Util.StringCompare(self.m_ocaGroup, l_theOther.m_ocaGroup) != 0 or Util.StringCompare(self.m_orderRef, l_theOther.m_orderRef) != 0 or Util.StringCompare(self.m_goodAfterTime, l_theOther.m_goodAfterTime) != 0 or Util.StringCompare(self.m_goodTillDate, l_theOther.m_goodTillDate) != 0 or Util.StringCompare(self.m_rule80A, l_theOther.m_rule80A) != 0 or Util.StringCompare(self.m_faGroup, l_theOther.m_faGroup) != 0 or Util.StringCompare(self.m_faProfile, l_theOther.m_faProfile) != 0 or Util.StringCompare(self.m_faMethod, l_theOther.m_faMethod) != 0 or Util.StringCompare(self.m_faPercentage, l_theOther.m_faPercentage) != 0 or Util.StringCompare(self.m_openClose, l_theOther.m_openClose) != 0 or Util.StringCompare(self.m_designatedLocation, l_theOther.m_designatedLocation) != 0 or Util.StringCompare(self.m_deltaNeutralOrderType, l_theOther.m_deltaNeutralOrderType) != 0 or Util.StringCompare(self.m_deltaNeutralSettlingFirm, l_theOther.m_deltaNeutralSettlingFirm) != 0 or Util.StringCompare(self.m_deltaNeutralClearingAccount, l_theOther.m_deltaNeutralClearingAccount) != 0 or Util.StringCompare(self.m_deltaNeutralClearingIntent, l_theOther.m_deltaNeutralClearingIntent) != 0 or Util.StringCompare(self.m_deltaNeutralOpenClose, l_theOther.m_deltaNeutralOpenClose) != 0 or Util.StringCompare(self.m_deltaNeutralDesignatedLocation, l_theOther.m_deltaNeutralDesignatedLocation) != 0 or Util.StringCompare(self.m_hedgeType, l_theOther.m_hedgeType) != 0 or Util.StringCompare(self.m_hedgeParam, l_theOther.m_hedgeParam) != 0 or Util.StringCompare(self.m_account, l_theOther.m_account) != 0 or Util.StringCompare(self.m_settlingFirm, l_theOther.m_settlingFirm) != 0 or Util.StringCompare(self.m_clearingAccount, l_theOther.m_clearingAccount) != 0 or Util.StringCompare(self.m_clearingIntent, l_theOther.m_clearingIntent) != 0 or Util.StringCompare(self.m_algoStrategy, l_theOther.m_algoStrategy) != 0 or Util.StringCompare(self.m_scaleTable, l_theOther.m_scaleTable) != 0: 225 | return False 226 | if not Util.VectorEqualsUnordered(self.m_algoParams, l_theOther.m_algoParams): 227 | return False 228 | if not Util.VectorEqualsUnordered(self.m_smartComboRoutingParams, l_theOther.m_smartComboRoutingParams): 229 | return False 230 | # compare order combo legs 231 | if not Util.VectorEqualsUnordered(self.m_orderComboLegs, l_theOther.m_orderComboLegs): 232 | return False 233 | return True 234 | 235 | -------------------------------------------------------------------------------- /app/ib/ext/OrderComboLeg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ generated source for module OrderComboLeg """ 3 | # 4 | # Original file copyright original author(s). 5 | # This file copyright Troy Melhase, troy@gci.net. 6 | # 7 | # WARNING: all changes to this file will be lost. 8 | 9 | from ib.lib import Double 10 | from ib.lib.overloading import overloaded 11 | 12 | # 13 | # * OrderComboLeg.java 14 | # * 15 | # 16 | # package: com.ib.client 17 | class OrderComboLeg(object): 18 | """ generated source for class OrderComboLeg """ 19 | m_price = float() 20 | 21 | # price per leg 22 | @overloaded 23 | def __init__(self): 24 | """ generated source for method __init__ """ 25 | self.m_price = Double.MAX_VALUE 26 | 27 | @__init__.register(object, float) 28 | def __init___0(self, p_price): 29 | """ generated source for method __init___0 """ 30 | self.m_price = p_price 31 | 32 | def __eq__(self, p_other): 33 | """ generated source for method equals """ 34 | if self is p_other: 35 | return True 36 | elif p_other is None: 37 | return False 38 | l_theOther = p_other 39 | if self.m_price != l_theOther.m_price: 40 | return False 41 | return True 42 | 43 | -------------------------------------------------------------------------------- /app/ib/ext/OrderState.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ generated source for module OrderState """ 3 | # 4 | # Original file copyright original author(s). 5 | # This file copyright Troy Melhase, troy@gci.net. 6 | # 7 | # WARNING: all changes to this file will be lost. 8 | 9 | from ib.lib.overloading import overloaded 10 | from ib.ext.Util import Util 11 | # 12 | # * OrderState.java 13 | # 14 | # package: com.ib.client 15 | class OrderState(object): 16 | """ generated source for class OrderState """ 17 | m_status = "" 18 | m_initMargin = "" 19 | m_maintMargin = "" 20 | m_equityWithLoan = "" 21 | m_commission = float() 22 | m_minCommission = float() 23 | m_maxCommission = float() 24 | m_commissionCurrency = "" 25 | m_warningText = "" 26 | 27 | @overloaded 28 | def __init__(self): 29 | """ generated source for method __init__ """ 30 | pass # super(OrderState, self).__init__(None, None, None, None, 0.0, 0.0, 0.0, None, None) 31 | 32 | @__init__.register(object, str, str, str, str, float, float, float, str, str) 33 | def __init___0(self, status, initMargin, maintMargin, equityWithLoan, commission, minCommission, maxCommission, commissionCurrency, warningText): 34 | """ generated source for method __init___0 """ 35 | self.m_initMargin = initMargin 36 | self.m_maintMargin = maintMargin 37 | self.m_equityWithLoan = equityWithLoan 38 | self.m_commission = commission 39 | self.m_minCommission = minCommission 40 | self.m_maxCommission = maxCommission 41 | self.m_commissionCurrency = commissionCurrency 42 | self.m_warningText = warningText 43 | 44 | def __eq__(self, other): 45 | """ generated source for method equals """ 46 | if self == other: 47 | return True 48 | if other is None: 49 | return False 50 | state = other 51 | if self.m_commission != state.m_commission or self.m_minCommission != state.m_minCommission or self.m_maxCommission != state.m_maxCommission: 52 | return False 53 | if Util.StringCompare(self.m_status, state.m_status) != 0 or Util.StringCompare(self.m_initMargin, state.m_initMargin) != 0 or Util.StringCompare(self.m_maintMargin, state.m_maintMargin) != 0 or Util.StringCompare(self.m_equityWithLoan, state.m_equityWithLoan) != 0 or Util.StringCompare(self.m_commissionCurrency, state.m_commissionCurrency) != 0: 54 | return False 55 | return True 56 | 57 | -------------------------------------------------------------------------------- /app/ib/ext/ScannerSubscription.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ generated source for module ScannerSubscription """ 3 | # 4 | # Original file copyright original author(s). 5 | # This file copyright Troy Melhase, troy@gci.net. 6 | # 7 | # WARNING: all changes to this file will be lost. 8 | 9 | from ib.lib import Double, Integer 10 | from ib.lib.overloading import overloaded 11 | # package: com.ib.client 12 | class ScannerSubscription(object): 13 | """ generated source for class ScannerSubscription """ 14 | NO_ROW_NUMBER_SPECIFIED = -1 15 | m_numberOfRows = NO_ROW_NUMBER_SPECIFIED 16 | m_instrument = "" 17 | m_locationCode = "" 18 | m_scanCode = "" 19 | m_abovePrice = Double.MAX_VALUE 20 | m_belowPrice = Double.MAX_VALUE 21 | m_aboveVolume = Integer.MAX_VALUE 22 | m_averageOptionVolumeAbove = Integer.MAX_VALUE 23 | m_marketCapAbove = Double.MAX_VALUE 24 | m_marketCapBelow = Double.MAX_VALUE 25 | m_moodyRatingAbove = "" 26 | m_moodyRatingBelow = "" 27 | m_spRatingAbove = "" 28 | m_spRatingBelow = "" 29 | m_maturityDateAbove = "" 30 | m_maturityDateBelow = "" 31 | m_couponRateAbove = Double.MAX_VALUE 32 | m_couponRateBelow = Double.MAX_VALUE 33 | m_excludeConvertible = "" 34 | m_scannerSettingPairs = "" 35 | m_stockTypeFilter = "" 36 | 37 | # Get 38 | @overloaded 39 | def numberOfRows(self): 40 | """ generated source for method numberOfRows """ 41 | return self.m_numberOfRows 42 | 43 | @overloaded 44 | def instrument(self): 45 | """ generated source for method instrument """ 46 | return self.m_instrument 47 | 48 | @overloaded 49 | def locationCode(self): 50 | """ generated source for method locationCode """ 51 | return self.m_locationCode 52 | 53 | @overloaded 54 | def scanCode(self): 55 | """ generated source for method scanCode """ 56 | return self.m_scanCode 57 | 58 | @overloaded 59 | def abovePrice(self): 60 | """ generated source for method abovePrice """ 61 | return self.m_abovePrice 62 | 63 | @overloaded 64 | def belowPrice(self): 65 | """ generated source for method belowPrice """ 66 | return self.m_belowPrice 67 | 68 | @overloaded 69 | def aboveVolume(self): 70 | """ generated source for method aboveVolume """ 71 | return self.m_aboveVolume 72 | 73 | @overloaded 74 | def averageOptionVolumeAbove(self): 75 | """ generated source for method averageOptionVolumeAbove """ 76 | return self.m_averageOptionVolumeAbove 77 | 78 | @overloaded 79 | def marketCapAbove(self): 80 | """ generated source for method marketCapAbove """ 81 | return self.m_marketCapAbove 82 | 83 | @overloaded 84 | def marketCapBelow(self): 85 | """ generated source for method marketCapBelow """ 86 | return self.m_marketCapBelow 87 | 88 | @overloaded 89 | def moodyRatingAbove(self): 90 | """ generated source for method moodyRatingAbove """ 91 | return self.m_moodyRatingAbove 92 | 93 | @overloaded 94 | def moodyRatingBelow(self): 95 | """ generated source for method moodyRatingBelow """ 96 | return self.m_moodyRatingBelow 97 | 98 | @overloaded 99 | def spRatingAbove(self): 100 | """ generated source for method spRatingAbove """ 101 | return self.m_spRatingAbove 102 | 103 | @overloaded 104 | def spRatingBelow(self): 105 | """ generated source for method spRatingBelow """ 106 | return self.m_spRatingBelow 107 | 108 | @overloaded 109 | def maturityDateAbove(self): 110 | """ generated source for method maturityDateAbove """ 111 | return self.m_maturityDateAbove 112 | 113 | @overloaded 114 | def maturityDateBelow(self): 115 | """ generated source for method maturityDateBelow """ 116 | return self.m_maturityDateBelow 117 | 118 | @overloaded 119 | def couponRateAbove(self): 120 | """ generated source for method couponRateAbove """ 121 | return self.m_couponRateAbove 122 | 123 | @overloaded 124 | def couponRateBelow(self): 125 | """ generated source for method couponRateBelow """ 126 | return self.m_couponRateBelow 127 | 128 | @overloaded 129 | def excludeConvertible(self): 130 | """ generated source for method excludeConvertible """ 131 | return self.m_excludeConvertible 132 | 133 | @overloaded 134 | def scannerSettingPairs(self): 135 | """ generated source for method scannerSettingPairs """ 136 | return self.m_scannerSettingPairs 137 | 138 | @overloaded 139 | def stockTypeFilter(self): 140 | """ generated source for method stockTypeFilter """ 141 | return self.m_stockTypeFilter 142 | 143 | # Set 144 | @numberOfRows.register(object, int) 145 | def numberOfRows_0(self, num): 146 | """ generated source for method numberOfRows_0 """ 147 | self.m_numberOfRows = num 148 | 149 | @instrument.register(object, str) 150 | def instrument_0(self, txt): 151 | """ generated source for method instrument_0 """ 152 | self.m_instrument = txt 153 | 154 | @locationCode.register(object, str) 155 | def locationCode_0(self, txt): 156 | """ generated source for method locationCode_0 """ 157 | self.m_locationCode = txt 158 | 159 | @scanCode.register(object, str) 160 | def scanCode_0(self, txt): 161 | """ generated source for method scanCode_0 """ 162 | self.m_scanCode = txt 163 | 164 | @abovePrice.register(object, float) 165 | def abovePrice_0(self, price): 166 | """ generated source for method abovePrice_0 """ 167 | self.m_abovePrice = price 168 | 169 | @belowPrice.register(object, float) 170 | def belowPrice_0(self, price): 171 | """ generated source for method belowPrice_0 """ 172 | self.m_belowPrice = price 173 | 174 | @aboveVolume.register(object, int) 175 | def aboveVolume_0(self, volume): 176 | """ generated source for method aboveVolume_0 """ 177 | self.m_aboveVolume = volume 178 | 179 | @averageOptionVolumeAbove.register(object, int) 180 | def averageOptionVolumeAbove_0(self, volume): 181 | """ generated source for method averageOptionVolumeAbove_0 """ 182 | self.m_averageOptionVolumeAbove = volume 183 | 184 | @marketCapAbove.register(object, float) 185 | def marketCapAbove_0(self, cap): 186 | """ generated source for method marketCapAbove_0 """ 187 | self.m_marketCapAbove = cap 188 | 189 | @marketCapBelow.register(object, float) 190 | def marketCapBelow_0(self, cap): 191 | """ generated source for method marketCapBelow_0 """ 192 | self.m_marketCapBelow = cap 193 | 194 | @moodyRatingAbove.register(object, str) 195 | def moodyRatingAbove_0(self, r): 196 | """ generated source for method moodyRatingAbove_0 """ 197 | self.m_moodyRatingAbove = r 198 | 199 | @moodyRatingBelow.register(object, str) 200 | def moodyRatingBelow_0(self, r): 201 | """ generated source for method moodyRatingBelow_0 """ 202 | self.m_moodyRatingBelow = r 203 | 204 | @spRatingAbove.register(object, str) 205 | def spRatingAbove_0(self, r): 206 | """ generated source for method spRatingAbove_0 """ 207 | self.m_spRatingAbove = r 208 | 209 | @spRatingBelow.register(object, str) 210 | def spRatingBelow_0(self, r): 211 | """ generated source for method spRatingBelow_0 """ 212 | self.m_spRatingBelow = r 213 | 214 | @maturityDateAbove.register(object, str) 215 | def maturityDateAbove_0(self, d): 216 | """ generated source for method maturityDateAbove_0 """ 217 | self.m_maturityDateAbove = d 218 | 219 | @maturityDateBelow.register(object, str) 220 | def maturityDateBelow_0(self, d): 221 | """ generated source for method maturityDateBelow_0 """ 222 | self.m_maturityDateBelow = d 223 | 224 | @couponRateAbove.register(object, float) 225 | def couponRateAbove_0(self, r): 226 | """ generated source for method couponRateAbove_0 """ 227 | self.m_couponRateAbove = r 228 | 229 | @couponRateBelow.register(object, float) 230 | def couponRateBelow_0(self, r): 231 | """ generated source for method couponRateBelow_0 """ 232 | self.m_couponRateBelow = r 233 | 234 | @excludeConvertible.register(object, str) 235 | def excludeConvertible_0(self, c): 236 | """ generated source for method excludeConvertible_0 """ 237 | self.m_excludeConvertible = c 238 | 239 | @scannerSettingPairs.register(object, str) 240 | def scannerSettingPairs_0(self, val): 241 | """ generated source for method scannerSettingPairs_0 """ 242 | self.m_scannerSettingPairs = val 243 | 244 | @stockTypeFilter.register(object, str) 245 | def stockTypeFilter_0(self, val): 246 | """ generated source for method stockTypeFilter_0 """ 247 | self.m_stockTypeFilter = val 248 | 249 | -------------------------------------------------------------------------------- /app/ib/ext/TagValue.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ generated source for module TagValue """ 3 | # 4 | # Original file copyright original author(s). 5 | # This file copyright Troy Melhase, troy@gci.net. 6 | # 7 | # WARNING: all changes to this file will be lost. 8 | 9 | from ib.lib.overloading import overloaded 10 | from ib.ext.Util import Util 11 | 12 | # 13 | # * UnderComp.java 14 | # * 15 | # 16 | # package: com.ib.client 17 | class TagValue(object): 18 | """ generated source for class TagValue """ 19 | m_tag = "" 20 | m_value = "" 21 | 22 | @overloaded 23 | def __init__(self): 24 | """ generated source for method __init__ """ 25 | pass 26 | 27 | @__init__.register(object, str, str) 28 | def __init___0(self, p_tag, p_value): 29 | """ generated source for method __init___0 """ 30 | self.m_tag = p_tag 31 | self.m_value = p_value 32 | 33 | def __eq__(self, p_other): 34 | """ generated source for method equals """ 35 | if self is p_other: 36 | return True 37 | if p_other is None: 38 | return False 39 | l_theOther = p_other 40 | if Util.StringCompare(self.m_tag, l_theOther.m_tag) != 0 or Util.StringCompare(self.m_value, l_theOther.m_value) != 0: 41 | return False 42 | return True 43 | 44 | -------------------------------------------------------------------------------- /app/ib/ext/TickType.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ generated source for module TickType """ 3 | # 4 | # Original file copyright original author(s). 5 | # This file copyright Troy Melhase, troy@gci.net. 6 | # 7 | # WARNING: all changes to this file will be lost. 8 | 9 | # 10 | # * TickType.java 11 | # * 12 | # 13 | # package: com.ib.client 14 | class TickType(object): 15 | """ generated source for class TickType """ 16 | # constants - tick types 17 | BID_SIZE = 0 18 | BID = 1 19 | ASK = 2 20 | ASK_SIZE = 3 21 | LAST = 4 22 | LAST_SIZE = 5 23 | HIGH = 6 24 | LOW = 7 25 | VOLUME = 8 26 | CLOSE = 9 27 | BID_OPTION = 10 28 | ASK_OPTION = 11 29 | LAST_OPTION = 12 30 | MODEL_OPTION = 13 31 | OPEN = 14 32 | LOW_13_WEEK = 15 33 | HIGH_13_WEEK = 16 34 | LOW_26_WEEK = 17 35 | HIGH_26_WEEK = 18 36 | LOW_52_WEEK = 19 37 | HIGH_52_WEEK = 20 38 | AVG_VOLUME = 21 39 | OPEN_INTEREST = 22 40 | OPTION_HISTORICAL_VOL = 23 41 | OPTION_IMPLIED_VOL = 24 42 | OPTION_BID_EXCH = 25 43 | OPTION_ASK_EXCH = 26 44 | OPTION_CALL_OPEN_INTEREST = 27 45 | OPTION_PUT_OPEN_INTEREST = 28 46 | OPTION_CALL_VOLUME = 29 47 | OPTION_PUT_VOLUME = 30 48 | INDEX_FUTURE_PREMIUM = 31 49 | BID_EXCH = 32 50 | ASK_EXCH = 33 51 | AUCTION_VOLUME = 34 52 | AUCTION_PRICE = 35 53 | AUCTION_IMBALANCE = 36 54 | MARK_PRICE = 37 55 | BID_EFP_COMPUTATION = 38 56 | ASK_EFP_COMPUTATION = 39 57 | LAST_EFP_COMPUTATION = 40 58 | OPEN_EFP_COMPUTATION = 41 59 | HIGH_EFP_COMPUTATION = 42 60 | LOW_EFP_COMPUTATION = 43 61 | CLOSE_EFP_COMPUTATION = 44 62 | LAST_TIMESTAMP = 45 63 | SHORTABLE = 46 64 | FUNDAMENTAL_RATIOS = 47 65 | RT_VOLUME = 48 66 | HALTED = 49 67 | BID_YIELD = 50 68 | ASK_YIELD = 51 69 | LAST_YIELD = 52 70 | CUST_OPTION_COMPUTATION = 53 71 | TRADE_COUNT = 54 72 | TRADE_RATE = 55 73 | VOLUME_RATE = 56 74 | LAST_RTH_TRADE = 57 75 | REGULATORY_IMBALANCE = 61 76 | 77 | @classmethod 78 | def getField(cls, tickType): 79 | """ generated source for method getField """ 80 | if tickType == cls.BID_SIZE: 81 | return "bidSize" 82 | elif tickType == cls.BID: 83 | return "bidPrice" 84 | elif tickType == cls.ASK: 85 | return "askPrice" 86 | elif tickType == cls.ASK_SIZE: 87 | return "askSize" 88 | elif tickType == cls.LAST: 89 | return "lastPrice" 90 | elif tickType == cls.LAST_SIZE: 91 | return "lastSize" 92 | elif tickType == cls.HIGH: 93 | return "high" 94 | elif tickType == cls.LOW: 95 | return "low" 96 | elif tickType == cls.VOLUME: 97 | return "volume" 98 | elif tickType == cls.CLOSE: 99 | return "close" 100 | elif tickType == cls.BID_OPTION: 101 | return "bidOptComp" 102 | elif tickType == cls.ASK_OPTION: 103 | return "askOptComp" 104 | elif tickType == cls.LAST_OPTION: 105 | return "lastOptComp" 106 | elif tickType == cls.MODEL_OPTION: 107 | return "modelOptComp" 108 | elif tickType == cls.OPEN: 109 | return "open" 110 | elif tickType == cls.LOW_13_WEEK: 111 | return "13WeekLow" 112 | elif tickType == cls.HIGH_13_WEEK: 113 | return "13WeekHigh" 114 | elif tickType == cls.LOW_26_WEEK: 115 | return "26WeekLow" 116 | elif tickType == cls.HIGH_26_WEEK: 117 | return "26WeekHigh" 118 | elif tickType == cls.LOW_52_WEEK: 119 | return "52WeekLow" 120 | elif tickType == cls.HIGH_52_WEEK: 121 | return "52WeekHigh" 122 | elif tickType == cls.AVG_VOLUME: 123 | return "AvgVolume" 124 | elif tickType == cls.OPEN_INTEREST: 125 | return "OpenInterest" 126 | elif tickType == cls.OPTION_HISTORICAL_VOL: 127 | return "OptionHistoricalVolatility" 128 | elif tickType == cls.OPTION_IMPLIED_VOL: 129 | return "OptionImpliedVolatility" 130 | elif tickType == cls.OPTION_BID_EXCH: 131 | return "OptionBidExchStr" 132 | elif tickType == cls.OPTION_ASK_EXCH: 133 | return "OptionAskExchStr" 134 | elif tickType == cls.OPTION_CALL_OPEN_INTEREST: 135 | return "OptionCallOpenInterest" 136 | elif tickType == cls.OPTION_PUT_OPEN_INTEREST: 137 | return "OptionPutOpenInterest" 138 | elif tickType == cls.OPTION_CALL_VOLUME: 139 | return "OptionCallVolume" 140 | elif tickType == cls.OPTION_PUT_VOLUME: 141 | return "OptionPutVolume" 142 | elif tickType == cls.INDEX_FUTURE_PREMIUM: 143 | return "IndexFuturePremium" 144 | elif tickType == cls.BID_EXCH: 145 | return "bidExch" 146 | elif tickType == cls.ASK_EXCH: 147 | return "askExch" 148 | elif tickType == cls.AUCTION_VOLUME: 149 | return "auctionVolume" 150 | elif tickType == cls.AUCTION_PRICE: 151 | return "auctionPrice" 152 | elif tickType == cls.AUCTION_IMBALANCE: 153 | return "auctionImbalance" 154 | elif tickType == cls.MARK_PRICE: 155 | return "markPrice" 156 | elif tickType == cls.BID_EFP_COMPUTATION: 157 | return "bidEFP" 158 | elif tickType == cls.ASK_EFP_COMPUTATION: 159 | return "askEFP" 160 | elif tickType == cls.LAST_EFP_COMPUTATION: 161 | return "lastEFP" 162 | elif tickType == cls.OPEN_EFP_COMPUTATION: 163 | return "openEFP" 164 | elif tickType == cls.HIGH_EFP_COMPUTATION: 165 | return "highEFP" 166 | elif tickType == cls.LOW_EFP_COMPUTATION: 167 | return "lowEFP" 168 | elif tickType == cls.CLOSE_EFP_COMPUTATION: 169 | return "closeEFP" 170 | elif tickType == cls.LAST_TIMESTAMP: 171 | return "lastTimestamp" 172 | elif tickType == cls.SHORTABLE: 173 | return "shortable" 174 | elif tickType == cls.FUNDAMENTAL_RATIOS: 175 | return "fundamentals" 176 | elif tickType == cls.RT_VOLUME: 177 | return "RTVolume" 178 | elif tickType == cls.HALTED: 179 | return "halted" 180 | elif tickType == cls.BID_YIELD: 181 | return "bidYield" 182 | elif tickType == cls.ASK_YIELD: 183 | return "askYield" 184 | elif tickType == cls.LAST_YIELD: 185 | return "lastYield" 186 | elif tickType == cls.CUST_OPTION_COMPUTATION: 187 | return "custOptComp" 188 | elif tickType == cls.TRADE_COUNT: 189 | return "trades" 190 | elif tickType == cls.TRADE_RATE: 191 | return "trades/min" 192 | elif tickType == cls.VOLUME_RATE: 193 | return "volume/min" 194 | elif tickType == cls.LAST_RTH_TRADE: 195 | return "lastRTHTrade" 196 | elif tickType == cls.REGULATORY_IMBALANCE: 197 | return "regulatoryImbalance" 198 | else: 199 | return "unknown" 200 | 201 | -------------------------------------------------------------------------------- /app/ib/ext/UnderComp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ generated source for module UnderComp """ 3 | # 4 | # Original file copyright original author(s). 5 | # This file copyright Troy Melhase, troy@gci.net. 6 | # 7 | # WARNING: all changes to this file will be lost. 8 | 9 | # 10 | # * UnderComp.java 11 | # * 12 | # 13 | # package: com.ib.client 14 | class UnderComp(object): 15 | """ generated source for class UnderComp """ 16 | m_conId = 0 17 | m_delta = float() 18 | m_price = float() 19 | 20 | def __init__(self): 21 | """ generated source for method __init__ """ 22 | self.m_conId = 0 23 | self.m_delta = 0 24 | self.m_price = 0 25 | 26 | def __eq__(self, p_other): 27 | """ generated source for method equals """ 28 | if self is p_other: 29 | return True 30 | if p_other is None or not (isinstance(p_other, (UnderComp, ))): 31 | return False 32 | l_theOther = p_other 33 | if self.m_conId != l_theOther.m_conId: 34 | return False 35 | if self.m_delta != l_theOther.m_delta: 36 | return False 37 | if self.m_price != l_theOther.m_price: 38 | return False 39 | return True 40 | 41 | -------------------------------------------------------------------------------- /app/ib/ext/Util.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ generated source for module Util """ 3 | # 4 | # Original file copyright original author(s). 5 | # This file copyright Troy Melhase, troy@gci.net. 6 | # 7 | # WARNING: all changes to this file will be lost. 8 | 9 | from ib.lib import Double, Integer 10 | # 11 | # * Util.java 12 | # 13 | # package: com.ib.client 14 | 15 | 16 | class Util(object): 17 | """ generated source for class Util """ 18 | @classmethod 19 | def StringIsEmpty(cls, strval): 20 | """ generated source for method StringIsEmpty """ 21 | return strval is None or 0 == len(strval) 22 | 23 | @classmethod 24 | def NormalizeString(cls, strval): 25 | """ generated source for method NormalizeString """ 26 | return strval if strval is not None else "" 27 | 28 | @classmethod 29 | def StringCompare(cls, lhs, rhs): 30 | """ generated source for method StringCompare """ 31 | return cmp(cls.NormalizeString(str(lhs)), cls.NormalizeString(str(rhs))) 32 | 33 | @classmethod 34 | def StringCompareIgnCase(cls, lhs, rhs): 35 | """ generated source for method StringCompareIgnCase """ 36 | return cmp(cls.NormalizeString(str(lhs)).lower(), cls.NormalizeString(str(rhs)).lower()) 37 | 38 | @classmethod 39 | def VectorEqualsUnordered(cls, lhs, rhs): 40 | """ generated source for method VectorEqualsUnordered """ 41 | if lhs == rhs: 42 | return True 43 | lhsCount = 0 if lhs is None else len(lhs) 44 | rhsCount = 0 if rhs is None else len(rhs) 45 | if lhsCount != rhsCount: 46 | return False 47 | if lhsCount == 0: 48 | return True 49 | matchedRhsElems = [bool() for __idx0 in range(rhsCount)] 50 | lhsIdx = 0 51 | while lhsIdx < lhsCount: 52 | lhsElem = lhs[lhsIdx] 53 | rhsIdx = 0 54 | while rhsIdx < rhsCount: 55 | if matchedRhsElems[rhsIdx]: 56 | continue 57 | if lhsElem == rhs[rhsIdx]: 58 | matchedRhsElems[rhsIdx] = True 59 | break 60 | rhsIdx += 1 61 | if rhsIdx >= rhsCount: 62 | # no matching elem found 63 | return False 64 | lhsIdx += 1 65 | return True 66 | 67 | @classmethod 68 | def IntMaxString(cls, value): 69 | """ generated source for method IntMaxString """ 70 | return "" if (value == Integer.MAX_VALUE) else str(value) 71 | 72 | @classmethod 73 | def DoubleMaxString(cls, value): 74 | """ generated source for method DoubleMaxString """ 75 | return "" if (value == Double.MAX_VALUE) else str(value) 76 | 77 | -------------------------------------------------------------------------------- /app/ib/ext/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ ib.ext -> externally generated code. 4 | 5 | The code here is generated with java2python_. The modules are created 6 | via the Makefile (not part of the ibpy distribution), and reference 7 | the ib.ext.cfg modules (also not part of the distribution). 8 | 9 | Refer to the documentation and to the Makefiles in the repository for 10 | more information. 11 | 12 | .. _java2python: http://code.google.com/p/java2python/ 13 | 14 | """ 15 | -------------------------------------------------------------------------------- /app/ib/lib/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ## 5 | # Just enough auxiliary bits to make the translated code work. 6 | # 7 | # This package provides the support necessary to use the translated 8 | # code. The configuration modules used in translation take care of 9 | # many semantic differences between Java and Python, while this 10 | # package provides the rest. 11 | ## 12 | 13 | import copy 14 | import functools 15 | import socket 16 | import struct 17 | import sys 18 | 19 | def toTypeName(value): 20 | return '%s%s' % (value[0].upper(), value[1:]) 21 | 22 | 23 | def maybeName(obj): 24 | """ Returns an object's __name__ attribute or it's string representation. 25 | 26 | @param obj any object 27 | @return obj name or string representation 28 | """ 29 | try: 30 | return obj.__name__ 31 | except (AttributeError, ): 32 | return str(obj) 33 | 34 | 35 | class classmethod_(classmethod): 36 | """ Classmethod that provides attribute delegation. 37 | 38 | """ 39 | def __init__(self, func): 40 | classmethod.__init__(self, func) 41 | self.func = func 42 | 43 | def __getattr__(self, name): 44 | return getattr(self.func, name) 45 | 46 | 47 | def synchronized(lock): 48 | """ Synchronization decorator. 49 | 50 | from http://wiki.python.org/moin/PythonDecoratorLibrary 51 | 52 | @param lock Lock or RLock instance 53 | @return decorator that provides automatic locking 54 | """ 55 | def wrapper(func): 56 | @functools.wraps(func) 57 | def inner(*args, **kwds): 58 | lock.acquire() 59 | try: 60 | return func(*args, **kwds) 61 | finally: 62 | lock.release() 63 | return inner 64 | return wrapper 65 | 66 | 67 | class Boolean(object): 68 | """ Partial implementation of Java Boolean type. 69 | 70 | """ 71 | def __init__(self, value): 72 | """ Constructor. 73 | 74 | @param value bool instance, True or False 75 | """ 76 | self.value = value 77 | 78 | def booleanValue(self): 79 | """ The value of this instance (a bool). 80 | 81 | @return True or False 82 | """ 83 | return self.value 84 | 85 | @classmethod 86 | def valueOf(cls, text): 87 | """ Creates an instance of this class with a bool value. 88 | 89 | @param cls this class 90 | @param text string 91 | @return instance of cls 92 | """ 93 | value = str(text).lower() == 'true' 94 | return cls(value) 95 | 96 | 97 | class Cloneable(object): 98 | """ Stub for the Cloneable Java interface. 99 | 100 | Some of the translated code implements the Java Cloneable 101 | interface, but its methods are never used. We provide this class 102 | for sub typing, and will implement methods as needed later. 103 | """ 104 | def clone(self): 105 | return copy.copy(self) 106 | 107 | 108 | class DataInputStream(object): 109 | """ Partial implementation of the Java DataInputStream type. 110 | 111 | """ 112 | def __init__(self, stream): 113 | """ Constructor. 114 | 115 | @param stream any object with recv method 116 | """ 117 | self.stream = stream 118 | self.recv = stream.recv 119 | 120 | def readByte(self, unpack=struct.unpack): 121 | """ Reads a byte from the contained stream. 122 | 123 | @return string read from stream 124 | """ 125 | return unpack('!b', self.recv(1))[0] 126 | 127 | 128 | class DataOutputStream(object): 129 | """ Partial implementation of the Java DataOutputStream type 130 | 131 | """ 132 | def __init__(self, stream): 133 | """ Constructor. 134 | 135 | @param stream any object with send method 136 | """ 137 | self.send = stream.send 138 | 139 | def write(self, data, pack=struct.pack, eol=struct.pack('!b', 0)): 140 | """ Writes data to the contained stream. 141 | 142 | @param data string to send, or 0 143 | @return None 144 | """ 145 | send = self.send 146 | if data == 0: 147 | send(eol) 148 | else: 149 | for char in data: 150 | if sys.version_info[0] > 2: 151 | char = char.encode('utf-8') 152 | send(pack('!c', char)) 153 | 154 | 155 | class Double(float): 156 | """ Partial implementation of Java Double type. 157 | 158 | """ 159 | ## 160 | # sentinel value used by the socket writer 161 | MAX_VALUE = sys.maxint 162 | 163 | @staticmethod 164 | def parseDouble(text): 165 | """ Float double (float) from string. 166 | 167 | @param text value to parse 168 | @return float instance 169 | """ 170 | return float(text or 0) 171 | 172 | 173 | class Integer(int): 174 | """ Partial implementation of Java Integer type. 175 | 176 | """ 177 | ## 178 | # sentinel value used by the socket writer 179 | MAX_VALUE = sys.maxint 180 | 181 | @staticmethod 182 | def parseInt(text): 183 | """ Int from string. 184 | 185 | @param text value to parse 186 | @return int instance 187 | """ 188 | return int(text or 0) 189 | 190 | @staticmethod 191 | def parseLong(text): 192 | """ Long from string. 193 | 194 | @param text value to parse 195 | @return long instance 196 | """ 197 | return long(text or 0) 198 | 199 | 200 | ## 201 | # The generated code uses Longs just like Integers, so we use an alias 202 | # instead of a subclass (for now). 203 | Long = Integer 204 | 205 | 206 | class Socket(socket.socket): 207 | """ Partial implementation of the Java Socket type. 208 | 209 | """ 210 | def __init__(self, host, port): 211 | """ Constructor; attempts connection immediately. 212 | 213 | @param host hostname as string 214 | @param port port number as integer 215 | """ 216 | socket.socket.__init__(self, socket.AF_INET, socket.SOCK_STREAM) 217 | self.connect((host, port)) 218 | 219 | def getInputStream(self): 220 | """ Returns this instance, which has a send method. 221 | 222 | """ 223 | return self 224 | 225 | def getOutputStream(self): 226 | """ Returns this instance, which has a recv method. 227 | 228 | """ 229 | return self 230 | 231 | def disconnect(self): 232 | self.shutdown(socket.SHUT_RDWR) 233 | self.close() 234 | 235 | def isConnected(self): 236 | try: 237 | throwaway = self.getpeername() 238 | return True 239 | except (socket.error, ), ex: 240 | return False 241 | 242 | 243 | class StringBuffer(list): 244 | """ Partial implementation of the Java StringBuffer type 245 | 246 | Translated code uses instances of this type to build up strings. 247 | The list base type provides the append method. 248 | """ 249 | def __str__(self, join=str.join, chr=chr): 250 | """ the string value of this instance 251 | 252 | @return string from characters contained in this instance 253 | """ 254 | return join('', [chr(v) for v in self]) 255 | 256 | 257 | if 'qt' in sys.modules: 258 | from qt import QThread 259 | 260 | class ThreadType(QThread): 261 | """ Partial implementation of Java Thread type, based on Qt3 QThread. 262 | 263 | """ 264 | def __init__(self, name): 265 | """ Constructor. 266 | 267 | @param name ignored 268 | """ 269 | QThread.__init__(self) 270 | 271 | def interrupt(self): 272 | """ Stop this thread (by call to terminate). 273 | 274 | """ 275 | return self.terminate() 276 | 277 | def isInterrupted(self): 278 | """ Check state of thread. 279 | 280 | @return True if thread is finished 281 | """ 282 | return self.finished() 283 | 284 | def setDaemon(self, value): 285 | """ No-op. 286 | 287 | @param value ignored 288 | @return None 289 | """ 290 | 291 | def setName(self, value): 292 | """ No-op. 293 | 294 | @param value ignored 295 | @return None 296 | """ 297 | 298 | 299 | 300 | elif 'PyQt4' in sys.modules: 301 | from PyQt4.QtCore import QThread 302 | 303 | class ThreadType(QThread): 304 | """ Partial implementation of Java Thread type, based on Qt4 QThread. 305 | 306 | """ 307 | def __init__(self, name): 308 | """ Constructor. 309 | 310 | @param name ignored 311 | """ 312 | QThread.__init__(self) 313 | 314 | def interrupt(self): 315 | """ stop this thread (by call to exit) 316 | 317 | """ 318 | return self.exit() 319 | 320 | def isInterrupted(self): 321 | """ check state of thread 322 | 323 | @return True if thread is finished 324 | """ 325 | return self.isFinished() 326 | 327 | def setDaemon(self, value): 328 | """ No-op. 329 | 330 | @param value ignored 331 | @return None 332 | """ 333 | 334 | def setName(self, value): 335 | """ sets the name of this QObject 336 | 337 | @param value name of object as string 338 | @return None 339 | """ 340 | self.setObjectName(value) 341 | 342 | 343 | else: 344 | import threading 345 | 346 | class ThreadType(threading.Thread): 347 | """ Partial implementation of Java Thread type, based on Python Thread. 348 | 349 | """ 350 | def __init__(self, name): 351 | """ Constructor. 352 | 353 | @param name name of this thread 354 | """ 355 | threading.Thread.__init__(self, name=name) 356 | self.setDaemon(True) 357 | 358 | def interrupt(self): 359 | """ No-op; Python threads are not directly interruptible. 360 | 361 | """ 362 | return False 363 | 364 | def isInterrupted(self): 365 | """ Check state of thread (always False). 366 | 367 | @return False 368 | """ 369 | return False 370 | 371 | 372 | class Thread(ThreadType): 373 | """ Thread parent type, based on available framework 374 | 375 | """ 376 | def __init__(self, name, parent, dis): 377 | """ Constructor. 378 | 379 | @param name name of this thread 380 | @param parent ignored 381 | @param dis ignored 382 | """ 383 | ThreadType.__init__(self, name=name) 384 | 385 | 386 | def term(self): 387 | def isInterrupted(): 388 | print 'down town' 389 | return True 390 | self.isInterrupted = isInterrupted 391 | self.m_dis.stream.shutdown(socket.SHUT_RDWR) 392 | self.m_dis.stream.close() 393 | -------------------------------------------------------------------------------- /app/ib/lib/logger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ## 5 | # Defines logging formats and logger instance 6 | ## 7 | 8 | import logging 9 | import os 10 | 11 | ## 12 | # Default log message formatting string. 13 | format = '%(asctime)s %(levelname)-9.9s %(message)s' 14 | 15 | ## 16 | # Default log date formatting string. 17 | datefmt = '%d-%b-%y %H:%M:%S' 18 | 19 | ## 20 | # Default log level. Set IBPY_LOGLEVEL environment variable to 21 | # change this default. 22 | level = int(os.environ.get('IBPY_LOGLEVEL', logging.DEBUG)) 23 | 24 | 25 | def logger(name='ibpy', level=level, format=format, 26 | datefmt=datefmt): 27 | """ Configures and returns a logging instance. 28 | 29 | @param name ignored 30 | @param level logging level 31 | @param format format string for log messages 32 | @param datefmt format string for log dates 33 | @return logging instance (the module) 34 | """ 35 | logging.basicConfig(level=level, format=format, datefmt=datefmt) 36 | return logging.getLogger(name) 37 | -------------------------------------------------------------------------------- /app/ib/lib/overloading.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2.5 2 | 3 | ## 4 | # Dynamically overloaded functions. 5 | # 6 | # This is an implementation of (dynamically, or run-time) overloaded 7 | # functions; also known as generic functions or multi-methods. 8 | # 9 | # This module is from Python SVN, 10 | # http://svn.python.org/view/sandbox/trunk/overload/overloading.py 11 | ## 12 | 13 | """Dynamically overloaded functions. 14 | 15 | This is an implementation of (dynamically, or run-time) overloaded 16 | functions; also known as generic functions or multi-methods. 17 | 18 | The dispatch algorithm uses the types of all argument for dispatch, 19 | similar to (compile-time) overloaded functions or methods in C++ and 20 | Java. 21 | 22 | Most of the complexity in the algorithm comes from the need to support 23 | subclasses in call signatures. For example, if an function is 24 | registered for a signature (T1, T2), then a call with a signature (S1, 25 | S2) is acceptable, assuming that S1 is a subclass of T1, S2 a subclass 26 | of T2, and there are no other more specific matches (see below). 27 | 28 | If there are multiple matches and one of those doesn't *dominate* all 29 | others, the match is deemed ambiguous and an exception is raised. A 30 | subtlety here: if, after removing the dominated matches, there are 31 | still multiple matches left, but they all map to the same function, 32 | then the match is not deemed ambiguous and that function is used. 33 | Read the method find_func() below for details. 34 | 35 | Python 2.5 is required due to the use of predicates any() and all(). 36 | 37 | """ 38 | 39 | from types import MethodType as instancemethod 40 | 41 | # Make the environment more like Python 3.0 42 | __metaclass__ = type 43 | from itertools import izip as zip 44 | 45 | 46 | class overloaded: 47 | """An implementation of overloaded functions.""" 48 | 49 | def __init__(self, default_func): 50 | # Decorator to declare new overloaded function. 51 | self.registry = {} 52 | self.cache = {} 53 | self.default_func = default_func 54 | 55 | def __get__(self, obj, type=None): 56 | if obj is None: 57 | return self 58 | return instancemethod(self, obj) 59 | 60 | def register(self, *types): 61 | """Decorator to register an implementation for a specific set of types. 62 | 63 | .register(t1, t2)(f) is equivalent to .register_func((t1, t2), f). 64 | 65 | """ 66 | def helper(func): 67 | self.register_func(types, func) 68 | return func 69 | return helper 70 | 71 | def register_func(self, types, func): 72 | """Helper to register an implementation.""" 73 | self.registry[tuple(types)] = func 74 | self.cache = {} # Clear the cache (later we can optimize this). 75 | 76 | def __call__(self, *args): 77 | """Call the overloaded function.""" 78 | types = tuple(map(type, args)) 79 | func = self.cache.get(types) 80 | if func is None: 81 | self.cache[types] = func = self.find_func(types) 82 | return func(*args) 83 | 84 | def find_func(self, types): 85 | """Find the appropriate overloaded function; don't call it. 86 | 87 | This won't work for old-style classes or classes without __mro__. 88 | 89 | """ 90 | func = self.registry.get(types) 91 | if func is not None: 92 | # Easy case -- direct hit in registry. 93 | return func 94 | 95 | # XXX Phillip Eby suggests to use issubclass() instead of __mro__. 96 | # There are advantages and disadvantages. 97 | 98 | # I can't help myself -- this is going to be intense functional code. 99 | # Find all possible candidate signatures. 100 | mros = tuple(t.__mro__ for t in types) 101 | n = len(mros) 102 | candidates = [sig for sig in self.registry 103 | if len(sig) == n and 104 | all(t in mro for t, mro in zip(sig, mros))] 105 | if not candidates: 106 | # No match at all -- use the default function. 107 | return self.default_func 108 | if len(candidates) == 1: 109 | # Unique match -- that's an easy case. 110 | return self.registry[candidates[0]] 111 | 112 | # More than one match -- weed out the subordinate ones. 113 | 114 | def dominates(dom, sub, 115 | orders=tuple(dict((t, i) for i, t in enumerate(mro)) 116 | for mro in mros)): 117 | # Predicate to decide whether dom strictly dominates sub. 118 | # Strict domination is defined as domination without equality. 119 | # The arguments dom and sub are type tuples of equal length. 120 | # The orders argument is a precomputed auxiliary data structure 121 | # giving dicts of ordering information corresponding to the 122 | # positions in the type tuples. 123 | # A type d dominates a type s iff order[d] <= order[s]. 124 | # A type tuple (d1, d2, ...) dominates a type tuple of equal length 125 | # (s1, s2, ...) iff d1 dominates s1, d2 dominates s2, etc. 126 | if dom is sub: 127 | return False 128 | return all(order[d] <= order[s] 129 | for d, s, order in zip(dom, sub, orders)) 130 | 131 | # I suppose I could inline dominates() but it wouldn't get any clearer. 132 | candidates = [cand 133 | for cand in candidates 134 | if not any(dominates(dom, cand) for dom in candidates)] 135 | if len(candidates) == 1: 136 | # There's exactly one candidate left. 137 | return self.registry[candidates[0]] 138 | 139 | # Perhaps these multiple candidates all have the same implementation? 140 | funcs = set(self.registry[cand] for cand in candidates) 141 | if len(funcs) == 1: 142 | return funcs.pop() 143 | 144 | # No, the situation is irreducibly ambiguous. 145 | raise TypeError("ambigous call; types=%r; candidates=%r" % 146 | (types, candidates)) 147 | -------------------------------------------------------------------------------- /app/ib/opt/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ## 5 | # Sugary sweet layer of icing on top of the TWS API. 6 | # 7 | # Use: 8 | # {{{ 9 | # from ib.opt import ibConnection, message 10 | # 11 | # def my_callback(msg): 12 | # ... 13 | # 14 | # con = ibConnection() 15 | # con.register(my_callback, message.TickSize, message.TickPrice) 16 | # con.connect() 17 | # con.reqAccountUpdates(...) 18 | # ... 19 | # con.unregister(my_callback, message.TickSize) 20 | # }}} 21 | # 22 | # Enable and disable logging: 23 | # 24 | # {{{ 25 | # con.enableLogging() 26 | # ... 27 | # con.enableLogging(False) 28 | # }}} 29 | ## 30 | from ib.opt.connection import Connection 31 | 32 | 33 | ## 34 | # This is the preferred client interface to this module. 35 | # Alternatively, the Connection type can be sub-classed an its 36 | # 'create' classmethod reused. 37 | ibConnection = Connection.create 38 | -------------------------------------------------------------------------------- /app/ib/opt/connection.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ## 5 | # Defines the Connection class to encapsulate a connection to IB TWS. 6 | # 7 | # Connection instances defer failed attribute lookup to their receiver 8 | # and sender member objects. This makes it easy to access the 9 | # receiver to register functions: 10 | # 11 | # >>> con = ibConnection() 12 | # >>> con.register(my_callable) 13 | # 14 | # And it makes it easy to access the sender functions: 15 | # 16 | # >>> con.reqScannerParameters() 17 | # >>> con.placeOrder(...) 18 | # 19 | ## 20 | from ib.opt.dispatcher import Dispatcher 21 | from ib.opt.receiver import Receiver 22 | from ib.opt.sender import Sender 23 | 24 | 25 | class Connection(object): 26 | """ Encapsulates a connection to TWS. 27 | 28 | """ 29 | def __init__(self, host, port, clientId, receiver, sender, dispatcher): 30 | """ Constructor. 31 | 32 | @param host name of host for connection; default is localhost 33 | @param port port number for connection; default is 7496 34 | @param clientId client identifier to send when connected 35 | @param receiver instance for reading from the connected socket 36 | @param sender instance for writing to the connected socket 37 | @param dispatcher instance for dispatching socket messages 38 | """ 39 | self.host = host 40 | self.port = port 41 | self.clientId = clientId 42 | self.receiver = receiver 43 | self.sender = sender 44 | self.dispatcher = dispatcher 45 | 46 | def __getattr__(self, name): 47 | """ x.__getattr__('name') <==> x.name 48 | 49 | @return attribute of instance dispatcher, receiver, or sender 50 | """ 51 | for obj in (self.dispatcher, self.receiver, self.sender): 52 | try: 53 | return getattr(obj, name) 54 | except (AttributeError, ): 55 | pass 56 | err = "'%s' object has no attribute '%s'" 57 | raise AttributeError(err % (self.__class__.__name__, name)) 58 | 59 | def connect(self): 60 | """ Establish a connection to TWS with instance attributes. 61 | 62 | @return True if connected, otherwise raises an exception 63 | """ 64 | return self.sender.connect(self.host, self.port, self.clientId, 65 | self.receiver) 66 | 67 | @classmethod 68 | def create(cls, host='localhost', port=7496, clientId=0, 69 | receiver=None, sender=None, dispatcher=None): 70 | """ Creates and returns Connection class (or subclass) instance. 71 | 72 | For the receiver, sender, and dispatcher parameters, pass in 73 | an object instance for those duties; leave as None to have new 74 | instances constructed. 75 | 76 | @param host name of host for connection; default is localhost 77 | @param port port number for connection; default is 7496 78 | @param clientId client identifier to send when connected 79 | 80 | @param receiver=None object for reading messages 81 | @param sender=None object for writing requests 82 | @param dispatcher=None object for dispatching messages 83 | 84 | @return Connection (or subclass) instance 85 | """ 86 | dispatcher = Dispatcher() if dispatcher is None else dispatcher 87 | receiver = Receiver(dispatcher) if receiver is None else receiver 88 | sender = Sender(dispatcher) if sender is None else sender 89 | return cls(host, port, clientId, receiver, sender, dispatcher) 90 | -------------------------------------------------------------------------------- /app/ib/opt/dispatcher.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ## 5 | # Defines Dispatcher class to send messages to registered listeners. 6 | # 7 | ## 8 | from Queue import Queue, Empty 9 | 10 | from ib.lib import maybeName, logger 11 | from ib.opt import message 12 | 13 | 14 | class Dispatcher(object): 15 | """ 16 | 17 | """ 18 | def __init__(self, listeners=None, messageTypes=None): 19 | """ Initializer. 20 | 21 | @param listeners=None mapping of existing listeners 22 | @param types=None method name to message type lookup 23 | """ 24 | self.listeners = listeners if listeners else {} 25 | self.messageTypes = messageTypes if messageTypes else message.registry 26 | self.logger = logger.logger() 27 | 28 | def __call__(self, name, args): 29 | """ Send message to each listener. 30 | 31 | @param name method name 32 | @param args arguments for message instance 33 | @return None 34 | """ 35 | results = [] 36 | try: 37 | messageType = self.messageTypes[name] 38 | listeners = self.listeners[maybeName(messageType[0])] 39 | except (KeyError, ): 40 | return results 41 | message = messageType[0](**args) 42 | for listener in listeners: 43 | try: 44 | results.append(listener(message)) 45 | except (Exception, ): 46 | errmsg = ("Exception in message dispatch. " 47 | "Handler '%s' for '%s'") 48 | self.logger.exception(errmsg, maybeName(listener), name) 49 | results.append(None) 50 | return results 51 | 52 | def enableLogging(self, enable=True): 53 | """ Enable or disable logging of all messages. 54 | 55 | @param enable if True (default), enables logging; otherwise disables 56 | @return True if enabled, False otherwise 57 | """ 58 | if enable: 59 | self.registerAll(self.logMessage) 60 | else: 61 | self.unregisterAll(self.logMessage) 62 | return enable 63 | 64 | def logMessage(self, message): 65 | """ Format and send a message values to the logger. 66 | 67 | @param message instance of Message 68 | @return None 69 | """ 70 | line = str.join(', ', ('%s=%s' % item for item in message.items())) 71 | self.logger.debug('%s(%s)', message.typeName, line) 72 | 73 | def iterator(self, *types): 74 | """ Create and return a function for iterating over messages. 75 | 76 | @param *types zero or more message types to associate with listener 77 | @return function that yields messages 78 | """ 79 | queue = Queue() 80 | closed = [] 81 | def messageGenerator(block=True, timeout=0.1): 82 | while True: 83 | try: 84 | yield queue.get(block=block, timeout=timeout) 85 | except (Empty, ): 86 | if closed: 87 | break 88 | self.register(closed.append, 'ConnectionClosed') 89 | if types: 90 | self.register(queue.put, *types) 91 | else: 92 | self.registerAll(queue.put) 93 | return messageGenerator 94 | 95 | def register(self, listener, *types): 96 | """ Associate listener with message types created by this Dispatcher. 97 | 98 | @param listener callable to receive messages 99 | @param *types zero or more message types to associate with listener 100 | @return True if associated with one or more handler; otherwise False 101 | """ 102 | count = 0 103 | for messagetype in types: 104 | key = maybeName(messagetype) 105 | listeners = self.listeners.setdefault(key, []) 106 | if listener not in listeners: 107 | listeners.append(listener) 108 | count += 1 109 | return count > 0 110 | 111 | def registerAll(self, listener): 112 | """ Associate listener with all messages created by this Dispatcher. 113 | 114 | @param listener callable to receive messages 115 | @return True if associated with one or more handler; otherwise False 116 | """ 117 | return self.register(listener, *[maybeName(i) for v in self.messageTypes.values() for i in v]) 118 | 119 | def unregister(self, listener, *types): 120 | """ Disassociate listener with message types created by this Dispatcher. 121 | 122 | @param listener callable to no longer receive messages 123 | @param *types zero or more message types to disassociate with listener 124 | @return True if disassociated with one or more handler; otherwise False 125 | """ 126 | count = 0 127 | for messagetype in types: 128 | try: 129 | listeners = self.listeners[maybeName(messagetype)] 130 | except (KeyError, ): 131 | pass 132 | else: 133 | if listener in listeners: 134 | listeners.remove(listener) 135 | count += 1 136 | return count > 0 137 | 138 | def unregisterAll(self, listener): 139 | """ Disassociate listener with all messages created by this Dispatcher. 140 | 141 | @param listener callable to no longer receive messages 142 | @return True if disassociated with one or more handler; otherwise False 143 | """ 144 | return self.unregister(listener, *[maybeName(i) for v in list(self.messageTypes.values()) for i in v]) 145 | -------------------------------------------------------------------------------- /app/ib/opt/message.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ## 5 | # Defines message types for the Receiver class. 6 | # 7 | # This module inspects the EWrapper class to build a set of Message 8 | # types. In creating the types, it also builds a registry of them 9 | # that the Receiver class then uses to determine message types. 10 | ## 11 | 12 | import sys 13 | from ast import NodeVisitor, parse 14 | from inspect import getsourcefile 15 | from re import match 16 | 17 | from ib.ext.AnyWrapper import AnyWrapper 18 | from ib.ext.EWrapper import EWrapper 19 | from ib.ext.EClientSocket import EClientSocket 20 | from ib.lib import toTypeName 21 | 22 | 23 | class SignatureAccumulator(NodeVisitor): 24 | """ 25 | 26 | """ 27 | def __init__(self, classes): 28 | NodeVisitor.__init__(self) 29 | self.signatures = [] 30 | for filename in (getsourcefile(cls) for cls in classes): 31 | self.visit(parse(open(filename).read())) 32 | 33 | def visit_FunctionDef(self, node): 34 | if sys.version_info[0] < 3: 35 | args = [arg.id for arg in node.args.args] 36 | else: 37 | args = [arg.arg for arg in node.args.args] 38 | self.signatures.append((node.name, args[1:])) 39 | 40 | 41 | class EClientSocketAccumulator(SignatureAccumulator): 42 | def getSignatures(self): 43 | for name, args in self.signatures: 44 | if match('(?i)req|cancel|place', name): 45 | yield (name, args) 46 | 47 | 48 | class EWrapperAccumulator(SignatureAccumulator): 49 | def getSignatures(self): 50 | for name, args in self.signatures: 51 | if match('(?!((?i)error.*))', name): 52 | yield (name, args) 53 | 54 | 55 | ## 56 | # Dictionary that associates wrapper method names to the message class 57 | # that should be instantiated for delivery during that method call. 58 | registry = {} 59 | 60 | 61 | def messageTypeNames(): 62 | """ Builds set of message type names. 63 | 64 | @return set of all message type names as strings 65 | """ 66 | def typeNames(): 67 | for types in registry.values(): 68 | for typ in types: 69 | yield typ.typeName 70 | return set(typeNames()) 71 | 72 | 73 | class Message(object): 74 | """ Base class for Message types. 75 | 76 | """ 77 | __slots__ = () 78 | 79 | def __init__(self, **kwds): 80 | """ Constructor. 81 | 82 | @param **kwds keywords and values for instance 83 | """ 84 | for name in self.__slots__: 85 | setattr(self, name, kwds.pop(name, None)) 86 | assert not kwds 87 | 88 | def __len__(self): 89 | """ x.__len__() <==> len(x) 90 | 91 | """ 92 | return len(self.keys()) 93 | 94 | def __str__(self): 95 | """ x.__str__() <==> str(x) 96 | 97 | """ 98 | name = self.typeName 99 | items = str.join(', ', ['%s=%s' % item for item in self.items()]) 100 | return '<%s%s>' % (name, (' ' + items) if items else '') 101 | 102 | def items(self): 103 | """ List of message (slot, slot value) pairs, as 2-tuples. 104 | 105 | @return list of 2-tuples, each slot (name, value) 106 | """ 107 | return zip(self.keys(), self.values()) 108 | 109 | def values(self): 110 | """ List of instance slot values. 111 | 112 | @return list of each slot value 113 | """ 114 | return [getattr(self, key, None) for key in self.keys()] 115 | 116 | def keys(self): 117 | """ List of instance slots. 118 | 119 | @return list of each slot. 120 | """ 121 | return self.__slots__ 122 | 123 | 124 | class Error(Message): 125 | """ Specialized message type. 126 | 127 | The error family of method calls can't be built programmatically, 128 | so we define one here. 129 | """ 130 | __slots__ = ('id', 'errorCode', 'errorMsg') 131 | 132 | 133 | def buildMessageRegistry(seq, suffixes=[''], bases=(Message, )): 134 | """ Construct message types and add to given mapping. 135 | 136 | @param seq pairs of method (name, arguments) 137 | @param bases sequence of base classes for message types 138 | @return None 139 | """ 140 | for name, args in sorted(seq): 141 | for suffix in suffixes: 142 | typename = toTypeName(name) + suffix 143 | typens = {'__slots__':args, '__assoc__':name, 'typeName':name} 144 | msgtype = type(typename, bases, typens) 145 | if name in registry: 146 | registry[name] = registry[name] + (msgtype, ) 147 | else: 148 | registry[name] = (msgtype, ) 149 | 150 | 151 | 152 | 153 | eWrapperAccum = EWrapperAccumulator((AnyWrapper, EWrapper)) 154 | eClientAccum = EClientSocketAccumulator((EClientSocket, )) 155 | 156 | wrapperMethods = list(eWrapperAccum.getSignatures()) 157 | clientSocketMethods = list(eClientAccum.getSignatures()) 158 | errorMethods = [('error', Error.__slots__), ] 159 | 160 | buildMessageRegistry(wrapperMethods) 161 | buildMessageRegistry(clientSocketMethods, suffixes=('Pre', 'Post')) 162 | buildMessageRegistry(errorMethods) 163 | 164 | def initModule(): 165 | target = globals() 166 | for messageTypes in registry.values(): 167 | for messageType in messageTypes: 168 | target[messageType.typeName] = messageType 169 | 170 | try: 171 | initModule() 172 | except (NameError, ): 173 | pass 174 | else: 175 | del(initModule) 176 | 177 | 178 | del(AnyWrapper) 179 | del(EWrapper) 180 | del(EClientSocket) 181 | del(eWrapperAccum) 182 | del(eClientAccum) 183 | -------------------------------------------------------------------------------- /app/ib/opt/messagetools.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from functools import partial, wraps 5 | 6 | from ib.ext.TickType import TickType 7 | 8 | 9 | ## 10 | # To programmatically generate the TickType filters, use something like this sketch: 11 | # 12 | # vs = [(name, value) for name, value in [(name, getattr(TickType, name)) 13 | # for name in dir(TickType)] if type(value)==int] 14 | # titlevalues = [(title[0].lower()+title[1:], value) 15 | # for title in [''.join([part.title() for part in name.split('_')]) 16 | # for name, value in vs]] 17 | 18 | 19 | def messageFilter(function, predicate=lambda msg:True): 20 | @wraps(function) 21 | def inner(msg): 22 | if predicate(msg): 23 | return function(msg) 24 | return inner 25 | 26 | 27 | askSizeFilter = partial(messageFilter, predicate=lambda msg:msg.field==TickType.ASK_SIZE) 28 | askPriceFilter = partial(messageFilter, predicate=lambda msg:msg.field==TickType.ASK) 29 | 30 | bidSizeFilter = partial(messageFilter, predicate=lambda msg:msg.field==TickType.BID_SIZE) 31 | bidPriceFilter = partial(messageFilter, predicate=lambda msg:msg.field==TickType.BID) 32 | 33 | lastSizeFilter = partial(messageFilter, predicate=lambda msg:msg.field==TickType.LAST_SIZE) 34 | lastPriceFilter = partial(messageFilter, predicate=lambda msg:msg.field==TickType.LAST) 35 | 36 | 37 | # We don't need functions for filtering by message type because that's 38 | # what the reader/receiver/dispatcher already does. 39 | -------------------------------------------------------------------------------- /app/ib/opt/receiver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ## 5 | # Defines Receiver class to handle inbound data. 6 | # 7 | # The Receiver class is built programatically at runtime. Message 8 | # types are defined in the ib.opt.message module, and those types are 9 | # used to construct methods on the Receiver class during its 10 | # definition. Refer to the ReceiverType metaclass and the 11 | # ib.opt.message module more information. 12 | # 13 | ## 14 | from ib.lib.overloading import overloaded 15 | from ib.opt.message import wrapperMethods 16 | 17 | 18 | def messageMethod(name, parameters): 19 | """ Creates method for dispatching messages. 20 | 21 | @param name name of method as string 22 | @param parameters list of method argument names 23 | @return newly created method (as closure) 24 | """ 25 | def dispatchMethod(self, *arguments): 26 | self.dispatcher(name, dict(zip(parameters, arguments))) 27 | dispatchMethod.__name__ = name 28 | return dispatchMethod 29 | 30 | 31 | class ReceiverType(type): 32 | """ Metaclass to add EWrapper methods to Receiver class. 33 | 34 | When the Receiver class is defined, this class adds all of the 35 | wrapper methods to it. 36 | """ 37 | def __new__(cls, name, bases, namespace): 38 | """ Creates a new type. 39 | 40 | @param name name of new type as string 41 | @param bases tuple of base classes 42 | @param namespace dictionary with namespace for new type 43 | @return generated type 44 | """ 45 | for methodName, methodArgs in wrapperMethods: 46 | namespace[methodName] = messageMethod(methodName, methodArgs) 47 | return type(name, bases, namespace) 48 | 49 | 50 | class Receiver(object): 51 | """ Receiver -> dispatches messages to interested callables 52 | 53 | Instances implement the EWrapper interface by way of the 54 | metaclass. 55 | """ 56 | __metaclass__ = ReceiverType 57 | 58 | def __init__(self, dispatcher): 59 | """ Initializer. 60 | 61 | @param dispatcher message dispatcher instance 62 | """ 63 | self.dispatcher = dispatcher 64 | 65 | @overloaded 66 | def error(self, e): 67 | """ Dispatch an error generated by the reader. 68 | 69 | Error message types can't be associated in the default manner 70 | with this family of methods, so we define these three here 71 | by hand. 72 | 73 | @param e some error value 74 | @return None 75 | """ 76 | self.dispatcher('error', dict(errorMsg=e)) 77 | 78 | @error.register(object, str) 79 | def error_0(self, strval): 80 | """ Dispatch an error given a string value. 81 | 82 | @param strval some error value as string 83 | @return None 84 | """ 85 | self.dispatcher('error', dict(errorMsg=strval)) 86 | 87 | @error.register(object, int, int, str) 88 | def error_1(self, id, errorCode, errorMsg): 89 | """ Dispatch an error given an id, code and message. 90 | 91 | @param id error id 92 | @param errorCode error code 93 | @param errorMsg error message 94 | @return None 95 | """ 96 | params = dict(id=id, errorCode=errorCode, errorMsg=errorMsg) 97 | self.dispatcher('error', params) 98 | -------------------------------------------------------------------------------- /app/ib/opt/sender.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ## 5 | # Defines Sender class to handle outbound requests. 6 | # 7 | # Sender instances defer failed attribute lookup to their 8 | # EClientSocket member objects. 9 | # 10 | ## 11 | from functools import wraps 12 | 13 | from ib.ext.EClientSocket import EClientSocket 14 | from ib.lib import toTypeName 15 | from ib.opt.message import registry, clientSocketMethods 16 | 17 | 18 | class Sender(object): 19 | """ Encapsulates an EClientSocket instance, and proxies attribute 20 | lookup to it. 21 | 22 | """ 23 | client = None 24 | 25 | def __init__(self, dispatcher): 26 | """ Initializer. 27 | 28 | @param dispatcher message dispatcher instance 29 | """ 30 | self.dispatcher = dispatcher 31 | self.clientMethodNames = [m[0] for m in clientSocketMethods] 32 | 33 | def connect(self, host, port, clientId, handler, clientType=EClientSocket): 34 | """ Creates a TWS client socket and connects it. 35 | 36 | @param host name of host for connection; default is localhost 37 | @param port port number for connection; default is 7496 38 | @param clientId client identifier to send when connected 39 | @param handler object to receive reader messages 40 | @keyparam clientType=EClientSocket callable producing socket client 41 | @return True if connected, False otherwise 42 | """ 43 | def reconnect(): 44 | self.client = client = clientType(handler) 45 | client.eConnect(host, port, clientId) 46 | return client.isConnected() 47 | self.reconnect = reconnect 48 | return self.reconnect() 49 | 50 | def disconnect(self): 51 | """ Disconnects the client. 52 | 53 | @return True if disconnected, False otherwise 54 | """ 55 | client = self.client 56 | if client and client.isConnected(): 57 | client.eDisconnect() 58 | return not client.isConnected() 59 | return False 60 | 61 | def __getattr__(self, name): 62 | """ x.__getattr__('name') <==> x.name 63 | 64 | @return named attribute from EClientSocket object 65 | """ 66 | try: 67 | value = getattr(self.client, name) 68 | except (AttributeError, ): 69 | raise 70 | if name not in self.clientMethodNames: 71 | return value 72 | return value 73 | preName, postName = name+'Pre', name+'Post' 74 | preType, postType = registry[preName], registry[postName] 75 | @wraps(value) 76 | def wrapperMethod(*args): 77 | mapping = dict(zip(preType.__slots__, args)) 78 | results = self.dispatcher(preName, mapping) 79 | if not all(results): 80 | return # raise exception instead? 81 | result = value(*args) 82 | self.dispatcher(postName, mapping) 83 | return result # or results? 84 | return wrapperMethod 85 | -------------------------------------------------------------------------------- /app/ib/sym/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | def durationMethod(k): 6 | def m(cls, val): 7 | return '%s %s' % (val, k) 8 | return classmethod(m) 9 | 10 | 11 | class HDDuration: 12 | seconds = durationMethod('S') 13 | days = durationMethod('D') 14 | weeks = durationMethod('W') 15 | months = durationMethod('M') 16 | years = durationMethod('Y') 17 | 18 | 19 | class HDBar: 20 | sec = sec1 = '1 sec' 21 | sec5 = '5 secs' 22 | sec15 = '15 secs' 23 | sec30 = '30 secs' 24 | min1 = '1 min' 25 | min2 = '2 mins' 26 | min5 = '5 mins' 27 | min15 = '15 mins' 28 | min30 = '30 mins' 29 | hour = hour1 = '1 hour' 30 | day = day1 = '1 day' 31 | week = week1 = '1 week' 32 | month = month1 = '1 month' 33 | month3 = '3 months' 34 | year = year1 = '1 year' 35 | 36 | 37 | class HDShow: 38 | trades = 'TRADES' 39 | mid = 'MIDPOINT' 40 | bid = 'BID' 41 | ask = 'ASK' 42 | bid_ask = 'BID/ASK' 43 | 44 | class HDDateFormat: 45 | long = 1 # yyyymmdd{space}{space}hh:mm:dd 46 | short = 2 # 1/1/1970 GMT 47 | 48 | 49 | class YesNo: 50 | no = false = 0 51 | yes = true = 1 52 | 53 | 54 | class RTH(YesNo): 55 | pass 56 | 57 | 58 | class AllOrNone(YesNo): 59 | pass 60 | 61 | class Override(YesNo): 62 | pass 63 | 64 | class FirmQuoteOnly(YesNo): 65 | pass 66 | 67 | class ETradeOnly(YesNo): 68 | pass 69 | 70 | class ContinuousUpdate(YesNo): 71 | pass 72 | 73 | 74 | class AuctionStrategy: 75 | match = 1 76 | improvement = 2 77 | transparent = 3 78 | 79 | class ServerLogLevel: 80 | system, error, warning, information, detail = \ 81 | sys, err, warn, info, det = range(1, 6) 82 | 83 | 84 | class FaDataType: 85 | groups, profile, account_aliases = range(1, 4) 86 | 87 | 88 | class ExerciseAction: 89 | exercise, lapse = range(1, 3) 90 | 91 | 92 | class TriggerMethod: 93 | default = 0 94 | double_askbid = 1 95 | last = 2 96 | double_last = 3 97 | 98 | 99 | class ShortSaleSlot: 100 | unapplicable = 0 101 | clearing_broker = 1 102 | third_party = 2 103 | 104 | 105 | class OcaType: 106 | cancel_on_fill_block = 1 107 | reduce_on_fill_block = 2 108 | reduce_on_fill_noblock = 3 109 | 110 | 111 | class Rule80a: 112 | individual = 'I' 113 | agency = 'A' 114 | agent_other_member = 'W' 115 | individual_ptia = 'J' 116 | agency_ptia = 'U' 117 | agent_other_member_ptia = 'M' 118 | individual_pt = 'K' 119 | agency_pt = 'Y' 120 | agent_other_member_pt = 'N' 121 | 122 | 123 | class RefPriceType: 124 | avg = 1 125 | bidask = 2 126 | 127 | class VolatilityType: 128 | daily = 1 129 | annual = 2 130 | 131 | 132 | class GenericTickTypes: 133 | option_volume = 100 134 | option_open_interest = 101 135 | historical_volatility = 104 136 | option_implied_volatility = 106 137 | index_future_premium = 162 138 | misc_stats = 165 139 | mark_price = 221 140 | auction_values = 225 141 | shortable = 236 142 | 143 | 144 | class TickValues: 145 | low_13_week = 15 146 | high_13_week = 16 147 | low_26_week = 17 148 | high_26_week = 18 149 | low_52_week = 19 150 | high_52_week = 20 151 | avg_volume = 21 152 | option_historical_vol = 23 153 | option_implied_vol = 24 154 | option_call_open_interest = 27 155 | option_put_open_interest = 28 156 | option_call_volume = 29 157 | option_put_volume = 30 158 | index_future_premium = 31 159 | auction_volume = 34 160 | auction_price = 35 161 | auction_imbalance = 36 162 | mark_price = 37 163 | shortable = 46 164 | -------------------------------------------------------------------------------- /app/ib/wiki/GettingStarted.md: -------------------------------------------------------------------------------- 1 | Getting Started - instructions for installation and general use 2 | 3 | # Synopsis 4 | 5 | from ib.opt import ibConnection, message 6 | 7 | def my_account_handler(msg): 8 | ... do something with account msg ... 9 | 10 | def my_tick_handler(msg): 11 | ... do something with market data msg ... 12 | 13 | connection = ibConnection() 14 | connection.register(my_account_handler, 'UpdateAccountValue') 15 | connection.register(my_tick_handler, 'TickSize', 'TickPrice') 16 | connection.connect() 17 | connection.reqAccountUpdates(...) 18 | 19 | # Details 20 | 21 | IbPy provides an optional interface that does not require subclassing. This interface lives in the ib.opt package, and provides several conveniences for your use. 22 | 23 | To interoperate with this package, first define your handlers. Each handler must take a single parameter, a Message instance. Instances of Message have attributes and values set by the connection object before they're passed to your handler. 24 | 25 | After your handlers are defined, you associate them with the connection object via the register method. You pass your handler as the first parameter, and you indicate what message types to send it with parameters that follow it. Message types can be strings, or better, Message classes. Both forms are shown here: 26 | 27 | connection.register(my_account_handler, 'UpdateAccountValue') 28 | connection.register(my_tick_handler, message.TickPrice, message.TickSize) 29 | 30 | You can break the association between your handlers and messages with the unregister method, like so: 31 | 32 | connection.unregister(my_tick_handler, message.TickSize) 33 | 34 | In the above example, my_tick_handler will still be called with TickPrice messages. 35 | 36 | Connection objects also allow you to associate a handler with all messages generated. The call looks like this: 37 | 38 | connection.registerAll(my_generic_handler) 39 | 40 | And of course, there's an unregisterAll method as well: 41 | 42 | connection.unregisterAll(my_generic_handler) 43 | 44 | ## Attributes 45 | The Connection class exposes the attributes of its connection, so you can write: 46 | 47 | connection.reqIds() 48 | 49 | ## Logging 50 | The Connection class provides a basic logging facility (via the Python logging module). To activate it, call it like this: 51 | 52 | connection.enableLogging() 53 | 54 | To deactivate logging, call the same method with False as the first parameter: 55 | 56 | connection.enableLogging(False) 57 | 58 | ## Message Objects 59 | Your handlers are passed a single parameter, an instance of the Message class (or one of its subclasses). These instances will have attributes that match the parameter names from the underlying method call. For example, when you're passed a Message instance generated from a TickSize call, the object might look like this: 60 | 61 | msg.tickerId = 4 62 | msg.field = 3 63 | msg.size = 100 -------------------------------------------------------------------------------- /app/ib/wiki/ThreadAndGUINotes.md: -------------------------------------------------------------------------------- 1 | Using GUI frameworks and Threads - how to use IbPy with various GUI frameworks and threading models 2 | 3 | # GUI Frameworks 4 | IbPy should work with any GUI framework available to Python. If you have problems integrating with a particular framework, please post a message to the mailing list with the details. 5 | 6 | # Thread Models 7 | IbPy plays nice with three different thread models: native Python threads, Qt3 and Qt4 threads. If the package detects a prior import of Qt3 or Qt4, it will select the appropriate thread class for the (internal) EReader instance. The only requirement is that you must import Qt3 or Qt4 first. 8 | 9 | from PyQt4 import QtGui 10 | from ib.opt import ibConnection ## this works 11 | from ib.opt import ibConnection ## wrong thread model selected! 12 | from qt import * 13 | 14 | Other threading models (e.g., GTK) should work normally. If you have problems, please post a message to the mailing list with details. -------------------------------------------------------------------------------- /app/ib/wiki/ibPyOptional.md: -------------------------------------------------------------------------------- 1 | # Synopsis 2 | from ib.opt import ibConnection, message 3 | 4 | def my_account_handler(msg): 5 | ... do something with account msg ... 6 | 7 | def my_tick_handler(msg): 8 | ... do something with market data msg ... 9 | 10 | connection = ibConnection() 11 | connection.register(my_account_handler, 'UpdateAccountValue') 12 | connection.register(my_tick_handler, 'TickSize', 'TickPrice') 13 | connection.connect() 14 | connection.reqAccountUpdates(...) 15 | 16 | # Details 17 | IbPy provides an optional interface that does not require subclassing. This interface lives in the ib.opt package, and provides several conveniences for your use. 18 | 19 | To interoperate with this package, first define your handlers. Each handler must take a single parameter, a Message instance. Instances of Message have attributes and values set by the connection object before they're passed to your handler. 20 | 21 | After your handlers are defined, you associate them with the connection object via the register method. You pass your handler as the first parameter, and you indicate what message types to send it with parameters that follow it. Message types can be strings, or better, Message classes. Both forms are shown here: 22 | 23 | connection.register(my_account_handler, 'UpdateAccountValue') 24 | connection.register(my_tick_handler, message.TickPrice, message.TickSize) 25 | 26 | You can break the association between your handlers and messages with the unregister method, like so: 27 | 28 | connection.unregister(my_tick_handler, message.TickSize) 29 | 30 | In the above example, my_tick_handler will still be called with TickPrice messages. 31 | 32 | Connection objects also allow you to associate a handler with all messages generated. The call looks like this: 33 | 34 | connection.registerAll(my_generic_handler) 35 | 36 | And of course, there's an unregisterAll method as well: 37 | 38 | connection.unregisterAll(my_generic_handler) 39 | 40 | ## Attributes 41 | The Connection class exposes the attributes of its connection, so you can write: 42 | 43 | connection.reqIds() 44 | 45 | ## Logging 46 | The Connection class provides a basic logging facility (via the Python logging module). To activate it, call it like this: 47 | 48 | connection.enableLogging() 49 | 50 | To deactivate logging, call the same method with False as the first parameter: 51 | 52 | connection.enableLogging(False) 53 | 54 | ## Message Objects 55 | Your handlers are passed a single parameter, an instance of the Message class (or one of its subclasses). These instances will have attributes that match the parameter names from the underlying method call. For example, when you're passed a Message instance generated from a TickSize call, the object might look like this: 56 | 57 | msg.tickerId = 4 58 | msg.field = 3 59 | msg.size = 100 -------------------------------------------------------------------------------- /app/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """ IBREST API Flask app controller file. This file establishes the routes used for the IBREST API. More info at: 3 | https://github.com/hamx0r/IBREST 4 | 5 | Most of this API takes HTTP requests and translates them to EClientSocket Methods: 6 | https://www.interactivebrokers.com/en/software/api/apiguide/java/java_eclientsocket_methods.htm 7 | 8 | The HTTP response is handled by compiling messages from EWrappper Methods into JSON: 9 | https://www.interactivebrokers.com/en/software/api/apiguide/java/java_ewrapper_methods.htm 10 | """ 11 | 12 | # DO THIS FIRST! Logging import and setup 13 | import logging 14 | 15 | log_format = '%(asctime)s %(levelname)-5.5s [%(name)s-%(funcName)s:%(lineno)d][%(threadName)s] %(message)s' 16 | logging.basicConfig(format=log_format, level=logging.DEBUG) 17 | # Flask imports 18 | from flask import Flask, request 19 | from flask_restful import Resource, Api, reqparse, abort 20 | # IBREST imports 21 | import sync, feeds 22 | import parsers 23 | import globals as g 24 | import utils 25 | import json 26 | import time 27 | import os 28 | import connection 29 | # Beacon imports 30 | import requests 31 | from datetime import datetime 32 | from functools import wraps 33 | from ib.opt import ibConnection 34 | # database setup 35 | from database import init_db, FilledOrders, Commissions 36 | init_db() 37 | 38 | __author__ = 'Jason Haury' 39 | 40 | app = Flask(__name__) 41 | api = Api(app) 42 | 43 | # Logger for this module 44 | log = logging.getLogger(__name__) 45 | 46 | 47 | # --------------------------------------------------------------------- 48 | # Authentication 49 | # --------------------------------------------------------------------- 50 | def authenticate(func): 51 | @wraps(func) 52 | def wrapper(*args, **kwargs): 53 | if not getattr(func, 'authenticated', True) or g.serializer is None: 54 | return func(*args, **kwargs) 55 | 56 | # authorized is true when the token from the decrypted Beacon-Token matches either our current or last token 57 | # log.debug('Auth looking for flare in {}'.format(request)) 58 | flare = request.headers.get('Beacon-Flare', None) 59 | # log.debug('Auth flare: {}'.format(flare)) 60 | if flare is None: 61 | log.warn('No Beacon-Flare in header') 62 | abort(401) 63 | 64 | # TODO remove this hack when not developing 65 | if flare == '1p2o3i4u5y': 66 | log.warn('Using vulnerable Beacon-Flare') 67 | return func(*args, **kwargs) 68 | 69 | ibrest_info = g.serializer.loads(flare) 70 | authorized = False 71 | # if ibrest_info['ip'] == g.current_ip: 72 | if ibrest_info['token'] in [g.beacon_current_token, g.beacon_last_token]: 73 | authorized = True 74 | 75 | log.debug('Authorized = {}'.format(authorized)) 76 | if authorized: 77 | return func(*args, **kwargs) 78 | 79 | abort(401) 80 | 81 | return wrapper 82 | 83 | 84 | def send_flare_to_gae(): 85 | token = datetime.now().strftime('%D_%H:%M') 86 | # we want to save our last token in case we get a bit out of sync - both can be valid for a while 87 | if token == g.beacon_current_token: 88 | # Nothing's changed since the last time this endpoint was called, so return now 89 | return 90 | else: 91 | # Things have changed, so update our last and current tokens 92 | g.beacon_last_token = g.beacon_current_token 93 | g.beacon_current_token = token 94 | 95 | # Since our token has been updated, we need to tell GAE about this 96 | # First get our secret key from env vars 97 | secret_key = g.id_secret_key 98 | if secret_key is None: 99 | log.error('No secret key') 100 | return None, 500 101 | 102 | # We want to tell GAE our IP address too 103 | # curl -H "Metadata-Flavor: Google" http://metadata/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip 104 | # headers = {'Metadata-Flavor': 'Google'} 105 | # metadata_url = "http://metadata/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip" 106 | # ip = requests.get(metadata_url, headers=headers).text 107 | # if ip is not None: 108 | # g.current_ip = ip 109 | 110 | # set up a response for be encrypted & signed 111 | # resp = {'ip': ip, 'token': token} 112 | resp = {'token': token} 113 | # Now sign it, seal it...: 114 | flare = g.serializer.dumps(resp) 115 | resp['payload'] = flare 116 | 117 | # ...and deliver it: 118 | put_resp = requests.put('https://orion-minute.appspot.com/beacon', data={'flare': flare}).text 119 | resp['put_resp'] = put_resp 120 | log.debug('Beacon: {}'.format(resp)) 121 | 122 | return resp 123 | 124 | 125 | # --------------------------------------------------------------------- 126 | # RESOURCES 127 | # --------------------------------------------------------------------- 128 | class History(Resource): 129 | """ Resource to handle requests for historical data (15min delayed) 130 | """ 131 | 132 | method_decorators = [authenticate] 133 | 134 | def get(self, symbol): 135 | """ Uses reqHistoricalData() to start a stream of historical data, then upon getting data in that streatm, 136 | cancels the stream with cancelHistoricalData() before returning the history 137 | """ 138 | return utils.make_response(feeds.get_history(symbol, request.args)) 139 | 140 | 141 | class Market(Resource): 142 | """ Resource to handle requests for market data 143 | """ 144 | 145 | method_decorators = [authenticate] 146 | 147 | def get(self, symbol): 148 | """ 149 | :return: JSON dict of dicts, with main keys being tickPrice, tickSize and optionComputation. 150 | """ 151 | # TODO add query string params for Contract, and create feed accordingly 152 | return utils.make_response(feeds.get_market_data(symbol, request.args)) 153 | 154 | 155 | class Order(Resource): 156 | """ Resource to handle requests for Order 157 | """ 158 | method_decorators = [authenticate] 159 | 160 | def get(self): 161 | """ Retrieves details of open orders using reqAllOpenOrders() 162 | """ 163 | return utils.make_response(sync.get_open_orders()) 164 | 165 | def post(self): 166 | """ Places an order with placeOrder(). This requires enough args to create a Contract & an Order: 167 | https://www.interactivebrokers.com/en/software/api/apiguide/java/java_socketclient_properties.htm 168 | 169 | To allow bracketed, a JSON list may be posted in the body with each list object being an order. Arg 170 | parsing does not happen in this case 171 | http://interactivebrokers.github.io/tws-api/bracket_order.html 172 | 173 | Note: This implies the JSON list starts with an order to open a position followed by 1-2 orders for closing 174 | that position (profit taker, loss stopper) 175 | 176 | Option orders with Combo Legs can also be made with an order dict in a JSON list with secType = 'BAG' 177 | """ 178 | # Detect a JSON object being posted 179 | # Convert to not-unicode 180 | all_args = request.json 181 | all_args = json.dumps(all_args) 182 | all_args = json.loads(all_args, object_hook=utils.json_object_hook) 183 | log.debug('all_args: {}'.format(all_args)) 184 | # If there was no JSON object, then use query string params 185 | if all_args is None: 186 | parser = parsers.order_parser.copy() 187 | for arg in parsers.contract_parser.args: 188 | parser.add_argument(arg) 189 | args = parser.parse_args() 190 | 191 | all_args = {k: v for k, v in request.values.iteritems()} 192 | # update with validated data 193 | for k, v in args.iteritems(): 194 | all_args[k] = v 195 | 196 | return utils.make_response(sync.place_order(all_args)) 197 | 198 | def delete(self): 199 | """ Cancels order with cancelOrder() 200 | """ 201 | parser = reqparse.RequestParser(bundle_errors=True) 202 | parser.add_argument('orderId', type=int, required=True, 203 | help='Order ID to cancel') 204 | args = parser.parse_args() 205 | return utils.make_response(sync.cancel_order(args['orderId'])) 206 | 207 | 208 | class OrderFilled(Resource): 209 | """ Resource to get filled orders. 210 | """ 211 | method_decorators = [authenticate] 212 | 213 | 214 | def get(self): 215 | """ Retrieves details of filled orders using stored data in SQLite DB 216 | """ 217 | parser = reqparse.RequestParser(bundle_errors=True) 218 | parser.add_argument('orderId', type=int, required=False, 219 | help='Order ID to get ExecutionReport for') 220 | args = parser.parse_args() 221 | orderId = args.get('orderId') 222 | 223 | if orderId is None: 224 | # Get all filled orders available 225 | resp = FilledOrders.query.all() 226 | else: 227 | resp = FilledOrders.query.filter(FilledOrders.order_id == orderId).first() 228 | resp = [r.order_status for r in resp] 229 | return utils.make_response(resp) 230 | 231 | 232 | 233 | class OrderOCA(Resource): 234 | """ Resource to handle requests for Bracket-like OCA Orders 235 | 236 | Takes a JSON list of Orders. Item 0 is always considered to be the opening order of a position, and the rest are 237 | the OCA group to close the position. 238 | 239 | This behaves like an elaborate Bracketed Order, but the logic is handled by IBREST instead of the IB GW client since 240 | this OCA groups are meant to work on a preexisting position. 241 | """ 242 | method_decorators = [authenticate] 243 | 244 | def post(self): 245 | # Detect a JSON object being posted 246 | # Convert to not-unicode 247 | all_args = request.json 248 | all_args = json.dumps(all_args) 249 | all_args = json.loads(all_args, object_hook=utils.json_object_hook) 250 | return utils.make_response(sync.place_order_oca(all_args)) 251 | 252 | 253 | class PortfolioPositions(Resource): 254 | """ Resource to handle requests for market data 255 | """ 256 | method_decorators = [authenticate] 257 | 258 | def get(self): 259 | """ 260 | :return: JSON dict of dicts, with main keys being tickPrice, tickSize and optionComputation. 261 | """ 262 | return utils.make_response(sync.get_portfolio_positions()) 263 | 264 | 265 | class AccountSummary(Resource): 266 | """ Resource to handle requests for account summary information 267 | """ 268 | 269 | method_decorators = [authenticate] 270 | 271 | def get(self): 272 | """ 273 | One may either provide a CSV string of `tags` desired, or else provide duplicate query string `tag` values 274 | which the API will then put together in a CSV list as needed by IbPy 275 | :return: JSON dict of dicts 276 | """ 277 | choices = {"AccountType", "NetLiquidation", "TotalCashValue", "SettledCash", "AccruedCash", "BuyingPower", 278 | "EquityWithLoanValue", "PreviousDayEquityWithLoanValue", "GrossPositionValue", "RegTEquity", 279 | "RegTMargin", "SMA", "InitMarginReq", "MaintMarginReq", "AvailableFunds", "ExcessLiquidity", 280 | "Cushion", "FullInitMarginReq", "FullMaintMarginReq", "FullAvailableFunds", "FullExcessLiquidity", 281 | "LookAheadNextChange", "LookAheadInitMarginReq", "LookAheadMaintMarginReq", 282 | "LookAheadAvailableFunds", "LookAheadExcessLiquidity", "HighestSeverity", "DayTradesRemaining", 283 | "Leverage"} 284 | parser = reqparse.RequestParser(bundle_errors=True) 285 | parser.add_argument('tags', type=str, help='CSV list of tags from this set: {}'.format(choices), trim=True) 286 | parser.add_argument('tag', type=str, action='append', help='Account information you want to see: {error_msg}', 287 | trim=True, choices=choices, default=[]) 288 | # NOTE beware that flask will reject GET requests if there's a Content-Type in the header with an error: 289 | # "message": "The browser (or proxy) sent a request that this server could not understand." 290 | 291 | args = parser.parse_args() 292 | # Make a master list of tags from all possible arguments 293 | tags = args['tag'] 294 | tags += args['tags'].split(',') if args['tags'] is not None else [] 295 | if len(tags) == 0: 296 | # No tags were passed, so throw an error 297 | return dict(message=dict(tags='Must provide 1 or more `tag` args, and/or a CSV `tags` arg')), 400 298 | # Reduce and re-validate 299 | tags = set(tags) 300 | if not tags.issubset(choices): 301 | return dict(message=dict(tags='All tags must be from this set: {}'.format(choices))), 400 302 | # re-create CSV list 303 | tags = ','.join(list(tags)) 304 | # debug('TAGS: {}'.format(tags)) 305 | return utils.make_response(sync.get_account_summary(tags)) 306 | 307 | 308 | class AccountUpdate(Resource): 309 | """ Resource to handle requests for account update information. 310 | """ 311 | method_decorators = [authenticate] 312 | 313 | def get(self): 314 | """ 315 | This endpoint does _not_ subscribe to account info (hence "Update" instead of "Updates" - use feed for that), 316 | but only gets latest info for given acctCode. 317 | :return: JSON dict of dicts 318 | """ 319 | parser = reqparse.RequestParser() 320 | parser.add_argument('acctCode', type=str, help='Account number/code', trim=True, required=True) 321 | args = parser.parse_args() 322 | return utils.make_response(sync.get_account_update(args['acctCode'])) 323 | 324 | class Executions(Resource): 325 | """ Resource to handle requests for recent executions. 326 | """ 327 | method_decorators = [authenticate] 328 | 329 | def get(self): 330 | """ Use optional filter params in querystring to retrieve execDetails from past 24hrs (IB API limitation): 331 | https://www.interactivebrokers.com/en/software/api/apiguide/java/executionfilter.htm 332 | """ 333 | parser = reqparse.RequestParser() 334 | parser.add_argument('acctCode', type=str, help='Account number/code to Filter', trim=True, required=False) 335 | parser.add_argument('clientId', type=str, help='Client ID to Filter', trim=True, required=False) 336 | parser.add_argument('exchange', type=str, help='Exhange to Filter', trim=True, required=False) 337 | parser.add_argument('secType', type=str, help='Security Type to Filter', trim=True, required=False) 338 | parser.add_argument('side', type=str, help='Side to Filter', trim=True, required=False) 339 | parser.add_argument('time', type=str, help='Time (yyyymmdd-hh:mm:ss) to Filter', trim=True, required=False) 340 | 341 | args = parser.parse_args() 342 | return utils.make_response(sync.get_executions(args)) 343 | 344 | 345 | class ExecutionCommissions(Resource): 346 | """ Resource to get CommissionReports from past executions. No guarantee as DB is wiped every time docker container 347 | is created. 348 | """ 349 | method_decorators = [authenticate] 350 | 351 | def get(self, execId=None): 352 | """ Retrieves details of filled orders using stored data in SQLite DB 353 | """ 354 | parser = reqparse.RequestParser(bundle_errors=True) 355 | parser.add_argument('execId', type=int, required=False, 356 | help='Execution ID to get CommissionReport for') 357 | args = parser.parse_args() 358 | execId = args.get('execId') 359 | 360 | if execId is None: 361 | # Get all filled orders available 362 | resp = Commissions.query.all() 363 | else: 364 | resp = Commissions.query.filter(Commissions.exec_id == execId).first() 365 | resp = [r.commission_report for r in resp] 366 | return utils.make_response(resp) 367 | 368 | 369 | 370 | 371 | class ClientState(Resource): 372 | """ Explore what the connection state for client connection to TWS 373 | """ 374 | method_decorators = [authenticate] 375 | 376 | def get(self): 377 | resp = dict(connected=dict()) 378 | resp['connected'][g.client_id] = g.client_connection.isConnected() 379 | return utils.make_response(resp) 380 | 381 | 382 | class Beacon(Resource): 383 | def get(self): 384 | """ A GET here causes a PUT to our GAE App with needed info. GETs initiated by GAE or a cron job with: 385 | */5 13-20 * * 1-5 curl -k https://localhost/beacon 386 | """ 387 | agent = request.headers.get('User-Agent') 388 | ip = request.remote_addr # 172.17.0.1 means from container host 389 | 390 | # log.debug('Agent: "{}", IP: {}'.format(agent, request.remote_addr)) 391 | # If done with curl locally: curl/7.26.0 392 | if agent != 'AppEngine-Google; (+http://code.google.com/appengine; appid: s~orion-minute)' \ 393 | and ip != '172.17.0.1': 394 | msg = 'Unexpected user agent ({}) or IP ({})'.format(agent, ip) 395 | log.error(msg) 396 | return None, 400 397 | 398 | return send_flare_to_gae() 399 | 400 | 401 | class Test(Resource): 402 | def get(self): 403 | resp = {k: str(v) for k, v in request.environ.iteritems()} 404 | 405 | log.debug('Environment vars: {}'.format(resp)) 406 | return resp 407 | 408 | 409 | class Hello(Resource): 410 | def get(self): 411 | return dict(msg="Hello World! Here's info on the client used to connect to IBGW", 412 | clientId=g.client_id, 413 | connected=g.client_connection.isConnected()) 414 | 415 | 416 | # --------------------------------------------------------------------- 417 | # ROUTING 418 | # --------------------------------------------------------------------- 419 | api.add_resource(History, '/history/') 420 | api.add_resource(Market, '/market/') 421 | api.add_resource(Order, '/order') 422 | api.add_resource(OrderOCA, '/order/oca') 423 | api.add_resource(OrderFilled, '/order/filled') 424 | api.add_resource(PortfolioPositions, '/account/positions') 425 | api.add_resource(AccountSummary, '/account/summary') 426 | api.add_resource(AccountUpdate, '/account/update') 427 | api.add_resource(Executions, '/executions') 428 | api.add_resource(ExecutionCommissions, '/executions/commissions') 429 | api.add_resource(ClientState, '/clients') 430 | api.add_resource(Beacon, '/beacon') 431 | api.add_resource(Test, '/test') 432 | api.add_resource(Hello, '/') 433 | 434 | # --------------------------------------------------------------------- 435 | # SETUP CLIENTS 436 | # --------------------------------------------------------------------- 437 | 438 | 439 | log.debug('Using IB GW client at: {}:{}'.format(g.client_connection.host, g.client_connection.port)) 440 | 441 | if __name__ == '__main__': 442 | host = os.getenv('IBREST_HOST', '127.0.0.1') 443 | port = int(os.getenv('IBREST_PORT', '443')) 444 | client_id = g.client_id 445 | 446 | # Set up our client connection with IBGW 447 | client = ibConnection(g.ibgw_host, g.ibgw_port, client_id) 448 | connection.setup_client(client) 449 | client.connect() 450 | g.client_connection = client 451 | # g.clientId_pool = [client_id] 452 | 453 | # Call our own beacon code to register with GAE 454 | if g.serializer is not None: 455 | log.debug('Sent flare to GAE with response: {}'.format(send_flare_to_gae())) 456 | else: 457 | log.debug('No beacon flare sent. No ID_SECRET_KEY found in environment: {}. Client ID: {}'.format(os.getenv('ID_SECRET_KEY'), client_id)) 458 | 459 | 460 | # When runnning with werkzeug, we already get good logging to stdout, so disabble loggers 461 | # root.setLevel(logging.ERROR) 462 | log.debug('Setting up IBREST at {}:{}'.format(host, port)) 463 | context = ('ibrest.crt', 'ibrest.key') 464 | 465 | # Log to file to since Docker isn't doing it for use 466 | # Add rotating file log handler 467 | # from logging.handlers import TimedRotatingFileHandler 468 | # 469 | # hdlr_file = TimedRotatingFileHandler('ibrest.log', when='D', backupCount=5) 470 | # hdlr_file.setLevel(logging.DEBUG) 471 | # hdlr_file.setFormatter(logging.Formatter(log_format)) 472 | # logging.getLogger().addHandler(hdlr_file) 473 | 474 | 475 | 476 | DEBUG = False 477 | # For HTTPS with or without debugging 478 | app.run(debug=DEBUG, host=host, port=port, ssl_context=context) 479 | # app.run(debug=DEBUG, host=host, port=port) 480 | 481 | 482 | 483 | # For HTTP (take note of port) 484 | # app.run(debug=False, host=host, port=port, threaded=True) 485 | -------------------------------------------------------------------------------- /app/parsers.py: -------------------------------------------------------------------------------- 1 | """ Flask-RESTful request parsers which help enforce argument needs for IB types: 2 | * Order 3 | * Contract 4 | """ 5 | from flask_restful import reqparse 6 | 7 | __author__ = 'Jason Haury' 8 | 9 | # --------------------------------------------------------------------- 10 | # ORDER PARSER 11 | # --------------------------------------------------------------------- 12 | # Contains all args used for Order objects: 13 | # https://www.interactivebrokers.com/en/software/api/apiguide/java/order.htm 14 | order_parser = reqparse.RequestParser(bundle_errors=True) 15 | # Order args https://www.interactivebrokers.com/en/software/api/apiguide/java/order.htm 16 | # Order types https://www.interactivebrokers.com/en/software/api/apiguide/tables/supported_order_types.htm 17 | order_parser.add_argument('totalQuantity', type=int, required=True, help='Total Quantity to order', store_missing=False) 18 | order_parser.add_argument('minQty', type=int, help='Min Quantity to order', store_missing=False) 19 | order_parser.add_argument('orderId', type=int, help='Order ID', store_missing=False) 20 | order_parser.add_argument('trailingPercent', type=float, help='Trailing Stop percent', store_missing=False) 21 | order_parser.add_argument('action', type=str, required=False, help='Must be BUY, SELL or SHORT', store_missing=False) 22 | order_parser.add_argument('tif', type=str, help='Time in force', choices=['DAY', 'GTC', 'IOC', 'GTD'], store_missing=False) 23 | 24 | 25 | # --------------------------------------------------------------------- 26 | # CONTRACT PARSER 27 | # --------------------------------------------------------------------- 28 | # Contains all args used for Contract objects: 29 | # https://www.interactivebrokers.com/en/software/api/apiguide/java/contract.htm 30 | contract_parser = reqparse.RequestParser(bundle_errors=True) 31 | # clientId is handled by sync code 32 | contract_parser.add_argument('symbol', type=str, required=False, help='Stock ticker symbol to order', store_missing=False) 33 | contract_parser.add_argument('orderType', type=str, required=False, help='Type of Order to place', 34 | choices=['LMT', 'MTL', 'MKT PRT', 'QUOTE', 'STP', 'STP LMT', 'TRAIL LIT', 'TRAIL MIT', 35 | 'TRAIL', 'TRAIL LIMIT', 'MKT', 'MIT', 'MOC', 'MOO', 'PEG MKT', 'REL', 'BOX TOP', 36 | 'LOC', 'LOO', 'LIT', 'PEG MID', 'VWAP', 'GAT', 'GTD', 'GTC', 'IOC', 'OCA', 'VOL'], store_missing=True) 37 | contract_parser.add_argument('secType', type=str, required=False, default='STK', help='Security Type', 38 | choices=['STK', 'OPT', 'FUT', 'IND', 'FOP', 'CASH', 'BAG', 'NEWS'], store_missing=True) 39 | contract_parser.add_argument('exchange', type=str, required=False, default='SMART', help='Exchange (ie NASDAQ, SMART)', store_missing=True) 40 | contract_parser.add_argument('currency', type=str, required=False, default='USD', 41 | help='Currency used for order (ie USD, GBP))', store_missing=True) -------------------------------------------------------------------------------- /app/sync.py: -------------------------------------------------------------------------------- 1 | """ Synchronous wrapper on IbPy to do heavy lifting for our Flask app. 2 | This module contains all IB client handling, even if connection will be used for a feed 3 | """ 4 | import connection 5 | import globals as g 6 | 7 | # from flask import g 8 | from ib.ext.Contract import Contract 9 | from ib.ext.Order import Order 10 | from ib.ext.ComboLeg import ComboLeg 11 | from ib.ext.ExecutionFilter import ExecutionFilter 12 | import time 13 | import logging 14 | 15 | __author__ = 'Jason Haury' 16 | 17 | log = logging.getLogger(__name__) 18 | 19 | 20 | # --------------------------------------------------------------------- 21 | # ORDER FUNCTIONS 22 | # --------------------------------------------------------------------- 23 | def get_open_orders(): 24 | """ Uses reqAllOpenOrders to get all open orders from 25 | """ 26 | client = connection.get_client(0) 27 | if client is None: 28 | return g.error_resp[-2] 29 | elif client.isConnected() is False: 30 | return g.error_resp[-1] 31 | 32 | # Reset our order resp to prepare for new data 33 | g.order_resp = dict(openOrderEnd=False, openOrder=[], orderStatus=[]) 34 | client.reqAllOpenOrders() 35 | timeout = g.timeout 36 | while g.order_resp['openOrderEnd'] is False and client.isConnected() is True and timeout > 0: 37 | # log.debug("Waiting for Open Orders responses on client {}...".format(client.clientId)) 38 | time.sleep(0.25) 39 | timeout -= 1 40 | connection.close_client(client) 41 | return g.order_resp 42 | 43 | 44 | def cancel_order(orderId): 45 | """ Uses cancelOrder to cancel an order. The only response is what comes back right away (no EWrapper messages) 46 | """ 47 | g.error_resp[orderId] = None # Reset our error for later 48 | 49 | client = connection.get_client(0) 50 | if client is None: 51 | return g.error_resp[-2] 52 | elif client.isConnected() is False: 53 | return g.error_resp[-1] 54 | 55 | log.info('Cancelling order {}'.format(orderId)) 56 | # Reset our order resp to prepare for new data 57 | g.order_resp_by_order[orderId] = dict(openOrder=dict(), orderStatus=dict()) 58 | client.cancelOrder(int(orderId)) 59 | timeout = g.timeout 60 | while len(g.order_resp_by_order[orderId]['orderStatus']) == 0 and client.isConnected() is True and timeout > 0: 61 | # log.debug("Waiting for Cancel Order responses on client {}...".format(client.clientId)) 62 | if g.error_resp[orderId] is not None: 63 | connection.close_client(client) 64 | return g.error_resp[orderId] 65 | time.sleep(0.25) 66 | timeout -= 1 67 | connection.close_client(client) 68 | resp = g.order_resp.copy() 69 | # Cancelling an order also produces an error, we'll capture that here too 70 | resp['error'] = g.error_resp[orderId] 71 | return resp 72 | 73 | 74 | def wait_for_responses(order_ids, client, timeout, 75 | status_list=['Filled', 'Submitted', 'Presubmitted', 'Cancelled']): 76 | """ Takes a set of 'order_ids' and waits 'timeout' quarter-seconds for some kind of response (error or other) from 77 | the open client connection. 78 | 79 | 'status_list' is list of string includeing: Filled, Submitted, Presubmitted, Cancelled 80 | It dictates the order status levels that are sufficient to consider a response complete. 81 | 82 | Some orderIDs we don't want to wait for them to get a status in status_list, but simply want to have _something_ 83 | to return. 84 | """ 85 | # Reset our global order resp dicts to prepare for new data 86 | for orderId in order_ids: 87 | g.order_resp_by_order[orderId] = dict(openOrder=dict(), orderStatus=dict()) 88 | g.error_resp[orderId] = None # Reset our error for later 89 | 90 | # The only response for placing an order is an error, so we'll check for open orders and wait for this orderId or 91 | # and error to show up. 92 | client.reqOpenOrders() 93 | 94 | resp = {} 95 | errors = {} 96 | while order_ids and client.isConnected() is True and timeout > 0: 97 | new_order_ids = order_ids.copy() 98 | log.debug("Waiting for orderIds {} responses for {} more times...".format(order_ids, timeout)) 99 | for orderId in order_ids: 100 | if g.order_resp_by_order[orderId]['orderStatus'].get('status', None): 101 | order_resp = g.order_resp_by_order[orderId] 102 | partial_resp = {'status': order_resp['orderStatus']['status']} 103 | for k_o in ['m_totalQuantity', 'm_orderType', 'm_trailingPercent', 'm_auxPrice', 'm_lmtPrice']: 104 | try: 105 | partial_resp[k_o] = order_resp['openOrder']['order'][k_o] 106 | except: 107 | pass 108 | # Clean up further 109 | if partial_resp.get('m_orderType', 'TRAIL') != 'TRAIL': 110 | partial_resp.pop('m_trailingPercent') 111 | try: 112 | avgFillPrice = order_resp['openOrder']['order']['avgFillPrice'] 113 | except: 114 | avgFillPrice = 0.0 115 | # Add in Contract info 116 | try: 117 | partial_resp['m_symbol'] = order_resp['openOrder']['contract']['m_symbol'] 118 | except: 119 | pass 120 | # We complete our partial response for a symbol once its status is in `status_list` 121 | if partial_resp['status'] in status_list: 122 | if avgFillPrice != 0.0: partial_resp['avgFillPrice'] = avgFillPrice 123 | new_order_ids.discard(orderId) 124 | # log.debug('Order Status: {}'.format(order_resp['orderStatus'])) 125 | log.debug('Order {} partial response: {}'.format(orderId, partial_resp)) 126 | # # Merge in our responses to make a single response dict 127 | resp[orderId] = partial_resp.copy() 128 | elif g.error_resp[orderId] is not None: 129 | log.error('Error placing order: {}'.format(g.error_resp[orderId])) 130 | errors[orderId] = g.error_resp[orderId].copy() 131 | new_order_ids.discard(orderId) 132 | # Always remove orderIds which we should only look once for if we have _something_ to return 133 | collected_ids = set(resp.keys()) 134 | new_order_ids = new_order_ids - collected_ids 135 | order_ids = new_order_ids.copy() 136 | time.sleep(0.25) 137 | timeout -= 1 138 | # request a new set of open orders every second 139 | if timeout % 4 == 0: 140 | client.reqOpenOrders() 141 | 142 | # add in any errors we may have found 143 | if errors: 144 | log.error('Found these order errors: {}'.format(errors)) 145 | resp['errors'] = errors 146 | return resp 147 | 148 | 149 | def place_order(order_list): 150 | """ Auto-detects which args should be assigned to a new Contract or Order, then use to place order. 151 | Makes use of globals to set initial values, but allows args to override (ie clientId) 152 | 153 | To modify and order, the following parameters are needed (IB won't complain if some of these are missing, but the 154 | order update won't succeed): 155 | * orderId 156 | * exchange 157 | * totalQuantity 158 | * secType 159 | * action 160 | * orderType AND related paramters (ie TRAIL needs trailingPercent) 161 | * symbol 162 | * currency 163 | * exchange 164 | 165 | When an object in `order_list` has secType = 'BAG', it implies an Options combo order will be placed, requiring 166 | comboLegs: a JSON list of details required for this function to fetch the conId to then build the ComboLeg. 167 | """ 168 | log.debug('Starting place_order with args_list: {}'.format(order_list)) 169 | client = connection.get_client(0) 170 | if client is None: 171 | connection.close_client(client) 172 | return g.error_resp[-2] 173 | elif client.isConnected() is False: 174 | return g.error_resp[-1] 175 | 176 | # To allow for bracketed orders (or processing a string of orders in a single request), we expect args to be a list 177 | if not isinstance(order_list, list): 178 | order_list = [order_list] 179 | 180 | parentId = None 181 | order_ids = set() 182 | dont_wait_order_ids = set() # Some orders aren't worth waiting for responses on 183 | for args in order_list: 184 | # If an orderId was provided, we'll be updating an existing order, so only send attributes which are updatable: 185 | # totalQuantity, orderType, symbol, secType, action 186 | # TODO consider casting unicode to utf-8 here. Otherwise we get error(id=1, errorCode=512, errorMsg=Order Sending Error - char format require string of length 1) 187 | # log.debug('Processing args from order_list: {}'.format(args)) 188 | orderId = args.get('orderId', None) 189 | if orderId is None: 190 | orderId = g.orderId 191 | g.orderId += 1 192 | order_ids.add(orderId) 193 | if 'goodAfterTime' in args: 194 | dont_wait_order_ids.add(orderId) 195 | contract = Contract() 196 | order = Order() 197 | 198 | # Populate contract with appropriate args 199 | for attr in dir(contract): 200 | if attr[:2] == 'm_' and attr[2:] in args: 201 | setattr(contract, attr, args[attr[2:]]) 202 | # Populate order with appropriate 203 | order.m_clientId = client.clientId 204 | for attr in dir(order): 205 | if attr[:2] == 'm_' and attr[2:] in args: 206 | setattr(order, attr, args[attr[2:]]) 207 | 208 | # Option Combo Orders need the comboLegs details turned into actual ComboLeg objects 209 | comboLegs = args.get('comboLegs', None) 210 | if comboLegs: 211 | # We need to build ComboLegs by first fetching the conId from the contract details 212 | all_legs = [] 213 | req_ids = [] 214 | # Clear out our global ContractDetails so our handler can repopulate them 215 | g.contract_resp['contractDetails'] = dict() 216 | g.contract_resp['contractDetailsEnd'] = False 217 | 218 | # Request new ContractDetails so we can get the conIds needed for our legs 219 | for idx, leg in enumerate(comboLegs): 220 | # Each leg is a dict of details needed to make a ComboLeg object 221 | leg_contract = Contract() 222 | 223 | # Populate leg_contract with appropriate args 224 | for attr in dir(leg_contract): 225 | if attr[:2] == 'm_' and attr[2:] in leg: 226 | setattr(leg_contract, attr, leg[attr[2:]]) 227 | 228 | # Fetch conId for leg_contract 229 | client.reqContractDetails(idx, leg_contract) 230 | req_ids.append(idx) 231 | 232 | # We've now requested ContractDetails for all legs. Wait to get their async responses. 233 | timeout = g.timeout 234 | while g.contract_resp['contractDetailsEnd'] is False and client.isConnected() is True and timeout > 0: 235 | time.sleep(0.25) 236 | timeout -= 1 237 | 238 | # Create our ComboLegs for our order 239 | for idx, leg in enumerate(comboLegs): 240 | combo_leg = ComboLeg() 241 | # Populate combo_leg with appropriate args 242 | for attr in dir(combo_leg): 243 | if attr[:2] == 'm_' and attr[2:] in leg: 244 | setattr(combo_leg, attr, leg[attr[2:]]) 245 | combo_leg.m_conId = g.contract_resp['contractDetails'][idx]['m_summary'].m_conId 246 | all_legs.append(combo_leg) 247 | contract.m_comboLegs = all_legs 248 | 249 | # If this is a bracketed order, we'll need to add in the parentId for children orders 250 | if parentId: 251 | order.m_parentId = parentId 252 | 253 | log.debug('Placing order # {} on client # {} (connected={}): {}'.format(orderId, client.clientId, 254 | client.isConnected(), args)) 255 | client.placeOrder(orderId, contract, order) 256 | # Assume our 1st order in the list is the parent. Use this for remaining bracket orders and also error handling 257 | if not parentId: 258 | parentId = orderId 259 | log.debug('Setting child order parentId={}'.format(parentId)) 260 | 261 | log.debug('Ignoring responses for these orderIds: {}'.format(dont_wait_order_ids)) 262 | order_ids = order_ids - dont_wait_order_ids 263 | 264 | # Don't look for order status or errors until we actually transmit the last order, but then look for status for 265 | # all order_ids 266 | timeout = g.timeout 267 | resp = wait_for_responses(order_ids, client, timeout) 268 | connection.close_client(client) 269 | return resp 270 | 271 | 272 | def place_order_oca(order_list): 273 | """ Places a Bracket-like OCA set of orders. 274 | 275 | The 1st item in `order_list` is to open a position, and all remaining orders in list 276 | are an OCA group to close the position. IBREST creates the OCA group name by using the orderId. 277 | 278 | Auto-detects which args should be assigned to a new Contract or Order, then use to place order. 279 | Makes use of globals to set initial values, but allows args to override (ie clientId) 280 | 281 | To modify and order, the following parameters are needed (IB won't complain if some of these are missing, but the 282 | order update won't succeed): 283 | * orderId 284 | * exchange 285 | * totalQuantity 286 | * secType 287 | * action 288 | * orderType AND related paramters (ie TRAIL needs trailingPercent) 289 | * symbol 290 | * currency 291 | * exchange 292 | 293 | 294 | Setting `oca` to True implies 295 | """ 296 | log.debug('Starting place_order_oca with args_list: {}'.format(order_list)) 297 | client = connection.get_client(0) 298 | if client is None: 299 | connection.close_client(client) 300 | return g.error_resp[-2] 301 | elif client.isConnected() is False: 302 | return g.error_resp[-1] 303 | 304 | # To allow for bracketed orders (or processing a string of orders in a single request), we expect args to be a list 305 | if not isinstance(order_list, list): 306 | log.error('place_order_oca requires list of orders') 307 | return 308 | 309 | # Our 1st order in the list opens a position. The rest close it, but are only placed once the 1st order is Filled. 310 | oca_list = [] 311 | ocaGroup = None 312 | order_ids = set() 313 | dont_wait_order_ids = set() # Some orders aren't worth waiting for responses on 314 | for args in order_list: 315 | # If an orderId was provided, we'll be updating an existing order, so only send attributes which are updateable: 316 | # totalQuantity, orderType, symbol, secType, action 317 | # TODO consider casting unicode to utf-8 here. Otherwise we get error(id=1, errorCode=512, errorMsg=Order Sending Error - char format require string of length 1) 318 | # log.debug('Processing args from order_list: {}'.format(args)) 319 | orderId = args.get('orderId', None) 320 | if orderId is None: 321 | orderId = g.orderId 322 | g.orderId += 1 323 | if 'goodAfterTime' in args: 324 | dont_wait_order_ids.add(orderId) 325 | contract = Contract() 326 | order = Order() 327 | 328 | # Populate contract with appropriate args 329 | for attr in dir(contract): 330 | if attr[:2] == 'm_' and attr[2:] in args: 331 | setattr(contract, attr, args[attr[2:]]) 332 | # Populate order with appropriate 333 | order.m_clientId = client.clientId 334 | for attr in dir(order): 335 | if attr[:2] == 'm_' and attr[2:] in args: 336 | setattr(order, attr, args[attr[2:]]) 337 | 338 | # If this is a bracketed order, we'll need to add in the ocaGroup for children orders 339 | if ocaGroup: 340 | # Use our ocaGroup as the OCA group name. 341 | # The 1st order won't have this because its an Open order 342 | # All remaining orders are Close orders in an OCA group, which we'll store to file for later use 343 | order_ids.add(orderId) 344 | order.m_ocaType = 1 345 | order.m_ocaGroup = str(ocaGroup) 346 | oca_list.append((orderId, contract, order)) 347 | # Don't actually place this OCA order just yet 348 | continue 349 | 350 | log.debug('Placing Open order # {} on client # {} (connected={}): {}'.format(orderId, client.clientId, 351 | client.isConnected(), args)) 352 | client.placeOrder(orderId, contract, order) 353 | # Assume our 1st order in the list is the parent. Use this for remaining bracket orders and also error handling 354 | if not ocaGroup: 355 | ocaGroup = orderId 356 | log.debug('Setting ocaGroup={}'.format(ocaGroup)) 357 | 358 | # The only response for placing an order is an error, so we'll check for open orders and wait for this orderId or 359 | # and error to show up. 360 | 361 | 362 | # At this point, our Open order has been placed. Look for it to be Filled, then place OCA orders. 363 | # The User must set a goodTillDate on their open order to keep it from filling >1min into the future, or else this 364 | # function will have given up waiting for it to be filled, and not set the closing OCA group. 365 | resp = dict() 366 | 367 | timeout = 60 / 0.25 368 | 369 | order_ids = order_ids - dont_wait_order_ids 370 | # Make a 1-item set representing our Open postion order 371 | oca_set = set([ocaGroup]) 372 | resp['open_resp'] = wait_for_responses(oca_set, client, timeout, ['Filled']) 373 | # Our open order is filled, so now place our OCA group close orders 374 | log.debug('Placing OCA group of {} orders, ignoring responses for these orderIds: {}'. 375 | format(len(oca_list), dont_wait_order_ids)) 376 | for o in oca_list: 377 | client.placeOrder(*o) 378 | # Now get responses for these new OCA orders 379 | resp['close_resp'] = wait_for_responses(order_ids, client, timeout) 380 | connection.close_client(client) 381 | return resp 382 | 383 | 384 | # --------------------------------------------------------------------- 385 | # ACCOUNT & PORTFOLIO FUNCTIONS 386 | # --------------------------------------------------------------------- 387 | def get_portfolio_positions(): 388 | client = connection.get_client() 389 | if client is None: 390 | return g.error_resp[-2] 391 | elif client.isConnected() is False: 392 | return g.error_resp[-1] 393 | g.portfolio_positions_resp = dict(positionEnd=False, positions=[]) 394 | client.reqPositions() 395 | timeout = g.timeout 396 | while g.portfolio_positions_resp['positionEnd'] is False and client.isConnected() is True and timeout > 0: 397 | # log.debug("Waiting for Portfolio Positions responses on client {}...".format(client.clientId)) 398 | time.sleep(0.25) 399 | timeout -= 1 400 | client.cancelPositions() 401 | connection.close_client(client) 402 | return g.portfolio_positions_resp 403 | 404 | 405 | def get_account_summary(tags): 406 | """ Calls reqAccountSummary() then listens for accountSummary messages() 407 | """ 408 | client = connection.get_client() 409 | if client is None: 410 | return g.error_resp[-2] 411 | elif client.isConnected() is False: 412 | return g.error_resp[-1] 413 | client_id = client.clientId 414 | g.account_summary_resp[client_id] = dict(accountSummaryEnd=False) 415 | client.reqAccountSummary(client_id, 'All', tags) 416 | timeout = g.timeout 417 | while g.account_summary_resp[client_id]['accountSummaryEnd'] is False \ 418 | and client.isConnected() is True \ 419 | and timeout > 0: 420 | # log.debug("Waiting for Account Summary responses on client {}...".format(client.clientId)) 421 | time.sleep(0.25) 422 | timeout -= 1 423 | # time.sleep(1) 424 | client.cancelAccountSummary(client_id) 425 | connection.close_client(client) 426 | return g.account_summary_resp[client_id] 427 | 428 | 429 | def get_account_update(acctCode): 430 | """ Calls reqAccountUpdates(subscribe=False) then listens for accountAccountTime/AccountValue/Portfolio messages 431 | """ 432 | client = connection.get_client() 433 | if client is None: 434 | return g.error_resp[-2] 435 | elif client.isConnected() is False: 436 | return g.error_resp[-1] 437 | client_id = client.clientId 438 | g.account_update_resp = dict(accountDownloadEnd=False, updateAccountValue=dict(), updatePortfolio=[]) 439 | log.debug('Requesting account updates for {}'.format(acctCode)) 440 | client.reqAccountUpdates(subscribe=False, acctCode=acctCode) 441 | timeout = g.timeout 442 | while g.account_update_resp['accountDownloadEnd'] is False and client.isConnected() is True and timeout > 0: 443 | # log.debug("Waiting for responses on client {}...".format(client.clientId)) 444 | time.sleep(.25) 445 | timeout -= 1 446 | log.debug('Current update {}'.format(g.account_update_resp)) 447 | client.cancelAccountSummary(client_id) 448 | connection.close_client(client) 449 | return g.account_update_resp 450 | 451 | 452 | def get_executions(args): 453 | """Gets all (filtered) executions from last 24hrs """ 454 | client = connection.get_client() 455 | if client is None: 456 | return g.error_resp[-2] 457 | elif client.isConnected() is False: 458 | return g.error_resp[-1] 459 | 460 | g.executions_resp = dict(execDetailsEnd=False, execDetails=[], commissionReport=dict()) 461 | log.debug('Requesting executions for filter {}'.format(args)) 462 | filter = ExecutionFilter() 463 | for attr in dir(filter): 464 | if attr[:2] == 'm_' and attr[2:] in args: 465 | setattr(filter, attr, args[attr[2:]]) 466 | log.debug('Filter: {}'.format(filter.__dict__)) 467 | filter.m_clientId = 0 468 | client.reqExecutions(1, filter) 469 | timeout = g.timeout / 2 470 | while g.executions_resp['execDetailsEnd'] is False and client.isConnected() is True and timeout > 0: 471 | # log.debug("Waiting for responses on client {}...".format(client.clientId)) 472 | time.sleep(.25) 473 | timeout -= 1 474 | log.debug('Current executions {}'.format(g.executions_resp)) 475 | connection.close_client(client) 476 | return g.executions_resp 477 | -------------------------------------------------------------------------------- /app/utils.py: -------------------------------------------------------------------------------- 1 | """ Needs documentation 2 | """ 3 | import logging 4 | from ib.ext.Contract import Contract 5 | 6 | __author__ = 'Jason Haury' 7 | log = logging.getLogger(__name__) 8 | 9 | 10 | def make_contract(symbol, args=None): 11 | contract = Contract() 12 | contract.m_symbol = symbol 13 | contract.m_secType = 'STK' 14 | contract.m_exchange = 'SMART' 15 | #contract.m_primaryExch = 'SMART' # removed per IB tech 16 | contract.m_currency = 'USD' 17 | #contract.m_localSymbol = symbol # removed per IB tech 18 | 19 | if args: 20 | for attr in dir(contract): 21 | if attr[:2] == 'm_' and attr[2:] in args: 22 | val = str(args[attr[2:]]) 23 | log.debug('Setting Contract attribute {}={}'.format(attr, val)) 24 | setattr(contract, attr, val) 25 | log.debug('Contract details: {}'.format(contract.__dict__)) 26 | return contract 27 | 28 | 29 | def make_response(resp): 30 | """ Returns Flask tuple `resp, code` code per http://flask.pocoo.org/docs/0.10/quickstart/#about-responses 31 | """ 32 | if 'errorMsg' in resp: 33 | # Error 162 pertains to "Historical data request pacing violation" 34 | if resp['errorCode'] in [None, 162]: 35 | return resp, 429 36 | # Bad request if arg which made it to TWS wasn't right 37 | return resp, 400 38 | else: 39 | return resp 40 | 41 | 42 | def json_object_hook(data, ignore_dicts=False): 43 | # if this is a unicode string, return its string representation 44 | if isinstance(data, unicode): 45 | return data.encode('utf-8') 46 | # if this is a list of values, return list of byteified values 47 | if isinstance(data, list): 48 | return [json_object_hook(item, ignore_dicts=True) for item in data] 49 | # if this is a dictionary, return dictionary of byteified keys and values 50 | # but only if we haven't already byteified it 51 | if isinstance(data, dict) and not ignore_dicts: 52 | return { 53 | json_object_hook(key, ignore_dicts=True): json_object_hook(value, ignore_dicts=True) 54 | for key, value in data.iteritems() 55 | } 56 | # if it's anything else, return it in its original form 57 | return data 58 | -------------------------------------------------------------------------------- /app/uwsgi.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | socket = 127.0.0.1:8000 3 | ;socket = /tmp/uwsgi.sock 4 | ;chown-socket = nginx:nginx 5 | ;chmod-socket = 664 6 | 7 | module = main 8 | callable = app 9 | post-buffering = 8192 10 | 11 | # set cheaper algorithm to use, if not set default will be used 12 | cheaper-algo = spare 13 | 14 | # minimum number of workers to keep at all times 15 | cheaper = 1 16 | 17 | # number of workers to spawn at startup 18 | cheaper-initial = 1 19 | 20 | # maximum number of workers that can be spawned 21 | workers = 1 22 | 23 | # how many workers should be spawned at a time 24 | cheaper-step = 0 25 | 26 | 27 | #location of log files 28 | #logto = /var/log/uwsgi/%n.log -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | ibgw: 4 | image: hamx0r/ibheadless 5 | container_name: ibgw 6 | restart: always 7 | environment: 8 | - IB_IbLoginId 9 | - IB_IbPassword 10 | ports: 11 | - "4003:4003" 12 | ibrest: 13 | build: 14 | context: . 15 | dockerfile: Dockerfile 16 | image: hamx0r/ibrest:latest 17 | container_name: ibrest 18 | restart: always 19 | environment: 20 | - ID_SECRET_KEY 21 | - IBGW_PORT_4003_TCP_ADDR=ibgw 22 | - IBGW_CLIENT_ID=0 23 | - IBREST_HOST=0.0.0.0 24 | - IBREST_PORT=443 25 | links: 26 | - ibgw 27 | ports: 28 | - "443:443" 29 | -------------------------------------------------------------------------------- /etc/nginx.conf: -------------------------------------------------------------------------------- 1 | #@IgnoreInspection BashAddShebang 2 | 3 | user nginx; 4 | worker_processes 1; 5 | 6 | error_log /var/log/nginx/error.log warn; 7 | pid /var/run/nginx.pid; 8 | 9 | events { 10 | worker_connections 1024; 11 | } 12 | http { 13 | upstream ibrest { 14 | server ibrest0; 15 | } 16 | # upstream ibrest { 17 | # server ibrest1; 18 | # server ibrest2; 19 | # server ibrest3; 20 | # server ibrest4; 21 | # server ibrest5; 22 | # server ibrest6; 23 | # server ibrest7; 24 | # } 25 | 26 | server { 27 | listen 443; 28 | ssl on; 29 | ssl_certificate /etc/ssl/ibrest.crt; # path to your cacert.pem 30 | ssl_certificate_key /etc/ssl/ibrest.key; # path to your privkey.pem 31 | 32 | # location /order { 33 | # proxy_pass http://order; 34 | # } 35 | location / { 36 | proxy_pass http://ibrest; 37 | } 38 | 39 | # 40 | # location / { 41 | # try_files $uri @app; 42 | # } 43 | # location @app { 44 | # include uwsgi_params; 45 | # #uwsgi_pass unix:///tmp/uwsgi.sock; 46 | # uwsgi_pass 127.0.0.1:8000; 47 | # } 48 | # location /static { 49 | # alias /app/static; 50 | # } 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /ibheadless/Dockerfile: -------------------------------------------------------------------------------- 1 | # To build docker image: 2 | # docker build -t ibgw . 3 | 4 | # Run with: 5 | FROM ibizaman/docker-ibcontroller 6 | 7 | # After ibgw starts once and stops, it creates a dir like /var/run/ibcontroller/tws/conf/dxxxxx. Copy ibgw.xml there. 8 | COPY ibg.xml /ibg.xml 9 | COPY start.sh /start.sh 10 | RUN chmod a+x /start.sh 11 | 12 | EXPOSE 4003 13 | 14 | CMD ["/start.sh"] -------------------------------------------------------------------------------- /ibheadless/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | conf=/etc/ibcontroller/conf.ini 4 | 5 | # Force those values 6 | export IB_ForceTwsApiPort= 7 | export IB_IbBindAddress=127.0.0.1 8 | export IB_IbDir=/var/run/ibcontroller/tws/conf 9 | 10 | # thanks to kafka-docker for this wonderful snippet 11 | for VAR in `env`; do 12 | if [[ $VAR =~ ^IB_ ]]; then 13 | name=`echo "$VAR" | sed -r "s/IB_(.*)=.*/\1/g"` 14 | env_var=`echo "$VAR" | sed -r "s/(.*)=.*/\1/g"` 15 | if egrep -q "(^|^#)$name=" $conf; then 16 | sed -r -i "s@(^|^#)($name)=(.*)@\2=${!env_var}@g" $conf #note that no config values may contain an '@' char 17 | else 18 | echo "$name=${!env_var}" >> $conf 19 | fi 20 | fi 21 | done 22 | 23 | socat TCP-LISTEN:4003,fork TCP:127.0.0.1:4002& 24 | 25 | /usr/sbin/xvfb-run \ 26 | --auto-servernum \ 27 | -f \ 28 | /var/run/xvfb/ \ 29 | java \ 30 | -cp \ 31 | /usr/share/java/ib-tws/jts.jar:/usr/share/java/ib-tws/total.jar:/usr/share/java/ibcontroller/ibcontroller.jar \ 32 | -Xmx512M \ 33 | ibcontroller.IBGatewayController \ 34 | $conf -------------------------------------------------------------------------------- /proxy.Dockerfile: -------------------------------------------------------------------------------- 1 | # Serves as proxy to 8 instances of ibrest, each with their own clientId 2 | # Nginx handles SSL 3 | FROM nginx:alpine 4 | RUN apk add --update openssl 5 | RUN openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/ibrest.key -out /etc/ssl/ibrest.crt -new -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=www.example.com" 6 | # Or copy your own serts 7 | #COPY ./etc/ibrest.crt /etc/ssl/ 8 | #COPY ./etc/ibrest.key /etc/ssl/ 9 | COPY etc/nginx.conf /etc/nginx/nginx.conf 10 | 11 | EXPOSE 443 -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | flask-restful 2 | itsdangerous 3 | # use older requests to avoid error messages 4 | requests==2.5.3 5 | #ib is not on PyPi, but can be found in lib folder https://github.com/blampe/IbPy 6 | sqlalchemy --------------------------------------------------------------------------------