Skip to content

Commit

Permalink
Merge pull request #19 from mhallsmoore/barevent
Browse files Browse the repository at this point in the history
Barevent
  • Loading branch information
mhallsmoore committed May 16, 2016
2 parents 386b8fd + 7c03e4f commit de780ca
Show file tree
Hide file tree
Showing 12 changed files with 371 additions and 194 deletions.
16 changes: 14 additions & 2 deletions backtest/backtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,15 @@ def _run_backtest(self):
print("Running Backtest...")
iters = 0
ticks = 0
bars = 0
while iters < self.max_iters and self.price_handler.continue_backtest:
try:
event = self.events_queue.get(False)
except queue.Empty:
self.price_handler.stream_next_tick()
if self.price_handler.type == "TICK_HANDLER":
self.price_handler.stream_next_tick()
else:
self.price_handler.stream_next_bar()
else:
if event is not None:
if event.type == 'TICK':
Expand All @@ -106,6 +110,13 @@ def _run_backtest(self):
self.strategy.calculate_signals(event)
self.portfolio_handler.update_portfolio_value()
ticks += 1
elif event.type == 'BAR':
self.cur_time = event.time
print("Bar %s, at %s" % (bars, self.cur_time))
self._append_equity_state()
self.strategy.calculate_signals(event)
self.portfolio_handler.update_portfolio_value()
bars += 1
elif event.type == 'SIGNAL':
self.portfolio_handler.on_signal(event)
elif event.type == 'ORDER':
Expand All @@ -124,4 +135,5 @@ def simulate_trading(self):
print("Backtest complete.")
statistics = Statistics()
statistics.generate_results()
statistics.plot_results()
statistics.plot_results()

93 changes: 93 additions & 0 deletions event/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,99 @@ def __repr__(self):
return str(self)


class BarEvent(Event):
"""
Handles the event of receiving a new market
open-high-low-close-volume bar, as would be generated
via common data providers such as Yahoo Finance.
"""
def __init__(
self, ticker, time, period,
open_price, high_price, low_price,
close_price, volume, adj_close_price=None
):
"""
Initialises the BarEvent.
Parameters:
ticker - The ticker symbol, e.g. 'GOOG'.
time - The timestamp of the bar
period - The time period covered by the bar in seconds
open_price - The unadjusted opening price of the bar
high_price - The unadjusted high price of the bar
low_price - The unadjusted low price of the bar
close_price - The unadjusted close price of the bar
volume - The volume of trading within the bar
adj_close_price - The vendor adjusted closing price
(e.g. back-adjustment) of the bar
Note: It is not advised to use 'open', 'close' instead
of 'open_price', 'close_price' as 'open' is a reserved
word in Python.
"""
self.type = 'BAR'
self.ticker = ticker
self.time = time
self.period = period
self.open_price = open_price
self.high_price = high_price
self.low_price = low_price
self.close_price = close_price
self.volume = volume
self.adj_close_price = adj_close_price
self.period_readable = self._readable_period()

def _readable_period(self):
"""
Creates a human-readable period from the number
of seconds specified for 'period'.
For instance, converts:
* 1 -> '1sec'
* 5 -> '5secs'
* 60 -> '1min'
* 300 -> '5min'
If no period is found in the lookup table, the human
readable period is simply passed through from period,
in seconds.
"""
lut = {
1: "1sec",
5: "5sec",
10: "10sec",
15: "15sec",
30: "30sec",
60: "1min",
300: "5min",
600: "10min",
900: "15min",
1800: "30min",
3600: "1hr",
86400: "1day",
604800: "1wk"
}
if self.period in lut:
return lut[self.period]
else:
return "%ssec" % str(self.period)

def __str__(self):
format_str = "Type: %s, Ticker: %s, Time: %s, Period: %s, " \
"Open: %s, High: %s, Low: %s, Close: %s, " \
"Adj Close: %s, Volume: %s" % (
str(self.type), str(self.ticker), str(self.time),
str(self.period_readable), str(self.open_price),
str(self.high_price), str(self.low_price),
str(self.close_price), str(self.adj_close_price),
str(self.volume)
)
return format_str

def __repr__(self):
return str(self)


class SignalEvent(Event):
"""
Handles the event of sending a Signal from a Strategy object.
Expand Down
23 changes: 23 additions & 0 deletions examples/test_mac_backtest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from decimal import Decimal

from qstrader.backtest.backtest import Backtest
from qstrader.execution_handler.execution_handler import IBSimulatedExecutionHandler
from qstrader.portfolio_handler.portfolio_handler import PortfolioHandler
from qstrader.position_sizer.position_sizer import TestPositionSizer
from qstrader.price_handler.yahoo_price_handler import YahooDailyBarPriceHandler
from qstrader.risk_manager.risk_manager import TestRiskManager
from qstrader import settings
from qstrader.strategy.moving_average_cross_strategy import MovingAverageCrossStrategy


if __name__ == "__main__":
tickers = ["AAPL"]

backtest = Backtest(
tickers, YahooDailyBarPriceHandler,
MovingAverageCrossStrategy, PortfolioHandler,
IBSimulatedExecutionHandler,
TestPositionSizer, TestRiskManager,
equity=Decimal("500000.00")
)
backtest.simulate_trading()
2 changes: 1 addition & 1 deletion examples/test_sp500tr_buy_and_hold_backtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from qstrader.execution_handler.execution_handler import IBSimulatedExecutionHandler
from qstrader.portfolio_handler.portfolio_handler import PortfolioHandler
from qstrader.position_sizer.position_sizer import TestPositionSizer
from qstrader.price_handler.price_handler import YahooDailyBarPriceHandler
from qstrader.price_handler.yahoo_price_handler import YahooDailyBarPriceHandler
from qstrader.risk_manager.risk_manager import TestRiskManager
from qstrader import settings
from qstrader.strategy.strategy import BuyAndHoldStrategy
Expand Down
12 changes: 8 additions & 4 deletions execution_handler/execution_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,15 @@ def execute_order(self, event):
quantity = event.quantity

# Obtain the fill price
bid, ask = self.price_handler.get_best_bid_ask(ticker)
if event.action == "BOT":
fill_price = Decimal(str(ask))
if self.price_handler.type == "TICK_HANDLER":
bid, ask = self.price_handler.get_best_bid_ask(ticker)
if event.action == "BOT":
fill_price = Decimal(str(ask))
else:
fill_price = Decimal(str(bid))
else:
fill_price = Decimal(str(bid))
close_price = self.price_handler.get_last_close(ticker)
fill_price = Decimal(str(close_price))

# Set a dummy exchange and calculate trade commission
exchange = "ARCA"
Expand Down
21 changes: 18 additions & 3 deletions portfolio/portfolio.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@ def _update_portfolio(self):
"""
for ticker in self.positions:
pt = self.positions[ticker]
bid, ask = self.price_handler.get_best_bid_ask(ticker)
if self.price_handler.type == "TICK_HANDLER":
bid, ask = self.price_handler.get_best_bid_ask(ticker)
else:
close_price = self.price_handler.get_last_close(ticker)
bid = close_price
ask = close_price
pt.update_market_value(bid, ask)
self.unrealised_pnl += pt.unrealised_pnl
self.realised_pnl += pt.realised_pnl
Expand All @@ -66,7 +71,12 @@ def _add_position(
"""
self._reset_values()
if ticker not in self.positions:
bid, ask = self.price_handler.get_best_bid_ask(ticker)
if self.price_handler.type == "TICK_HANDLER":
bid, ask = self.price_handler.get_best_bid_ask(ticker)
else:
close_price = self.price_handler.get_last_close(ticker)
bid = close_price
ask = close_price
position = Position(
action, ticker, quantity,
price, commission, bid, ask
Expand Down Expand Up @@ -97,7 +107,12 @@ def _modify_position(
self.positions[ticker].transact_shares(
action, quantity, price, commission
)
bid, ask = self.price_handler.get_best_bid_ask(ticker)
if self.price_handler.type == "TICK_HANDLER":
bid, ask = self.price_handler.get_best_bid_ask(ticker)
else:
close_price = self.price_handler.get_last_close(ticker)
bid = close_price
ask = close_price
self.positions[ticker].update_market_value(bid, ask)
self._update_portfolio()
else:
Expand Down
2 changes: 1 addition & 1 deletion portfolio/portfolio_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

class PriceHandlerMock(object):
def __init__(self):
pass
self.type = "TICK_HANDLER"

def get_best_bid_ask(self, ticker):
prices = {
Expand Down
2 changes: 1 addition & 1 deletion portfolio_handler/portfolio_handler_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

class PriceHandlerMock(object):
def __init__(self):
pass
self.type = "TICK_HANDLER"

def get_best_bid_ask(self, ticker):
prices = {
Expand Down
Loading

0 comments on commit de780ca

Please sign in to comment.