Skip to content

Commit fe3b5a0

Browse files
committed
backtest strats
some ta backtests
1 parent ca967c8 commit fe3b5a0

11 files changed

+4740
-1
lines changed

.gitignore

+8
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ __pycache__/
66
# C extensions
77
*.so
88

9+
# data
10+
*.csv
11+
*.xlsx
12+
13+
# pycharm
14+
*.xml
15+
*.iml
16+
917
# Distribution / packaging
1018
.Python
1119
build/

.idea/.gitignore

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backtest/README.md

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Backtest
2+
3+
4+
|Index |Backtest |
5+
|----:|:---------------------------------------------------------------------------------|-----------:|
6+
|1 | [Buy and Hold](./buy_hold.py) |
7+
|2 | [Moving Average cross](./ma_cross.py) |
8+
|3 | [Moving Average double cross](./ma_double_cross.py) |
9+
10+
```python
11+
12+
```

backtest/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-

backtest/buy_hold.py

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import os
2+
import pandas as pd
3+
from datetime import datetime
4+
import backtrader as bt
5+
6+
class BuyAndHold(bt.Strategy):
7+
def __init__(self):
8+
# To keep track of pending orders and buy price/commission
9+
self.order = None
10+
11+
def log(self, txt, dt=None):
12+
''' Logging function fot this strategy'''
13+
dt = dt or self.datas[0].datetime.date(0)
14+
print('%s, %s' % (dt.isoformat(), txt))
15+
16+
def start(self):
17+
self.val_start = self.broker.get_cash() # keep the starting cash
18+
19+
def nextstart(self):
20+
# Buy all the available cash
21+
# self.order_target_value(target=self.broker.get_cash())
22+
#size = int(self.broker.get_cash() / self.data)
23+
#self.order = self.buy(size=size)
24+
self.buy()
25+
26+
def notify_trade(self, trade):
27+
if not trade.isclosed:
28+
return
29+
self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' % (trade.pnl, trade.pnlcomm))
30+
31+
def notify_order(self, order):
32+
if order.status in [order.Submitted, order.Accepted]:
33+
# Buy/Sell order submitted/accepted to/by broker - Nothing to do
34+
return
35+
36+
# Check if an order has been completed
37+
# Attention: broker could reject order if not enough cash
38+
if order.status in [order.Completed]:
39+
if order.isbuy():
40+
self.log(
41+
'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
42+
(order.executed.price,
43+
order.executed.value,
44+
order.executed.comm))
45+
46+
self.buyprice = order.executed.price
47+
self.buycomm = order.executed.comm
48+
else: # Sell
49+
self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
50+
(order.executed.price,
51+
order.executed.value,
52+
order.executed.comm))
53+
54+
self.bar_executed = len(self)
55+
56+
def next(self):
57+
# Simply log the closing price of the series from the reference
58+
# self.log('Close, %.2f' % self.data.close[0])
59+
pass
60+
61+
def stop(self):
62+
# calculate the actual returns
63+
self.roi = (self.broker.get_value() / self.val_start) - 1.0
64+
print('ROI: {:.2f}%'.format(100.0 * self.roi))
65+
66+
67+
if __name__ == '__main__':
68+
# Create a cerebro entitysetsizing
69+
cerebro = bt.Cerebro()
70+
71+
datapath = os.path.join('../data/', 'AAPL.csv')
72+
73+
# Create a Data Feed
74+
data = bt.feeds.YahooFinanceCSVData(
75+
dataname=datapath,
76+
# Do not pass values before this date
77+
fromdate=datetime(2010, 1, 1),
78+
# Do not pass values before this date
79+
todate=datetime(2015, 12, 31),
80+
# Do not pass values after this date
81+
reverse=False)
82+
83+
# Add the Data Feed to Cerebro
84+
cerebro.adddata(data)
85+
86+
# Set our desired cash start
87+
cerebro.broker.setcash(100000.0)
88+
89+
# Add a FixedSize sizer according to the stake
90+
# cerebro.addsizer(bt.sizers.FixedSize, stake=10)
91+
cerebro.addsizer(bt.sizers.PercentSizer, percents=95)
92+
93+
# Set the commission - 0.1% ... divide by 100 to remove the %
94+
cerebro.broker.setcommission(commission=0.001)
95+
96+
# Print out the starting conditions
97+
print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
98+
99+
# Add Analyzer
100+
cerebro.addanalyzer(bt.analyzers.PyFolio, _name='pyfolio')
101+
102+
# Add a strategy
103+
cerebro.addstrategy(BuyAndHold)
104+
105+
# Run over everything
106+
results = cerebro.run()
107+
108+
# Print out the final result
109+
print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
110+
cerebro.plot(style='candlestick')
111+
112+
strat = results[0]
113+
pyfoliozer = strat.analyzers.getbyname('pyfolio')
114+
returns, positions, transactions, gross_lev = pyfoliozer.get_pf_items()
115+
print('-------------- RETURNS ----------------')
116+
print(returns)
117+
print('-------------- POSITIONS ----------------')
118+
print(positions)
119+
print('-------------- TRANSACTIONS ----------------')
120+
print(transactions)
121+
print('-------------- GROSS LEVERAGE ----------------')
122+
print(gross_lev)
123+
124+
import empyrical as ep
125+
import pyfolio as pf
126+
import matplotlib.pyplot as plt
127+
perf_stats_strat = pf.timeseries.perf_stats(returns)
128+
drawdown_table = pf.timeseries.gen_drawdown_table(returns, 5)
129+
monthly_ret_table = ep.aggregate_returns(returns, 'monthly')
130+
monthly_ret_table = monthly_ret_table.unstack().round(3)
131+
ann_ret_df = pd.DataFrame(ep.aggregate_returns(returns, 'yearly'))
132+
ann_ret_df = ann_ret_df.unstack().round(3)
133+
print('-------------- PERFORMANCE ----------------')
134+
print(perf_stats_strat)
135+
print('-------------- DRAWDOWN ----------------')
136+
print(drawdown_table)
137+
print('-------------- MONTHLY RETURN ----------------')
138+
print(monthly_ret_table)
139+
print('-------------- ANNUAL RETURN ----------------')
140+
print(ann_ret_df)
141+
142+
pf.create_full_tear_sheet(
143+
returns,
144+
positions=positions,
145+
transactions=transactions,
146+
#live_start_date='2005-05-01',
147+
round_trips=False)
148+
plt.show()

backtest/hist_downloader.py

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
4+
import os
5+
import argparse
6+
from datetime import datetime, timedelta
7+
import pandas as pd
8+
import numpy as np
9+
import pandas_datareader.data as web
10+
# import yfinance as yf
11+
12+
def save(df, fn):
13+
df = df[['Open', 'High', 'Low', 'Close', 'Adj Close', 'Volume']]
14+
df.to_csv(fn)
15+
16+
def run(args):
17+
current_path = os.path.dirname(os.path.abspath(__file__))
18+
hist_path = os.path.join(current_path, '..\data')
19+
end_date = datetime.today()
20+
#start_date = end_date + timedelta(days=-5 * 365)
21+
start_date = datetime(2006, 1, 1)
22+
23+
if args.index:
24+
print('Index downloading .............')
25+
data = web.DataReader(name='^GSPC', data_source='yahoo', start=start_date, end=end_date)
26+
save(data, os.path.join(hist_path, f'SPX.csv'))
27+
data = web.DataReader(name='^DJI', data_source='yahoo', start=start_date, end=end_date)
28+
save(data, os.path.join(hist_path, f'DJI.csv'))
29+
data = web.DataReader(name='^NDX', data_source='yahoo', start=start_date, end=end_date)
30+
save(data, os.path.join(hist_path, f'NDX.csv'))
31+
data = web.DataReader(name='^RUT', data_source='yahoo', start=start_date, end=end_date)
32+
save(data, os.path.join(hist_path, f'RUT.csv'))
33+
print('Index downloaded')
34+
35+
if args.dow:
36+
print('Dow30 downloading .............')
37+
df = pd.read_csv(os.path.join(hist_path, 'dow30.csv'), header=None)
38+
for idx, row in df.iterrows():
39+
try:
40+
data = web.DataReader(name=row[0], data_source='yahoo', start=start_date, end=end_date)
41+
save(data, os.path.join(hist_path, f'{row[0]}.csv'))
42+
except Exception as e:
43+
print(f'{row[0]} failed. {str(e)}')
44+
print('Dow30 downloaded')
45+
46+
if args.sector:
47+
print('Sector ETF downloading .............')
48+
df = pd.read_csv(os.path.join(hist_path, 'sectoretf.csv'), header=None)
49+
for idx, row in df.iterrows():
50+
try:
51+
data = web.DataReader(name=row[0], data_source='yahoo', start=start_date, end=end_date)
52+
save(data, os.path.join(hist_path, f'{row[0]}.csv'))
53+
except Exception as e:
54+
print(f'{row[0]} failed. {str(e)}')
55+
print('Sector ETF downloaded')
56+
57+
if args.country:
58+
print('Country ETF downloading .............')
59+
df = pd.read_csv(os.path.join(hist_path, 'countryetf.csv'), header=None)
60+
for idx, row in df.iterrows():
61+
try:
62+
data = web.DataReader(name=row[0], data_source='yahoo', start=start_date, end=end_date)
63+
save(data, os.path.join(hist_path, f'{row[0]}.csv'))
64+
except Exception as e:
65+
print(f'{row[0]} failed. {str(e)}')
66+
print('Country ETF downloaded')
67+
68+
if args.taa:
69+
print('Mebane Faber TAA downloading .............')
70+
symbols = ['SPY', 'EFA', 'AGG', 'VNQ', 'GLD'] # sp, em, bond, real estate, gold
71+
for sym in symbols:
72+
try:
73+
data = web.DataReader(name=sym, data_source='yahoo', start=start_date, end=end_date)
74+
save(data, os.path.join(hist_path, f'{sym}.csv'))
75+
except Exception as e:
76+
print(f'{sym} failed. {str(e)}')
77+
print('Mebane Faber TAA downloaded')
78+
79+
if args.sym:
80+
print(f'{args.sym} downloading .............')
81+
symbols = args.sym.split('+')
82+
for sym in symbols:
83+
try:
84+
data = web.DataReader(name=sym, data_source='yahoo', start=start_date, end=end_date)
85+
save(data, os.path.join(hist_path, f'{sym}.csv'))
86+
except Exception as e:
87+
print(f'{sym} failed. {str(e)}')
88+
print(f'{args.sym} downloaded')
89+
90+
91+
if __name__ == "__main__":
92+
parser = argparse.ArgumentParser(description='Historical Downloader')
93+
parser.add_argument('--index', action='store_true')
94+
parser.add_argument('--dow', action='store_true')
95+
parser.add_argument('--sector', action='store_true')
96+
parser.add_argument('--country', action='store_true')
97+
parser.add_argument('--taa', action='store_true')
98+
parser.add_argument('--sym', help='symbol')
99+
100+
args = parser.parse_args()
101+
run(args)

0 commit comments

Comments
 (0)