# Import basic libraries for manipulating data.
# Please refer to xarray.pydata.org for xarray documentation.
# xarray works optimally with N-dimensional datasets in Python
# and is well suited for financial datasets with labels "time",
# "field" and "asset". xarray data structures can also be easily
# converted to pandas dataframes.
import xarray as xr
import numpy as np
import pandas as pd
# Import quantnet libraries.
import qnt.data as qndata # data loading and manipulation
import qnt.stepper as qnstepper # strategy definition
import qnt.stats as qnstats # key statistics
import qnt.graph as qngraph # graphical tools
import qnt.forward_looking as qnfl # forward looking checking
import qnt.ta as qnta # TA functions
# display function for fancy displaying:
from IPython.display import display
import json
import time
import datetime as dt
# Load all available asset names since given date.
assets = qndata.load_assets(tail=dt.timedelta(days=5*365))
assets_names = [i["id"] for i in assets]
# Load all available data since given date.
# It is possible to set a max_date in the call in order to
# develop the system on a limited in-sample period and later
# test the system on unseen data after max_date.
# A submission will be accepted only if no max_date is set,
# as submissions will be evaluated on live data on a daily basis.
data = qndata.load_data(tail=dt.timedelta(days=5*365),
dims=("time", "field", "asset"),
assets=assets_names,
forward_order=True)
# A buy-and-hold strategy on liquid assets allocates
# constant fractions of capital to all liquid assets.
# Here xarray data structures are converted to pandas
# dataframes for simplicity in order to describe the
# development process.
# xarray.DataArray are converted to pandas dataframes:
is_liquid = data.loc[:,"is_liquid",:]
# set and normalize weights:
weights = is_liquid / is_liquid.sum('asset').fillna(0.0)
# display statistics for B'n'H strategy
stat = qnstats.calc_stat(data, weights, slippage_factor=0.05)
display(stat.to_pandas().tail())
We will use some fundamental indicators to improve this strategy.
At first, let's retrieve the data from the fundamental database.
You can also discover available attributes here: http://xbrlview.fasb.org/yeti/resources/yeti-gwt/Yeti.jsp (us-gaap taxonomy)
You cal aso read the Introduction to financial statements: https://www.sec.gov/oiea/reportspubs/investor-publications/beginners-guide-to-financial-statements.html
# we will use this function to load parsed XBRL fillings
help(qndata.load_secgov_forms)
# build dict cik -> asset
ciks = [(a['cik'], a) for a in assets if a['cik'] is not None]
ciks = dict(ciks)
# define result array
fundamental = xr.concat(
[data.sel(field='close')] * 4,
pd.Index(['assets', 'liabilities', 'shares', 'eps'], name='field')
)
fundamental[:] = np.nan
def extract_last_fact(facts, name):
res = tuple(f for f in facts if f['name'] == name)
if len(res) > 0:
res = max(res, key = lambda f: f['period']['value'])
res = res['value']
else:
res = None
return res
print_first_record = True
progress = 0
st = time.time()
for form in qndata.load_secgov_forms(
ciks=list(ciks.keys()), # load only liquid ciks
types=['10-Q'], # only quarter reports
facts=[
'us-gaap:EarningsPerShareDiluted',
'us-gaap:Liabilities',
'us-gaap:Assets',
'us-gaap:CommonStockSharesOutstanding'
],
skip_segment = True,
tail=dt.timedelta(days=5*365)
):
facts = form['facts']
# print first report to analyze the structure
if len(facts) > 0 and print_first_record:
print("The report example:")
display(form)
print_first_record = False
# display progress
progress += 1
if progress % 500 == 0:
print("Progress:", progress, form['date'], time.time() - st)
# process report
asset_id = ciks[form['cik']]['id']
if asset_id not in data.asset:
continue
date = form['date']
date = fundamental.time.loc[date:]
if len(date) < 1:
print("wrong date", form['date'])
continue
date = date[0].values
fundamental.loc[{'asset':asset_id, 'time':date, 'field':'shares'}] \
= extract_last_fact(facts, 'us-gaap:CommonStockSharesOutstanding')
fundamental.loc[{'asset':asset_id, 'time':date, 'field':'eps'}] \
= extract_last_fact(facts, 'us-gaap:EarningsPerShareDiluted')
fundamental.loc[{'asset':asset_id, 'time':date, 'field':'liabilities'}] \
= extract_last_fact(facts, 'us-gaap:Liabilities')
fundamental.loc[{'asset':asset_id, 'time':date, 'field':'assets'}] \
= extract_last_fact(facts, 'us-gaap:Assets')
print('Loaded')
# now we will prepare some ratios
ratios = xr.concat(
[data.sel(field='close')] * 4,
pd.Index(['price/earnings', 'price/equity', 'liabilites/equity', 'market_cap'], name='field')
)
eps_y = qnta.sma(fundamental.sel(field='eps'), 4) * 4
ratios.loc[{'field':'price/earnings'}] = data.sel(field='close') / eps_y#.ffill('time')
equity = fundamental.sel(field='assets') - fundamental.sel(field='liabilities')
equity_per_share = equity/fundamental.sel(field='shares')
ratios.loc[{'field':'price/equity'}] = data.sel(field='close') / equity_per_share#.ffill('time')
ratios.loc[{'field':'liabilites/equity'}] = fundamental.sel(field='liabilities') / equity#.ffill('time')
ratios.loc[{'field':'market_cap'}] = data.sel(field='close') * fundamental.sel(field='shares')#.ffill('time')
ratios = ratios.ffill('time')
# we will use these ratios to build the output
output = data.sel(field="is_liquid")
output = output.where(ratios.sel(field='price/earnings') > 4)
output = output.where(ratios.sel(field='price/earnings') < 25)
output = output.where(ratios.sel(field='price/equity') > 0.7)
output = output.where(ratios.sel(field='price/equity') < 1.3)
output = output.where(ratios.sel(field='liabilites/equity') > 2)
output = output.where(ratios.sel(field='liabilites/equity') < 8)
output = output.where(ratios.sel(field='market_cap').rank('asset') > 50)
output = output.fillna(0)
output /= output.sum('asset')
output = xr.where(output > 0.1, 0.1, output)
# display stats
stat = qnstats.calc_stat(data, output, slippage_factor=0.05)
display(stat.to_pandas().tail())
# print(output.isel(time=-1).dropna('asset').asset.values)
def print_stat(stat):
"""Prints selected statistical key indicators:
- the global Sharpe ratio of the strategy;
- the global mean profit;
- the global volatility;
- the maximum drawdown.
Note that Sharpe ratio, mean profit and volatility
apply to max simulation period, and not to the
rolling basis of 3 years.
"""
days = len(stat.coords["time"])
returns = stat.loc[:, "relative_return"]
equity = stat.loc[:, "equity"]
sharpe_ratio = qnstats.calc_sharpe_ratio_annualized(
returns,
max_periods=days,
min_periods=days).to_pandas().values[-1]
profit = (qnstats.calc_mean_return_annualized(
returns,
max_periods=days,
min_periods=days).to_pandas().values[-1])*100.0
volatility = (qnstats.calc_volatility_annualized(
returns,
max_periods=days,
min_periods=days).to_pandas().values[-1])*100.0
max_ddown = (qnstats.calc_max_drawdown(
qnstats.calc_underwater(equity)).to_pandas().values[-1])*100.0
print("Sharpe Ratio : ", "{0:.3f}".format(sharpe_ratio))
print("Mean Return [%] : ", "{0:.3f}".format(profit))
print("Volatility [%] : ", "{0:.3f}".format(volatility))
print("Maximum Drawdown [%] : ", "{0:.3f}".format(-max_ddown))
print_stat(stat)
# show plot with profit and losses:
performance = stat.to_pandas()["equity"].iloc[(252*3):]
qngraph.make_plot_filled(performance.index, performance, name="PnL (Equity)", type="log")
# show underwater chart:
UWchart = stat.to_pandas()["underwater"].iloc[(252*3):]
qngraph.make_plot_filled(UWchart.index, UWchart, color="darkred", name="Underwater Chart", range_max=0)
# show rolling Sharpe ratio on a 3-year basis:
SRchart = stat.to_pandas()["sharpe_ratio"].iloc[(252*3):]
qngraph.make_plot_filled(SRchart.index, SRchart, color="#F442C5", name="Rolling SR")
# show bias chart:
biaschart = stat.to_pandas()["bias"].iloc[(252*3):]
qngraph.make_plot_filled(biaschart.index, biaschart, color="#5A6351", name="Bias Chart")
# Finally, we write the last mandatory step for submission,
# namely writing output to file:
qndata.write_output(output)