-
Notifications
You must be signed in to change notification settings - Fork 861
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ib integration #186
Open
ryankennedyio
wants to merge
37
commits into
mhallsmoore:master
Choose a base branch
from
ryankennedyio:ib_integration
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Ib integration #186
Changes from 5 commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
abc3e86
Started an IB PriceHandler and a test case.
ryankennedyio 55df528
Start working on IBService; the master/background/network communicati…
ryankennedyio 63e1710
Remove prebuilt hist data request
ryankennedyio e673185
Rename references to EWrapper/EClient
ryankennedyio c46d051
Added stub test, partially implement historic prices and ib_bar price…
ryankennedyio ffe2938
Remove print statements
ryankennedyio deaf6b4
IBPriceHandler should now work for at least one historic request
ryankennedyio 7bc7f54
remove debug statement
ryankennedyio 84a1aa6
IBService implements threading interface to run the infinite message …
ryankennedyio 433868d
Merge branch 'master' of github.com:mhallsmoore/qstrader into ib_inte…
ryankennedyio 9519bc0
Moved IBService creation, setup and stop outside of the price handler
ryankennedyio 749e13c
Ensure tradelog is created if it does not exist
ryankennedyio fec3815
Fix some strange kind of systemerror that appeared using Python 3.5
ryankennedyio 2a2fb4b
Add a few notes
ryankennedyio 657b016
Add a fwe notes
ryankennedyio c806a64
Add some instructions for usage to the IBService
ryankennedyio 960cf79
Added an example IB Historic backtest
ryankennedyio e38a59a
The IBPriceHandler must take in a list of symbols for subscription, r…
ryankennedyio f0a8fc3
Merge branch 'master' into ib_integration
ryankennedyio b6417f2
Started adding more test cases. Mocking IBService.
ryankennedyio 39734f0
Fix bug in IB Price Handler's timestamps, create a whole mock object …
ryankennedyio 91165fa
Ensure all historical requests are made
ryankennedyio 5a27a3a
More intensive testing on streaming historic prices; fixed bug where …
ryankennedyio 922ffd0
Remove debugging
ryankennedyio 2a6b36a
Remote two test cases that were covered by another test
ryankennedyio 6d9c114
Move some test cases to TODO's because I'm not yet sure the best way …
ryankennedyio 60bfa5b
Linting
ryankennedyio eba9654
Fix queue compatibility import
ryankennedyio fe5f5df
Update a few references. Test cases don't cover live IB unfrotunately…
ryankennedyio 393ae63
Started some test cases for live market data requests
ryankennedyio c05e93a
Live pricehandler should request properly and stream bars when popual…
ryankennedyio efbf3a5
Live market data working with example
ryankennedyio 1fd72aa
Add displayStrategy for live trading example to see market data comin…
ryankennedyio 8c4f822
Linting
ryankennedyio 8c52055
Try to install ibapi python module manually in Travis build
ryankennedyio 55d62d0
Modifying travis build
ryankennedyio bc3e347
Modifying travis build
ryankennedyio File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import os | ||
import datetime | ||
import queue | ||
from .base import AbstractBarPriceHandler | ||
|
||
from qstrader.service.ib import IBService | ||
|
||
from ibapi.client import EClient | ||
from ibapi.wrapper import EWrapper | ||
from ibapi.utils import iswrapper | ||
from ibapi.contract import * | ||
from ibapi.common import * | ||
|
||
|
||
|
||
#types | ||
from ibapi.utils import (current_fn_name, BadMessage) | ||
from ibapi.common import * | ||
from ibapi.order_condition import * | ||
from ibapi.contract import * | ||
from ibapi.order import * | ||
from ibapi.order_state import * | ||
from ibapi.execution import Execution | ||
from ibapi.execution import ExecutionFilter | ||
from ibapi.commission_report import CommissionReport | ||
from ibapi.scanner import ScannerSubscription | ||
from ibapi.ticktype import * | ||
|
||
from ibapi.account_summary_tags import * | ||
|
||
|
||
class IBBarPriceHandler(AbstractBarPriceHandler): | ||
""" | ||
Designed to feed either live or historic market data bars | ||
from an Interactive Brokers connection. | ||
|
||
Uses the IBService to make requests and collect data once responses have returned. | ||
|
||
TODO: | ||
* Historic/Live mode to be set by whether QSTrader is in Backtest or Live mode | ||
* IBService should be an initialization parameter | ||
* Ports, etc, connection strings from config | ||
""" | ||
def __init__( | ||
self, events_queue, param_tickers, settings, mode="historic", | ||
hist_end_date = datetime.datetime.now() - datetime.timedelta(days=3), | ||
hist_duration="5 D", hist_barsize="1 min" | ||
): | ||
self.ib_service = IBService() | ||
self.ib_service.connect("127.0.0.1",4001,0) | ||
self.mode = mode | ||
self.continue_backtest = True | ||
self.hist_end_date = hist_end_date | ||
self.hist_duration = hist_duration | ||
self.hist_barsize = hist_barsize | ||
|
||
# The position of a ticker in this dict is used as its IB ID. | ||
self.tickers = {} # TODO gross | ||
self.ticker_lookup = {} | ||
|
||
for ticker in param_tickers: # TODO gross param_tickers -- combine above? | ||
self._subscribe_ticker(ticker) | ||
|
||
self._wait_for_hist_population() | ||
|
||
|
||
def _subscribe_ticker(self, ticker): | ||
""" | ||
Request ticker data from IB | ||
""" | ||
if ticker not in self.tickers: | ||
# Set up an IB ContractS | ||
contract = Contract() | ||
contract.exchange = "SMART" | ||
contract.symbol = ticker | ||
contract.secType = "STK" | ||
contract.currency = "AUD" | ||
|
||
if self.mode == "historic": | ||
ib_ticker_id = len(self.tickers) | ||
end_time = datetime.datetime.strftime(self.hist_end_date, "%Y%m%d 17:00:00") | ||
self.ib_service.reqHistoricalData( | ||
ib_ticker_id, contract, end_time, self.hist_duration, self.hist_barsize, | ||
"TRADES", True, 2, None) | ||
|
||
# TODO gross | ||
self.ticker_lookup[len(self.tickers)] = ticker | ||
self.tickers[ticker] = {} | ||
|
||
|
||
def _wait_for_hist_population(self): | ||
""" | ||
# TODO this *needs* to be an infinite loop running inside the service, | ||
# with the service launched on a new Thread or Process | ||
""" | ||
while (self.ib_service.conn.isConnected() or not self.ib_service.msg_queue.empty()) and len(self.ib_service.waitingHistoricalData) != 0: | ||
try: | ||
text = self.ib_service.msg_queue.get(block=True, timeout=0.2) | ||
if len(text) > MAX_MSG_LEN: | ||
self.ib_service.wrapper.error(NO_VALID_ID, BAD_LENGTH.code(), | ||
"%s:%d:%s" % (BAD_LENGTH.msg(), len(text), text)) | ||
self.ib_service.disconnect() | ||
break | ||
except queue.Empty: | ||
print("queue.get: empty") | ||
else: | ||
fields = comm.read_fields(text) | ||
print("fields %s", fields) | ||
self.ib_service.decoder.interpret(fields) | ||
|
||
print("conn:%d queue.sz:%d", | ||
self.ib_service.conn.isConnected(), | ||
self.ib_service.msg_queue.qsize()) | ||
|
||
self.ib_service.disconnect() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import sys | ||
import argparse | ||
import datetime | ||
import collections | ||
import inspect | ||
import queue | ||
|
||
import logging | ||
import time | ||
import os.path | ||
|
||
from ibapi.wrapper import EWrapper | ||
from ibapi.client import EClient | ||
from ibapi.utils import iswrapper | ||
|
||
#types | ||
from ibapi.utils import (current_fn_name, BadMessage) | ||
from ibapi.common import * | ||
from ibapi.order_condition import * | ||
from ibapi.contract import * | ||
from ibapi.order import * | ||
from ibapi.order_state import * | ||
from ibapi.execution import Execution | ||
from ibapi.execution import ExecutionFilter | ||
from ibapi.commission_report import CommissionReport | ||
from ibapi.scanner import ScannerSubscription | ||
from ibapi.ticktype import * | ||
|
||
from ibapi.account_summary_tags import * | ||
|
||
|
||
class IBService(EWrapper, EClient): | ||
""" | ||
The IBService is the primary conduit of data from QStrader to Interactive Brokers. | ||
This service provides functions to request data, and allows for | ||
callbacks to be triggered, which populates "data queues" with the response. | ||
|
||
All methods of the EClient are available (i.e. API Requests), as are | ||
the callbacks for EWrapper (i.e. API responses). It also provides a set of Queues | ||
which are populated with the responses from EWrapper. Other components in the | ||
system should use these queues collect the API response data. | ||
|
||
Any module or component that wishes to interact with IB should do so by using | ||
methods offered in this class. This ensures that the logic required to talk with IB | ||
is contained within this class exclusively, with the added benefit that we | ||
can easily create mock instances of the IBService for testing. | ||
""" | ||
def __init__(self): | ||
EWrapper.__init__(self) | ||
EClient.__init__(self, wrapper=self) | ||
|
||
self.historicalDataQueue = queue.Queue() | ||
self.waitingHistoricalData = [] | ||
|
||
|
||
def error(self, reqId:TickerId, errorCode:int, errorString:str): | ||
super().error(reqId, errorCode, errorString) | ||
print("Error. Id: " , reqId, " Code: " , errorCode , " Msg: " , errorString) | ||
|
||
|
||
""" | ||
Append `reqId` to waitingHistoricalData, then call the super method. | ||
""" | ||
def reqHistoricalData(self, reqId:TickerId , contract:Contract, endDateTime:str, | ||
durationStr:str, barSizeSetting:str, whatToShow:str, | ||
useRTH:int, formatDate:int, chartOptions:TagValueList): | ||
self.waitingHistoricalData.append(reqId) | ||
print("REQUESTING HISTORIC, WAITING FOR %s" % len(self.waitingHistoricalData)) | ||
super().reqHistoricalData( reqId, contract, endDateTime, | ||
durationStr, barSizeSetting, whatToShow, | ||
useRTH, formatDate, chartOptions) | ||
|
||
|
||
""" | ||
Populate the HistoricalData queue. | ||
""" | ||
def historicalData(self, reqId:TickerId , date:str, open:float, high:float, | ||
low:float, close:float, volume:int, barCount:int, | ||
WAP:float, hasGaps:int): | ||
print("HistoricalData. ", reqId, " Date:", date, "Open:", open, | ||
"High:", high, "Low:", low, "Close:", close, "Volume:", volume, | ||
"Count:", barCount, "WAP:", WAP, "HasGaps:", hasGaps) | ||
self.historicalDataQueue.put((reqId, date, open, high, low, close, | ||
volume, barCount, WAP, hasGaps)) | ||
|
||
""" | ||
Remove `reqId` from waitingHistoricalData | ||
TODO: Will it work with multiple historical requests for same symbol? | ||
""" | ||
def historicalDataEnd(self, reqId:int, start:str, end:str): | ||
print("FINISHED FOR %s" % reqId) | ||
self.waitingHistoricalData.remove(reqId) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import unittest | ||
|
||
from qstrader.price_handler.ib_bar import IBBarPriceHandler | ||
from qstrader.compat import queue | ||
from qstrader import settings | ||
|
||
class TestPriceHandlerSimpleCase(unittest.TestCase): | ||
def setUp(self): | ||
""" | ||
Set up the PriceHandler object with a small | ||
set of initial tickers for a backtest in historic mode. | ||
""" | ||
self.config = settings.TEST | ||
fixtures_path = self.config.CSV_DATA_DIR | ||
events_queue = queue.Queue() | ||
init_tickers = ["CBA"] | ||
self.price_handler = IBBarPriceHandler( | ||
events_queue, init_tickers, self.config | ||
) | ||
|
||
def test(self): | ||
self.assertEqual(1,2) | ||
|
||
|
||
|
||
if __name__ == "__main__": | ||
unittest.main() |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When previously trying to write a service to store historical options data I found it useful to pass a custom object with a unique ID and the contract object.
When you submit a type request for futures or options on the root symbol IB will return a stream of contract objects. If the functions to subscribe and request can accept the connection objects or a data structure containing the objects it saves you from having to pull out the relevant information required to recreate the objects later.