Skip to content
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 portfolio #196

Open
wants to merge 39 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
abc3e86
Started an IB PriceHandler and a test case.
ryankennedyio Feb 18, 2017
55df528
Start working on IBService; the master/background/network communicati…
ryankennedyio Feb 25, 2017
63e1710
Remove prebuilt hist data request
ryankennedyio Feb 25, 2017
e673185
Rename references to EWrapper/EClient
ryankennedyio Feb 25, 2017
c46d051
Added stub test, partially implement historic prices and ib_bar price…
ryankennedyio Feb 25, 2017
ffe2938
Remove print statements
ryankennedyio Feb 26, 2017
deaf6b4
IBPriceHandler should now work for at least one historic request
ryankennedyio Feb 26, 2017
7bc7f54
remove debug statement
ryankennedyio Feb 26, 2017
84a1aa6
IBService implements threading interface to run the infinite message …
ryankennedyio Mar 7, 2017
433868d
Merge branch 'master' of github.com:mhallsmoore/qstrader into ib_inte…
ryankennedyio Mar 7, 2017
9519bc0
Moved IBService creation, setup and stop outside of the price handler
ryankennedyio Mar 8, 2017
749e13c
Ensure tradelog is created if it does not exist
ryankennedyio Mar 8, 2017
fec3815
Fix some strange kind of systemerror that appeared using Python 3.5
ryankennedyio Mar 8, 2017
2a2fb4b
Add a few notes
ryankennedyio Mar 8, 2017
657b016
Add a fwe notes
ryankennedyio Mar 8, 2017
c806a64
Add some instructions for usage to the IBService
ryankennedyio Mar 8, 2017
960cf79
Added an example IB Historic backtest
ryankennedyio Mar 8, 2017
e38a59a
The IBPriceHandler must take in a list of symbols for subscription, r…
ryankennedyio Mar 8, 2017
f0a8fc3
Merge branch 'master' into ib_integration
ryankennedyio Mar 11, 2017
b6417f2
Started adding more test cases. Mocking IBService.
ryankennedyio Mar 11, 2017
39734f0
Fix bug in IB Price Handler's timestamps, create a whole mock object …
ryankennedyio Mar 11, 2017
91165fa
Ensure all historical requests are made
ryankennedyio Mar 11, 2017
5a27a3a
More intensive testing on streaming historic prices; fixed bug where …
ryankennedyio Mar 11, 2017
922ffd0
Remove debugging
ryankennedyio Mar 11, 2017
2a6b36a
Remote two test cases that were covered by another test
ryankennedyio Mar 11, 2017
6d9c114
Move some test cases to TODO's because I'm not yet sure the best way …
ryankennedyio Mar 11, 2017
60bfa5b
Linting
ryankennedyio Mar 11, 2017
eba9654
Fix queue compatibility import
ryankennedyio Mar 11, 2017
fe5f5df
Update a few references. Test cases don't cover live IB unfrotunately…
ryankennedyio Mar 12, 2017
393ae63
Started some test cases for live market data requests
ryankennedyio Mar 14, 2017
c05e93a
Live pricehandler should request properly and stream bars when popual…
ryankennedyio Mar 14, 2017
efbf3a5
Live market data working with example
ryankennedyio Mar 14, 2017
1fd72aa
Add displayStrategy for live trading example to see market data comin…
ryankennedyio Mar 15, 2017
8c4f822
Linting
ryankennedyio Mar 15, 2017
8c52055
Try to install ibapi python module manually in Travis build
ryankennedyio Mar 15, 2017
55d62d0
Modifying travis build
ryankennedyio Mar 15, 2017
bc3e347
Modifying travis build
ryankennedyio Mar 15, 2017
a6117d9
Start putting together a rough scaffold for how the IB Portfolio shou…
ryankennedyio Mar 16, 2017
8e858d7
IBPortfolio should extend Portfolio, but override init() in order to …
ryankennedyio Mar 16, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
sudo: false

language: python
python:
- "3.4"
- "3.5"

env:
- PYTHON=2.7 PANDAS=0.18.0
- PYTHON=3.4 PANDAS=0.18.0
- PYTHON=3.5 PANDAS=0.18.0

Expand All @@ -14,6 +16,10 @@ env:
# - env: PYTHON=2.7 PANDAS=0.11.0

install:
- wget http://interactivebrokers.github.io/downloads/twsapi_macunix.973.02.zip
- unzip twsapi_macunix.973.02.zip
- pip install IBJts/source/pythonclient/

- pip install -qq flake8
# You may want to periodically update this, although the conda update
# conda line below will keep everything up-to-date. We do this
Expand Down
103 changes: 103 additions & 0 deletions examples/buy_and_hold_historic_ib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import datetime

from qstrader import settings
from qstrader.strategy.base import AbstractStrategy
from qstrader.event import SignalEvent, EventType
from qstrader.compat import queue
from qstrader.trading_session import TradingSession
from qstrader.service.ib import IBService
from qstrader.price_handler.ib_bar import IBBarPriceHandler
from ibapi.contract import Contract


class BuyAndHoldStrategy(AbstractStrategy):
"""
A testing strategy that simply purchases (longs) any asset that
matches what was passed in on initialization and
then holds until the completion of a backtest.
"""
def __init__(
self, tickers, events_queue,
base_quantity=100
):
self.tickers = tickers
self.invested = dict.fromkeys(tickers)
self.events_queue = events_queue
self.base_quantity = base_quantity

def calculate_signals(self, event):
if (
event.type in [EventType.BAR, EventType.TICK] and
event.ticker in self.tickers
):
if not self.invested[event.ticker]:
signal = SignalEvent(
event.ticker, "BOT",
suggested_quantity=self.base_quantity
)
self.events_queue.put(signal)
self.invested[event.ticker] = True


def run(config, testing, tickers, filename):
# Backtest information
title = ['Buy and Hold Historic IB Example']
initial_equity = 10000.0
events_queue = queue.Queue()

# Set up IBService
ib_service = IBService()
ib_service.connect("127.0.0.1", 4001, 0) # TODO from config
ib_service.start()

# Set up IB Contract objects for the PriceHandler
# MORE INFO: https://www.interactivebrokers.com/en/?f=%2Fen%2Fgeneral%2Fcontact%2FtipsContractsDatabaseSearch.php%3Fib_entity%3Dllc
symbols = ["CBA", "BHP", "STO", "FMG", "WOW", "WES"]
contracts = []
for symbol in symbols:
contract = Contract()
contract.exchange = "SMART"
contract.symbol = symbol
contract.secType = "STK"
contract.currency = "AUD"
contracts.append(contract)

# Set up the IB PriceHandler. Want 5 day's of minute bars, up to yesterday.
# Look at IB Documentation for possible values.
end_date = datetime.datetime.now() - datetime.timedelta(days=1)
price_handler = IBBarPriceHandler(
ib_service, events_queue, contracts, config,
"historic", end_date, hist_duration="5 D", barsize="1 min"
)

# Use the Buy and Hold Strategy
strategy = BuyAndHoldStrategy(tickers, events_queue)

# Start/End TODO redundant -- only required for default (Yahoo) price handler.
start_date = datetime.datetime(2000, 1, 1)
end_date = datetime.datetime(2014, 1, 1)

# Set up the backtest
backtest = TradingSession(
config, strategy, tickers,
initial_equity, start_date, end_date,
events_queue, price_handler=price_handler, title=title
)
results = backtest.start_trading(testing=testing)

# Disconnect from services
ib_service.stop_event.set()
ib_service.join()

return results


if __name__ == "__main__":
# Configuration data
testing = False
config = settings.from_file(
settings.DEFAULT_CONFIG_FILENAME, testing
)
tickers = ["CBA", "BHP", "STO", "FMG", "WOW", "WES"]
filename = None
run(config, testing, tickers, filename)
136 changes: 136 additions & 0 deletions examples/buy_and_hold_live_ib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import datetime

from qstrader.price_parser import PriceParser
from qstrader import settings
from qstrader.strategy.base import Strategies, AbstractStrategy
from qstrader.event import SignalEvent, EventType
from qstrader.compat import queue
from qstrader.trading_session import TradingSession
from qstrader.service.ib import IBService
from qstrader.price_handler.ib_bar import IBBarPriceHandler
from ibapi.contract import Contract


class DisplayStrategy(AbstractStrategy):
"""
A strategy which display ticks / bars
params:
n = 10000
n_window = 5
"""
def __init__(self, n=100, n_window=5):
self.n = n
self.n_window = n_window
self.i = 0

def calculate_signals(self, event):
if event.type in [EventType.TICK, EventType.BAR]:
# Format the event for human display
if event.type == EventType.BAR:
event.open_price = PriceParser.display(event.open_price)
event.high_price = PriceParser.display(event.high_price)
event.low_price = PriceParser.display(event.low_price)
event.close_price = PriceParser.display(event.close_price)
event.adj_close_price = PriceParser.display(event.adj_close_price)
else: # event.type == EventType.TICK
event.bid = PriceParser.display(event.bid)
event.ask = PriceParser.display(event.ask)

if self.i % self.n in range(self.n_window):
print("%d %s" % (self.i, event))
self.i += 1


class BuyAndHoldStrategy(AbstractStrategy):
"""
A testing strategy that simply purchases (longs) any asset that
matches what was passed in on initialization and
then holds until the completion of a backtest.
"""
def __init__(
self, tickers, events_queue,
base_quantity=100
):
self.tickers = tickers
self.invested = dict.fromkeys(tickers)
self.events_queue = events_queue
self.base_quantity = base_quantity

def calculate_signals(self, event):
if (
event.type in [EventType.BAR, EventType.TICK] and
event.ticker in self.tickers
):
if not self.invested[event.ticker]:
signal = SignalEvent(
event.ticker, "BOT",
suggested_quantity=self.base_quantity
)
self.events_queue.put(signal)
self.invested[event.ticker] = True


def run(config, testing, tickers, filename):
# Backtest information
title = ['Buy and Hold Live IB Example -- 5 Sec Bars']
initial_equity = 10000.0
events_queue = queue.Queue()

# Set up IBService
ib_service = IBService()
ib_service.connect("127.0.0.1", 4001, 0) # TODO from config
ib_service.start()

# Set up IB Contract objects for the PriceHandler
# MORE INFO: https://www.interactivebrokers.com/en/?f=%2Fen%2Fgeneral%2Fcontact%2FtipsContractsDatabaseSearch.php%3Fib_entity%3Dllc
symbols = ["CBA", "BHP", "STO", "FMG", "WOW", "WES"]
contracts = []
for symbol in symbols:
contract = Contract()
contract.exchange = "SMART"
contract.symbol = symbol
contract.secType = "STK"
contract.currency = "AUD"
contracts.append(contract)

# Set up the IB PriceHandler. Want 5 day's of minute bars, up to yesterday.
# Look at IB Documentation for possible values.
end_date = datetime.datetime.now() - datetime.timedelta(days=1)
price_handler = IBBarPriceHandler(
ib_service, events_queue, contracts, config,
"live"
)

# Use the Buy and Hold Strategy
strategy = Strategies(BuyAndHoldStrategy(tickers, events_queue), DisplayStrategy(n=20))

# Start/End TODO redundant -- only required for default (Yahoo) price handler.
start_date = datetime.datetime(2000, 1, 1)
end_date = datetime.datetime(2014, 1, 1)

# Set up the backtest
session = TradingSession(
config, strategy, tickers,
initial_equity, start_date, end_date,
events_queue, session_type="live",
end_session_time=datetime.datetime.now() + datetime.timedelta(minutes=10),
price_handler=price_handler, title=title
)
results = session.start_trading(testing=testing)

# Disconnect from services
ib_service.stop_event.set()
ib_service.join()

return results


if __name__ == "__main__":
# Configuration data
testing = False
config = settings.from_file(
settings.DEFAULT_CONFIG_FILENAME, testing
)
tickers = ["CBA", "BHP", "STO", "FMG", "WOW", "WES"]
filename = None
run(config, testing, tickers, filename)
4 changes: 2 additions & 2 deletions qstrader/compliance/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def __init__(self, config):
"commission"
]
fname = os.path.expanduser(os.path.join(self.config.OUTPUT_DIR, self.csv_filename))
with open(fname, 'a') as csvfile:
with open(fname, 'a+') as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()

Expand All @@ -49,7 +49,7 @@ def record_trade(self, fill):
Append all details about the FillEvent to the CSV trade log.
"""
fname = os.path.expanduser(os.path.join(self.config.OUTPUT_DIR, self.csv_filename))
with open(fname, 'a') as csvfile:
with open(fname, 'a+') as csvfile:
writer = csv.writer(csvfile)
writer.writerow([
fill.timestamp, fill.ticker,
Expand Down
46 changes: 46 additions & 0 deletions qstrader/ib_portfolio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import time
from .position import Position
from .portfolio import Portfolio

class IBPortfolio(Portfolio):
def __init__(self, ib_service, price_handler, cash):
"""
On creation, the IB Portfolio will request all current portfolio details
from the IB API.

WORK PROCESS:
* Subclass the existing Portfolio implementation, override init() only
* Bootstrap the portfolio by requesting current positions from IB
* Decide where we handle OrderStatus() events (guessing ExecutionHandler)
"""
Portfolio.__init__(self, price_handler, cash)
self.ib_service = ib_service

# Bootstrap the portfolio by loading data from IB.
self.ib_service.reqAccountUpdates(True, "")
time.sleep(5) # Ugly, but no way to implement future/promise with IB's response?
while not self.ib_service.portfolioUpdatesQueue.empty():
# Create the position
portfolioUpdate = self.ib_service.portfolioUpdatesQueue.get(False)
contract = portfolioUpdate[0]
contract.exchange = contract.primaryExchange
position = Position(
"BOT" if portfolioUpdate[1] > 0 else "SLD",
contract.symbol, portfolioUpdate[1], 0, 0, 0, 0
)
# Override some of the position variables
if portfolioUpdate[1] > 0:
position.buys = portfolioUpdate[1] ## TODO Confirm correct
else:
position.sells = portfolioUpdate[1] ## TODO Confirm correct
position.quantity = portfolioUpdate[1]
position.init_price = portfolioUpdate[4]
position.realised_pnl = portfolioUpdate[6]
position.unrealized_pnl = portfolioUpdate[5]
position.market_value = portfolioUpdate[3]

# Add the position to the QSTrader portfolio
self.positions[contract.symbol] = position

# Subscribe the PriceHandler to this position so we get updates on value.
self.price_handler._subscribe_contract(contract)
Loading