Skip to content

Commit

Permalink
Re-added unit tests for Moving Avg Cross and Buy-and-Hold strategies.…
Browse files Browse the repository at this point in the history
… Eliminated 'stochastic ticker' bug in backtest by forcing the Yahoo price handler to always order ticker DataFrame deterministically.
  • Loading branch information
mhallsmoore committed Feb 16, 2017
1 parent c8c854d commit 84b2148
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 54 deletions.
11 changes: 11 additions & 0 deletions data/AMZN.csv
@@ -0,0 +1,11 @@
Ticker,Time,Bid,Ask
AMZN,01.02.2016 00:00:01.562,502.10001,502.11999
AMZN,01.02.2016 00:00:02.909,502.10003,502.11997
AMZN,01.02.2016 00:00:04.396,502.10007,502.11993
AMZN,01.02.2016 00:00:05.970,502.10008,502.11992
AMZN,01.02.2016 00:00:07.402,502.10009,502.11991
AMZN,01.02.2016 00:00:08.948,502.10012,502.11988
AMZN,01.02.2016 00:00:10.316,502.10013,502.11987
AMZN,01.02.2016 00:00:11.829,502.10015,502.11985
AMZN,01.02.2016 00:00:13.212,502.10016,502.11984
AMZN,01.02.2016 00:00:14.616,502.10015,502.11985
11 changes: 11 additions & 0 deletions data/GOOG.csv
@@ -0,0 +1,11 @@
Ticker,Time,Bid,Ask
GOOG,01.02.2016 00:00:01.358,683.56000,683.58000
GOOG,01.02.2016 00:00:02.544,683.55998,683.58002
GOOG,01.02.2016 00:00:03.765,683.55999,683.58001
GOOG,01.02.2016 00:00:05.215,683.56001,683.57999
GOOG,01.02.2016 00:00:06.509,683.56002,683.57998
GOOG,01.02.2016 00:00:07.964,683.55999,683.58001
GOOG,01.02.2016 00:00:09.369,683.56000,683.58000
GOOG,01.02.2016 00:00:10.823,683.56001,683.57999
GOOG,01.02.2016 00:00:12.221,683.56000,683.58000
GOOG,01.02.2016 00:00:13.546,683.56000,683.58000
11 changes: 11 additions & 0 deletions data/MSFT.csv
@@ -0,0 +1,11 @@
Ticker,Time,Bid,Ask
MSFT,01.02.2016 00:00:01.578,50.14999,50.17001
MSFT,01.02.2016 00:00:02.988,50.15002,50.16998
MSFT,01.02.2016 00:00:04.360,50.15003,50.16997
MSFT,01.02.2016 00:00:05.752,50.15004,50.16996
MSFT,01.02.2016 00:00:07.148,50.15005,50.16995
MSFT,01.02.2016 00:00:08.416,50.15003,50.16997
MSFT,01.02.2016 00:00:09.904,50.15000,50.17000
MSFT,01.02.2016 00:00:11.309,50.15001,50.16999
MSFT,01.02.2016 00:00:12.655,50.15003,50.16997
MSFT,01.02.2016 00:00:14.153,50.15005,50.16995
4 changes: 2 additions & 2 deletions examples/buy_and_hold_backtest.py
Expand Up @@ -40,7 +40,7 @@ def calculate_signals(self, event):

def run(config, testing, tickers, filename):
# Backtest information
title = ['Buy and Hold Example on SPY']
title = ['Buy and Hold Example on %s' % tickers[0]]
initial_equity = 10000.0
start_date = datetime.datetime(2000, 1, 1)
end_date = datetime.datetime(2014, 1, 1)
Expand All @@ -56,7 +56,7 @@ def run(config, testing, tickers, filename):
events_queue, title=title
)
results = backtest.simulate_trading(testing=testing)
return
return results


if __name__ == "__main__":
Expand Down
58 changes: 11 additions & 47 deletions examples/test_examples.py
Expand Up @@ -7,11 +7,9 @@
"""
import os
import math
import unittest

from qstrader import settings
from qstrader.statistics import load
import examples.buy_and_hold_backtest
import examples.moving_average_cross_backtest

Expand All @@ -30,72 +28,38 @@ def setUp(self):
def test_buy_and_hold_backtest(self):
"""
Test buy_and_hold
Bar 0, at 2000-01-01 00:00:00
Bar ????, at 2014-01-01 00:00:00
Begins at 2000-01-01 00:00:00
End at 2014-01-01 00:00:00
"""
tickers = ["SPY"]
filename = os.path.join(
settings.TEST.OUTPUT_DIR,
settings.TEST.OUTPUT_DIR,
"buy_and_hold_backtest.pkl"
)
results = examples.buy_and_hold_backtest.run(
self.config, self.testing, tickers, filename
)
for (key, expected) in [
('sharpe', 0.5968),
('max_drawdown_pct', 5.0308),
('max_drawdown', 30174.01)
('sharpe', 0.25234757),
('max_drawdown_pct', 0.79589309),
]:
value = float(results[key])
self.assertAlmostEqual(expected, value)
for (key, expected) in [
('equity_returns',
{
'min': -1.6027, 'max': 1.2553,
'first': 0.0000, 'last': -0.0580
}
),
('drawdowns',
{
'min': 0.0, 'max': 30174.01,
'first': 0.0, 'last': 4537.02
}
),
('equity',
{
'min': 488958.0, 'max': 599782.01,
'first': 500000.0, 'last': 595244.99
}
)
]:
values = results[key]
self.assertAlmostEqual(
float(min(values)), expected['min']
)
self.assertAlmostEqual(
float(max(values)), expected['max']
)
self.assertAlmostEqual(
float(values.iloc[0]), expected['first']
)
# self.assertAlmostEqual(
# float(values.iloc[-1]),
# expected['last']
# ) # TODO FAILING BY 1 CENT
stats = load(filename)
results = stats.get_results()
self.assertAlmostEqual(float(results['sharpe']), 0.5968)

def test_moving_average_cross_backtest(self):
"""
Test moving average crossover backtest
Begins at 2000-01-01 00:00:00
End at 2014-01-01 00:00:00
"""
tickers = ["AAPL", "SPY"]
filename = os.path.join(
settings.TEST.OUTPUT_DIR,
settings.TEST.OUTPUT_DIR,
"mac_backtest.pkl"
)
results = examples.moving_average_cross_backtest.run(
self.config, self.testing, tickers, filename
)
self.assertAlmostEqual(float(results['sharpe']), 0.6430103385)
self.assertAlmostEqual(
float(results['sharpe']), 0.643009566
)
6 changes: 5 additions & 1 deletion qstrader/price_handler/yahoo_daily_csv_bar.py
Expand Up @@ -72,7 +72,11 @@ def _merge_sort_ticker_data(self):
start = df.index.searchsorted(self.start_date)
if self.end_date is not None:
end = df.index.searchsorted(self.end_date)
# Determine how to slice
# This is added so that the ticker events are
# always deterministic, otherwise unit test values
# will differ
df['colFromIndex'] = df.index
df = df.sort_values(by=["colFromIndex", "Ticker"])
if start is None and end is None:
return df.iterrows()
elif start is not None and end is None:
Expand Down
14 changes: 10 additions & 4 deletions qstrader/trading_session/backtest.py
Expand Up @@ -109,7 +109,10 @@ def _run_backtest(self):
self.price_handler.stream_next()
else:
if event is not None:
if event.type == EventType.TICK or event.type == EventType.BAR:
if (
event.type == EventType.TICK or
event.type == EventType.BAR
):
self.cur_time = event.time
# Generate any sentiment events here
if self.sentiment_handler is not None:
Expand Down Expand Up @@ -138,9 +141,12 @@ def simulate_trading(self, testing=False):
results = self.statistics.get_results()
print("---------------------------------")
print("Backtest complete.")
print("Sharpe Ratio: %s" % results["sharpe"])
print("Max Drawdown: %s" % results["max_drawdown"])
print("Max Drawdown Pct: %s" % results["max_drawdown_pct"])
print("Sharpe Ratio: %0.2f" % results["sharpe"])
print(
"Max Drawdown: %0.2f%%" % (
results["max_drawdown_pct"] * 100.0
)
)
if not testing:
self.statistics.plot_results()
return results

0 comments on commit 84b2148

Please sign in to comment.