mirror of
https://github.com/wassname/options_backtester.git
synced 2026-07-26 13:28:06 +08:00
Fixed imports. Run examples using data from Tiingo
This commit is contained in:
@@ -1 +1,3 @@
|
||||
from . import datahandler, charts
|
||||
from .backtester import Backtest
|
||||
from .portfolio import *
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import pandas as pd
|
||||
import pyprind
|
||||
|
||||
from portfolio import Portfolio
|
||||
from .portfolio import Portfolio
|
||||
|
||||
|
||||
class Backtest:
|
||||
@@ -9,7 +9,6 @@ class Backtest:
|
||||
self.schema = schema
|
||||
self._portfolio = None
|
||||
self._data = None
|
||||
self.data_symbol = None
|
||||
|
||||
@property
|
||||
def portfolio(self):
|
||||
@@ -27,9 +26,8 @@ class Backtest:
|
||||
@data.setter
|
||||
def data(self, data):
|
||||
self._data = data
|
||||
self.data_symbol = df_symbol(data)
|
||||
|
||||
def run(self, initial_capital=1_000_000, periods='1', sma_months=None):
|
||||
def run(self, initial_capital=1_000_000, periods=1, sma_months=None):
|
||||
"""Runs a backtest and returns a dataframe with the daily balance"""
|
||||
assert self._data is not None
|
||||
assert self._portfolio is not None
|
||||
@@ -41,9 +39,9 @@ class Backtest:
|
||||
|
||||
data_iterator = self._data.iter_dates()
|
||||
|
||||
first_day = self._data['date'].iloc[0]
|
||||
last_day = self._data['date'].iloc[-1]
|
||||
rebalancing_days = pd.date_range(first_day, last_day, freq=periods +
|
||||
first_day = self._data['date'].min()
|
||||
last_day = self._data['date'].max()
|
||||
rebalancing_days = pd.date_range(first_day, last_day, freq=str(periods) +
|
||||
'BMS').to_pydatetime() if periods is not None else []
|
||||
|
||||
bar = pyprind.ProgBar(data_iterator.ngroups, bar_char='█')
|
||||
@@ -55,14 +53,10 @@ class Backtest:
|
||||
index=[self._data.start_date - pd.Timedelta(1, unit='day')])
|
||||
|
||||
for date, data in data_iterator:
|
||||
if date == self._data._data['date'][0]:
|
||||
if date in rebalancing_days or date == first_day:
|
||||
self._rebalance_portfolio(data)
|
||||
|
||||
self._update_balance(date, data)
|
||||
|
||||
if date in rebalancing_days:
|
||||
self._rebalance_portfolio(data)
|
||||
|
||||
bar.update()
|
||||
|
||||
self.balance['% change'] = self.balance['capital'].pct_change()
|
||||
@@ -73,12 +67,15 @@ class Backtest:
|
||||
def _rebalance_portfolio(self, data):
|
||||
"""Rebalances the portfolio so that the total money is allocated according to the given percentages"""
|
||||
money_total = self.current_cash + self.current_capital
|
||||
|
||||
for asset in self._portfolio.assets:
|
||||
asset_current = data[data['symbol'] == asset.symbol]
|
||||
asset_price = asset_current[self.schema['Adj Close']].values[0]
|
||||
query = '{} == "{}"'.format(self.schema['symbol'], asset.symbol)
|
||||
|
||||
asset_current = data.query(query)
|
||||
asset_price = asset_current[self.schema['adjClose']].values[0]
|
||||
|
||||
qty = (money_total * asset.percentage) // asset_price
|
||||
inventory_entry = self.inventory[self.inventory['symbol'] == asset.symbol]
|
||||
inventory_entry = self.inventory.query(query)
|
||||
self.inventory.drop(inventory_entry.index, inplace=True)
|
||||
updated_asset = pd.Series([asset.symbol, asset_price, qty])
|
||||
updated_asset.index = self.inventory.columns
|
||||
@@ -93,10 +90,11 @@ class Backtest:
|
||||
costs = []
|
||||
|
||||
for asset in self._portfolio.assets:
|
||||
asset_current = data[data['symbol'] == asset.symbol]
|
||||
inventory_asset = self.inventory[self.inventory['symbol'] == asset.symbol]
|
||||
query = '{} == "{}"'.format(self.schema['symbol'], asset.symbol)
|
||||
asset_current = data.query(query)
|
||||
inventory_asset = self.inventory.query(query)
|
||||
|
||||
cost = asset_current[self.schema['Adj Close']].values[0]
|
||||
cost = asset_current[self.schema['adjClose']].values[0]
|
||||
qty = inventory_asset['qty'].values[0]
|
||||
costs.append(cost * qty)
|
||||
|
||||
@@ -110,29 +108,27 @@ class Backtest:
|
||||
'capital': money_total,
|
||||
}, name=date)
|
||||
self.balance = self.balance.append(row)
|
||||
|
||||
|
||||
class df_symbol:
|
||||
def __init__(self, data, sma_months=None):
|
||||
|
||||
def __init__(self, data, sma_months = None):
|
||||
self.columns = data['symbol'].drop_duplicates(keep='first')
|
||||
|
||||
self.columns = data['symbol'].drop_duplicates(keep = 'first')
|
||||
|
||||
cols = pd.MultiIndex.from_product([self.columns.to_list(),data.columns[1:-1].to_list()])
|
||||
df = pd.DataFrame(columns = cols, index = data['date'].unique())
|
||||
cols = pd.MultiIndex.from_product([self.columns.to_list(), data.columns[1:-1].to_list()])
|
||||
df = pd.DataFrame(columns=cols, index=data['date'].unique())
|
||||
|
||||
for col in cols:
|
||||
symbol = data[data['symbol']==col[0]]
|
||||
symbol = data[data['symbol'] == col[0]]
|
||||
symbol = symbol.set_index('date')
|
||||
df[col[0],col[1]] = symbol[col[1]]
|
||||
df[col[0], col[1]] = symbol[col[1]]
|
||||
|
||||
self.data_symbol = df
|
||||
|
||||
def sma(self, sma_days):
|
||||
|
||||
df = pd.DataFrame(columns = self.columns)
|
||||
df = pd.DataFrame(columns=self.columns)
|
||||
|
||||
for col in self.columns:
|
||||
df[col] = self.data_symbol[col]['Adj Close'].rolling(sma_days, min_periods = 10).mean()
|
||||
df[col] = self.data_symbol[col]['Adj Close'].rolling(sma_days, min_periods=10).mean()
|
||||
return df
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import altair as alt
|
||||
import pandas as pd
|
||||
|
||||
|
||||
def returns_chart(report):
|
||||
# Time interval selector
|
||||
time_interval = alt.selection(type='interval', encodings=['x'])
|
||||
@@ -61,20 +62,18 @@ def monthly_returns_heatmap(report):
|
||||
|
||||
return chart
|
||||
|
||||
|
||||
def historical_values(data_sma, data_symbol, asset_name):
|
||||
|
||||
|
||||
asset_sma = pd.DataFrame(data_sma[asset_name])
|
||||
asset_sma = asset_sma.rename(columns = {asset_name:'value'})
|
||||
asset_sma['id'] = ['sma value']*(len(asset_sma.index))
|
||||
asset_sma = asset_sma.rename(columns={asset_name: 'value'})
|
||||
asset_sma['id'] = ['sma value'] * (len(asset_sma.index))
|
||||
asset_sma = asset_sma.dropna()
|
||||
asset_value = pd.DataFrame(data_symbol[asset_name]['Adj Close'])
|
||||
asset_value= asset_value.rename(columns={'Adj Close' :'value'})
|
||||
asset_value['id'] = ['Adj Close']*(len(asset_value.index))
|
||||
asset_value = asset_value.rename(columns={'Adj Close': 'value'})
|
||||
asset_value['id'] = ['Adj Close'] * (len(asset_value.index))
|
||||
|
||||
asset_value = asset_value.append(asset_sma)
|
||||
asset_value['index'] = asset_value.index
|
||||
plot = alt.Chart(asset_value).mark_line().encode(x='index:T',
|
||||
y=alt.Y('value:Q'),
|
||||
color='id'
|
||||
)
|
||||
return plot
|
||||
plot = alt.Chart(asset_value).mark_line().encode(x='index:T', y=alt.Y('value:Q'), color='id')
|
||||
return plot
|
||||
@@ -3,7 +3,10 @@ class Schema:
|
||||
Used to run validations and provide uniform access to fields in the data set.
|
||||
"""
|
||||
|
||||
columns = ["symbol", "date", "open", "close", "high", "low", "volume", "Adj Close"]
|
||||
columns = [
|
||||
"symbol", "date", "open", "close", "high", "low", "volume", "adjClose", "adjHigh", "adjLow", "adjOpen",
|
||||
"adjVolume", "divCash", "splitFactor"
|
||||
]
|
||||
|
||||
def canonical():
|
||||
"""Builder method that returns a `Schema` with default mappings"""
|
||||
|
||||
+1660
-84550
File diff suppressed because one or more lines are too long
@@ -5,4 +5,4 @@ class Asset:
|
||||
self.percentage = percentage
|
||||
|
||||
def __repr__(self):
|
||||
return "Asset(symbol={}, percentage={}, direction={})".format(self.symbol, self.percentage, self.direction)
|
||||
return "Asset(symbol={}, percentage={})".format(self.symbol, self.percentage)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
from .charts import returns_chart, returns_histogram, monthly_returns_heatmap
|
||||
@@ -1,62 +0,0 @@
|
||||
"""Generates charts from a portfolio report"""
|
||||
|
||||
import altair as alt
|
||||
|
||||
|
||||
def returns_chart(report):
|
||||
# Time interval selector
|
||||
time_interval = alt.selection(type='interval', encodings=['x'])
|
||||
|
||||
# Area plot
|
||||
areas = alt.Chart().mark_area(opacity=0.7).encode(x='index:T',
|
||||
y=alt.Y('accumulated return:Q', axis=alt.Axis(format='%')))
|
||||
|
||||
# Nearest point selector
|
||||
nearest = alt.selection(type='single', nearest=True, on='mouseover', fields=['index'], empty='none')
|
||||
|
||||
points = areas.mark_point().encode(opacity=alt.condition(nearest, alt.value(1), alt.value(0)))
|
||||
|
||||
# Transparent date selector
|
||||
selectors = alt.Chart().mark_point().encode(
|
||||
x='index:T',
|
||||
opacity=alt.value(0),
|
||||
).add_selection(nearest)
|
||||
|
||||
text = areas.mark_text(
|
||||
align='left', dx=5,
|
||||
dy=-5).encode(text=alt.condition(nearest, 'accumulated return:Q', alt.value(' '), format='.2%'))
|
||||
|
||||
layered = alt.layer(selectors,
|
||||
points,
|
||||
text,
|
||||
areas.encode(
|
||||
alt.X('index:T', axis=alt.Axis(title='date'), scale=alt.Scale(domain=time_interval))),
|
||||
width=700,
|
||||
height=350,
|
||||
title='Wealth over time')
|
||||
|
||||
lower = areas.properties(width=700, height=70).add_selection(time_interval)
|
||||
|
||||
return alt.vconcat(layered, lower, data=report.reset_index())
|
||||
|
||||
|
||||
def returns_histogram(report):
|
||||
bar = alt.Chart(report).mark_bar().encode(x=alt.X('% change:Q',
|
||||
bin=alt.BinParams(maxbins=100),
|
||||
axis=alt.Axis(format='%')),
|
||||
y='count():Q')
|
||||
return bar
|
||||
|
||||
|
||||
def monthly_returns_heatmap(report):
|
||||
resample = report.resample('M')['capital'].last()
|
||||
monthly_returns = resample.pct_change().reset_index()
|
||||
monthly_returns['capital'].iat[0] = resample.iloc[0] / report.iloc[0]['capital'] - 1
|
||||
monthly_returns.columns = ['date', 'capital']
|
||||
|
||||
chart = alt.Chart(monthly_returns).mark_rect().encode(
|
||||
alt.X('year(date):O', title='Year'), alt.Y('month(date):O', title='Month'),
|
||||
alt.Color('mean(capital)', title='Return', scale=alt.Scale(scheme='redyellowgreen')),
|
||||
alt.Tooltip('mean(capital)', format='.2f')).properties(title='Monthly Returns')
|
||||
|
||||
return chart
|
||||
Reference in New Issue
Block a user