Files
quantconnect--lean/PythonToolbox/quantconnect/LeanOutputReader.py
Jack Simonson d8039856f0 Update LeanOutputReader.py
Removed two crisis-situation charts to condense all crisis events to one page. Reduced text size in monthly returns matrix plot to ensure readability.
2019-07-26 09:46:14 -07:00

763 lines
38 KiB
Python

# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from base64 import b64encode
from datetime import date, datetime, timedelta
from io import BytesIO
import os
import re
import pandas as pd
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
font = {'family': 'Open Sans Condensed'}
matplotlib.rc('font',**font)
la = matplotlib.font_manager.FontManager()
lu = matplotlib.font_manager.FontProperties(family = "Open Sans Condensed")
from matplotlib.dates import DateFormatter
import matplotlib.colors as mcolors
from matplotlib.patches import Patch
from matplotlib.lines import Line2D
class LeanOutputReader(object):
def __init__(self, data, dpi, output):
self.data = data
self.dpi = dpi
self.output = output
# Parse the input file and make sure the input file is complete
self.is_drawable = False
if "Strategy Equity" in data["Charts"] and "Benchmark" in data["Charts"]:
# Get value series from the input file
strategySeries = data["Charts"]["Strategy Equity"]["Series"]["Equity"]["Values"]
benchmarkSeries = data["Charts"]["Benchmark"]["Series"]["Benchmark"]["Values"]
df_strategy = pd.DataFrame(strategySeries).set_index('x')
df_benchmark = pd.DataFrame(benchmarkSeries).set_index('x')
df_strategy = df_strategy[df_strategy > 0]
df_benchmark = df_benchmark[df_benchmark > 0]
df_strategy = df_strategy[~df_strategy.index.duplicated(keep='first')]
df_benchmark = df_benchmark[~df_benchmark.index.duplicated(keep='first')]
df = pd.concat([df_strategy,df_benchmark],axis = 1)
df.columns = ['Strategy','Benchmark']
df = df.set_index(pd.to_datetime(df.index, unit='s'))
self.df = df.fillna(method = 'ffill')
self.df = df.fillna(method = 'bfill')
self.initStrategyValue = self.df["Strategy"][0]
self.initBenchmarkValue = self.df["Benchmark"][0]
# Get order information from the input file
self.orders = data["Orders"]
df_this = self.df.copy()
df_this.drop("Benchmark",1,inplace = True)
df_values = pd.DataFrame()
df_values["Value"] = [x["Value"] for x in self.orders.values()]
df_values = df_values.set_index([[datetime.strptime(x["Time"][0:19], '%Y-%m-%dT%H:%M:%S') for x in self.orders.values()]])
df_this = df_this.join(df_values, how = "outer")
df_this["Cash"] = -df_this["Value"]
df_this["Cash"][0] = df_this["Strategy"][0]
df_this.fillna(0,inplace = True)
df_this["Cash"] = np.cumsum(df_this["Cash"])
df_this["Value"] = df_this["Strategy"] - df_this["Cash"]
self.df_cash = df_this
# Predefine this dataframe which is used to keep cash flow
self.df_values = pd.DataFrame()
# True means the essential information is complete
self.is_drawable = True
def fig_to_base64(self, filename, fig):
base64 = 'data:image/png;base64,'
if self.output is None:
bytesIO = BytesIO()
fig.savefig(bytesIO, format = 'png', dpi = self.dpi, bbox_inches='tight')
bytesIO.seek(0)
base64 += b64encode(bytesIO.read()).decode('utf-8').replace('\n', '')
else:
filename = f"{self.output}/{filename}"
fig.savefig(filename, dpi = self.dpi, bbox_inches='tight')
with open(filename, "rb") as fp:
base64 += b64encode(fp.read()).decode('utf-8').replace('\n', '')
return base64
def cumulative_return(self, name = "cumulative-return.png", width = 11.5, height = 2.5):
if not self.is_drawable: return str()
# Prepare the dataset to be used for drawing charts
df_this = self.df.copy()
df_this["Strategy"] = (df_this["Strategy"]/self.initStrategyValue-1)*100
df_this["Benchmark"] = (df_this["Benchmark"]/self.initBenchmarkValue-1)*100
# Drawing charts
plt.figure()
ax = df_this.plot(color = ["#ffbb51","#b3bcc0"], linewidth = 0.5)
handles, labels = ax.get_legend_handles_labels()
p = plt.Rectangle((0, 0), 1, 1, fc="#ffbb51")
q = plt.Rectangle((0, 0,), 1, 1, fc = "#b3bcc0")
leg = ax.legend([p, q], [label for i,label in enumerate(labels)], handlelength=0.8, handleheight=0.8, frameon = False, fontsize = 8)
fig = ax.get_figure()
plt.xticks(rotation = 0,ha = 'center', fontsize = 8)
plt.yticks(fontsize = 8)
plt.xlabel("")
ax.xaxis.set_major_formatter(DateFormatter("%b %Y"))
plt.axhline(y = 0, color = '#d5d5d5')
plt.setp(ax.spines.values(), color='#d5d5d5')
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
#for line in leg.get_lines(): line.set_linewidth(3)
plt.setp([ax.get_xticklines(), ax.get_yticklines()], color='#d5d5d5')
plt.ylabel("")
plt.xlabel("")
ax.yaxis.grid(True, color = "#ececec")
fig.set_size_inches(width, height)
base64 = self.fig_to_base64(name, fig)
plt.cla()
plt.clf()
plt.close('all')
return base64
def daily_returns(self, name = "daily-returns.png", width = 11.5, height = 2.5):
if not self.is_drawable: return
# Prepare the dataset to be used for drawing charts
df_this = self.df.copy()
df_this.drop("Benchmark",1,inplace = True)
df_this = df_this.groupby([df_this.index.date]).apply(lambda x: x.tail(1))
df_this.index = df_this.index.droplevel(1)
ret_strategy = np.array([self.initStrategyValue] + df_this["Strategy"].tolist())
ret_strategy = ret_strategy[1:]/ret_strategy[:-1] - 1
df_this["Strategy"] = ret_strategy*100
df_this.index = pd.to_datetime(df_this.index)
if len(df_this) > 1:
dg = df_this.copy()
dg.index = dg.index + timedelta(0.99)
df_this = df_this.append(dg)
df_this.sort_index(inplace = True)
# Drawing charts
plt.figure()
ax = df_this.plot(color = "white", alpha=0)
ax.fill_between(df_this.index.values,0,df_this['Strategy'], where = 0<df_this['Strategy'], color = "#ffbb51",step = "pre")
ax.fill_between(df_this.index.values,0,df_this['Strategy'], where = 0>df_this['Strategy'], color = "#b3bcc0",step = "pre")
fig = ax.get_figure()
plt.xticks(rotation = 0,ha = 'center', fontsize = 8)
plt.yticks(fontsize = 8)
plt.ylabel("")
plt.xlabel("")
ax.xaxis.set_major_formatter(DateFormatter("%b %Y"))
plt.axhline(y = 0, color = '#d5d5d5')
ax.legend_.remove()
plt.setp(ax.spines.values(), color='#d5d5d5')
plt.setp([ax.get_xticklines(), ax.get_yticklines()], color='#d5d5d5')
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
ax.yaxis.grid(True, color = "#ececec")
fig.set_size_inches(width, height)
base64 = self.fig_to_base64(name, fig)
plt.cla()
plt.clf()
return base64
def drawdown(self,name = "drawdowns.png",width = 11.5, height = 2.5):
if not self.is_drawable: return str()
# Prepare the dataset to be used for drawing charts
df_this = self.df.copy()
df_this.drop("Benchmark",1,inplace = True)
df_this["Drawdown"] = 1
lastPeak = self.initStrategyValue
for i in range(len(df_this)):
if df_this.iloc[i,0] < lastPeak:
df_this.iloc[i,1] = df_this.iloc[i,0]/lastPeak
else:
lastPeak = df_this.iloc[i,0]
df_this["DDGroup"] = 0
tmp = 0
for i in range(1,len(df_this)):
if df_this.iloc[i,1] != 1:
df_this.iloc[i,2] = tmp
else:
continue
if df_this.iloc[i-1,1] == 1:
tmp += 1
df_this.iloc[i,2] = tmp
df_this["index"] = [i for i in range(len(df_this))]
tmp_df = pd.DataFrame.from_dict({'MDD':df_this.groupby([df_this["DDGroup"]])['Drawdown'].min(),
'Offset':df_this.groupby([df_this["DDGroup"]])['Drawdown'].apply(lambda x: np.where(x == min(x))[0][0]),
'Start':df_this.groupby([df_this["DDGroup"]])['index'].first(),
'End':df_this.groupby([df_this["DDGroup"]])['index'].last()})
tmp_df.drop(tmp_df.index[[0]],inplace = True)
tmp_df.sort_values("MDD",inplace = True)
df_this = (df_this["Drawdown"] - 1)*100
# Drawing charts
plt.figure()
tmp_colors = ["#FFCCCCCC","#FFE5CCCC","#FFFFCCCC","#E5FFCCCC","#CCFFCCCC"]
tmp_texts = ["1st Worst","2nd Worst","3rd Worst","4th Worst","5th Worst"]
ax = df_this.plot(color = "#b3bcc0",zorder = 2)
ax.fill_between(df_this.index.values,df_this,0, color = "#b3bcc0",zorder = 3)
for i in range(min(len(tmp_df),5)):
tmp_start = df_this.index.values[int(tmp_df.iloc[i]["Start"])]
tmp_end = df_this.index.values[int(tmp_df.iloc[i]["End"])]
tmp_mid = df_this.index.values[int(tmp_df.iloc[i]["Offset"])+int(tmp_df.iloc[i]["Start"])]
plt.axvspan(tmp_start, tmp_end,0,0.95, color = tmp_colors[i],zorder = 1)
plt.axvline(tmp_mid, 0,0.95, ls = "dashed",color ="black", zorder = 4)
plt.text(tmp_mid,min(df_this)*0.75,tmp_texts[i], rotation = 90, zorder = 4)
fig = ax.get_figure()
plt.xticks(rotation = 0,ha = 'center', fontsize = 8)
plt.yticks(fontsize = 8)
plt.ylabel("")
plt.xlabel("")
ax.xaxis.set_major_formatter(DateFormatter("%b %Y"))
plt.axhline(y = 0, color = '#d5d5d5')
plt.setp(ax.spines.values(), color='#d5d5d5')
plt.setp([ax.get_xticklines(), ax.get_yticklines()], color='#d5d5d5')
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
ax.yaxis.grid(True, color = "#ececec")
fig.set_size_inches(width, height)
base64 = self.fig_to_base64(name, fig)
plt.cla()
plt.clf()
plt.close('all')
return base64
def monthly_returns(self, name = "monthly-returns.png",width = 3.5*2, height = 2.5*2):
if not self.is_drawable: return str()
# Prepare the dataset to be used for drawing charts
df_this = self.df.copy()
df_this.drop("Benchmark",1,inplace = True)
df_this1 = df_this.groupby([df_this.index.year,df_this.index.month]).apply(lambda x: x.head(1))
df_this2 = df_this.groupby([df_this.index.year,df_this.index.month]).apply(lambda x: x.tail(1))
df_this1.index = df_this1.index.droplevel(2)
df_this2.index = df_this2.index.droplevel(2)
df_this = pd.concat([df_this1,df_this2],axis = 1)
df_this["Return"] = (df_this.iloc[:,1] / df_this.iloc[:,0] - 1) * 100
df_this = df_this.iloc[:,2]
for i in range(1,df_this.index[0][1]):
df_this.loc[df_this.index[0][0],i] = float("nan")
df_this.sort_index(0,0,inplace = True)
df_this = df_this.unstack()
df_this = df_this.iloc[::-1]
# Define the rules of color change
def make_colormap(seq):
seq = [(None,) * 3, 0.0] + list(seq) + [1.0, (None,) * 3]
cdict = {'red': [], 'green': [], 'blue': []}
for i, item in enumerate(seq):
if isinstance(item, float):
r1, g1, b1 = seq[i - 1]
r2, g2, b2 = seq[i + 1]
cdict['red'].append([item, r1, r2])
cdict['green'].append([item, g1, g2])
cdict['blue'].append([item, b1, b2])
return mcolors.LinearSegmentedColormap('CustomMap', cdict)
c = mcolors.ColorConverter().to_rgb
c_map = make_colormap([c('#CC0000'),0.1,c('#FF0000'),0.2,c('#FF3333'),
0.3,c('#FF9933'),0.4,c('#FFFF66'),0.5,c('#FFFF99'),
0.6,c('#B2FF66'),0.7,c('#99FF33'),0.8,
c('#00FF00'),0.9, c('#00CC00')])
# Drawing charts
plt.figure()
ax = plt.imshow(df_this, aspect='auto',cmap=c_map, interpolation='none',vmin = -10, vmax = 10)
fig = ax.get_figure()
fig.set_size_inches(3.5*2,2.5*2)
plt.xlabel('')
plt.ylabel('')
plt.yticks(range(len(df_this.index.values)),df_this.index.values, fontsize = 8)
plt.xticks(range(12),["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"])
for (j,i),label in np.ndenumerate(df_this):
plt.text(i,j,round(label,1),ha='center',va='center', fontsize = 7)
fig.set_size_inches(width, height)
base64 = self.fig_to_base64(name, fig)
plt.cla()
plt.clf()
plt.close('all')
return base64
def annual_returns(self, name = "annual-returns.png",width = 3.5*2, height = 2.5*2):
if not self.is_drawable: return str()
# Prepare the dataset to be used for drawing charts
df_this = self.df.copy()
df_this.drop("Benchmark",1,inplace = True)
df_this1 = df_this.groupby([df_this.index.year]).apply(lambda x: x.head(1))
df_this2 = df_this.groupby([df_this.index.year]).apply(lambda x: x.tail(1))
df_this1.index = df_this1.index.droplevel(1)
df_this2.index = df_this2.index.droplevel(1)
df_this = pd.concat([df_this1,df_this2],axis = 1)
df_this["Return"] = (df_this.iloc[:,1] / df_this.iloc[:,0] - 1) * 100
df_this = df_this.iloc[:,2]
# Drawing charts
plt.figure()
ax = df_this.plot.barh(color = ["#428BCA"])
fig = ax.get_figure()
plt.xticks(rotation = 0,ha = 'center', fontsize = 8)
plt.yticks(fontsize = 8)
plt.axvline(x = 0, color = '#d5d5d5', linewidth = 0.5)
vline = plt.axvline(x = np.mean(df_this),color = "red", ls = "dashed", label = "mean", linewidth = 0.5)
plt.legend([vline],["mean"],loc='upper right', frameon=False, fontsize = 8)
plt.setp(ax.spines.values(), color='#d5d5d5')
plt.setp([ax.get_xticklines(), ax.get_yticklines()], color='#d5d5d5')
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
plt.xlabel("")
plt.ylabel("")
ax.xaxis.grid(True)
fig.set_size_inches(width, height)
base64 = self.fig_to_base64(name, fig)
plt.cla()
plt.clf()
plt.close('all')
return base64
def monthly_return_distribution(self, name = "distribution-of-monthly-returns.png",width = 3.5*2, height = 2.5*2):
if not self.is_drawable: return str()
# Prepare the dataset to be used for drawing charts
df_this = self.df.copy()
df_this.drop("Benchmark",1,inplace = True)
df_this1 = df_this.groupby([df_this.index.year,df_this.index.month]).apply(lambda x: x.head(1))
df_this2 = df_this.groupby([df_this.index.year,df_this.index.month]).apply(lambda x: x.tail(1))
df_this1.index = df_this1.index.droplevel(2)
df_this2.index = df_this2.index.droplevel(2)
df_this = pd.concat([df_this1,df_this2],axis = 1)
df_this["Return"] = (df_this.iloc[:,1] / df_this.iloc[:,0] - 1) * 100
df_this["Group"] = np.floor(df_this["Return"])
tmp_mean = np.mean(df_this["Return"])
tmp_mean = 11 if tmp_mean > 10 else -11 if tmp_mean < -10 else tmp_mean
df_this = df_this.iloc[:,[2,3]]
df_this["Group"] = [x if x<=10 and x>=-10 else float("-Inf") if x<-10 else float("Inf") for x in df_this["Group"]]
df_this = df_this.groupby([df_this["Group"]]).count()
tmp_min = int(min(max(min(df_this.index.values),-11),0))
tmp_max = int(max(min(max(df_this.index.values), 11),0))
for i in range(max(tmp_min,-10), min(tmp_max,10)+1):
if i not in df_this.index.values:
tmp = df_this.iloc[0].copy()
tmp[0] = 0
tmp.name = np.float64(i)
df_this = df_this.append(tmp,ignore_index = False)
df_this.sort_index(inplace = True)
df_this.index = [">10" if x == float("Inf") else "<-10" if x == float("-Inf") else int(x) for x in df_this.index]
# Drawing charts
plt.figure()
ax = df_this.plot.bar(color = ["#ffbb51"])
fig = ax.get_figure()
plt.xticks(rotation = 0,ha = 'center', fontsize = 8)
plt.yticks(fontsize = 8)
plt.axvline(x = -tmp_min, color = '#d5d5d5', linewidth = 0.5)
vline = plt.axvline(x = tmp_mean-tmp_min,color = "red", ls = "dashed", label = "mean", linewidth = 0.5)
plt.legend([vline],["mean"],loc='upper left', frameon=False, fontsize = 8)
plt.setp(ax.spines.values(), color='#d5d5d5')
plt.setp([ax.get_xticklines(), ax.get_yticklines()], color='#d5d5d5')
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
plt.xlabel("")
plt.ylabel("")
ax.yaxis.grid(True, color = "#ececec")
fig.set_size_inches(width, height)
base64 = self.fig_to_base64(name, fig)
plt.cla()
plt.clf()
plt.close('all')
return base64
def crisis_events(self, width = 3.5*2, height = 2.5*2):
if not self.is_drawable: return dict()
# Prepare the dataset to be used for drawing charts
df_this = self.df.copy()
start_date = ["2000-03-10","2001-09-11","2003-01-08","2008-08-01","2010-05-05",
"2007-08-01","2008-03-01","2008-09-01","2009-01-01","2009-03-01",
"2011-08-05","2012-09-10",
"2014-10-01","2015-08-15",
"2005-01-01","2007-08-01","2009-04-01","2013-01-01"]
end_date = ["2000-09-10","2001-10-11","2003-02-07","2008-09-30","2010-05-10",
"2007-08-31","2008-03-31","2008-09-30","2009-02-28","2009-05-31",
"2011-09-05","2012-10-10",
"2014-10-31","2015-09-30",
"2007-07-31","2009-03-31","2012-12-31",str(date.today())]
titles = ["Dotcom","9-11","US Housing Bubble 2003","Lehman Brothers","Flash Crash",
"Aug07","Mar08","Sept08","2009Q1","2009Q2",
"US Downgrade-European Debt Crisis","ECB IR Event 2012",
"Oct14","Fall2015",
"Low Volatility Bull Market","GFC Crash","Recovery","New Normal"]
crisis = dict()
# Drawing charts
for i in range(len(start_date)):
df_this_tmp = df_this[start_date[i]:end_date[i]].copy()
if not len(df_this_tmp):
continue
df_this_tmp["Strategy"] = (df_this_tmp["Strategy"]/df_this_tmp["Strategy"][0]-1)*100
df_this_tmp["Benchmark"] = (df_this_tmp["Benchmark"]/df_this_tmp["Benchmark"][0]-1)*100
plt.figure()
ax = df_this_tmp.plot(color = ["#ffbb51","#b3bcc0"], linewidth = 0.5)
fig = ax.get_figure()
plt.xticks(ha = 'center')
plt.yticks(fontsize = 8)
plt.xlabel("")
plt.ylabel('Return(%)',size = 12,fontweight='bold')
handles, labels = ax.get_legend_handles_labels()
p = plt.Rectangle((0, 0), 1, 1, fc="#ffbb51")
q = plt.Rectangle((0, 0,), 1, 1, fc = "#b3bcc0")
leg = ax.legend([p, q], [label for i,label in enumerate(labels)], handlelength=0.8, handleheight=0.8, frameon = False, fontsize = 8)
ax.xaxis.set_major_formatter(DateFormatter("%Y-%m-%d"))
for line in leg.get_lines(): line.set_linewidth(3)
plt.axhline(y = 0, color = '#b3bcc0')
plt.setp(ax.spines.values(), color='#d5d5d5')
plt.setp([ax.get_xticklines(), ax.get_yticklines()], color='#d5d5d5')
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
plt.xlabel("")
plt.ylabel("")
ax.yaxis.grid(True, color = "#ececec")
fig.set_size_inches(width, height)
name = f"crisis-{re.sub(r' ','-',titles[i].lower())}.png"
crisis.update({f"Crisis {titles[i]}": self.fig_to_base64(name, fig)})
plt.cla()
plt.clf()
plt.close('all')
return crisis
def rolling_beta(self, name = "rolling-portfolio-beta-to-equity.png",width = 11.5, height = 2.5):
if not self.is_drawable: return str()
# Prepare the dataset to be used for drawing charts
days_L = 252
days_S = 126
if len(set(self.df.index.date)) > days_L:
df_this = self.df.copy()
df_this = df_this.groupby([df_this.index.date]).apply(lambda x: x.tail(1))
df_this.index = df_this.index.droplevel(1)
ret_strategy = np.array([self.initStrategyValue] + df_this["Strategy"].tolist())
ret_strategy = ret_strategy[1:]/ret_strategy[:-1] - 1
df_this["Strategy"] = ret_strategy*100
ret_benchmark = np.array([self.initBenchmarkValue] + df_this["Benchmark"].tolist())
ret_benchmark = ret_benchmark[1:]/ret_benchmark[:-1] - 1
df_this["Benchmark"] = ret_benchmark*100
df_this["Beta6mo"] = float("nan")
df_this["Beta12mo"] = float("nan")
for i in range(days_L, len(df_this)):
cov_matrix = np.cov(df_this["Strategy"][(i-days_L):i],df_this["Benchmark"][(i-days_L):i])
df_this.iloc[[i],[3]] = cov_matrix[0,1]/cov_matrix[1,1]
for i in range(days_S, len(df_this)):
cov_matrix = np.cov(df_this["Strategy"][(i-days_S):i],df_this["Benchmark"][(i-days_S):i])
df_this.iloc[[i],[2]] = cov_matrix[0,1]/cov_matrix[1,1]
df_this.drop(["Benchmark","Strategy"],1,inplace = True)
df_this["Empty"] = 0
# Drawing charts
plt.figure()
ax = df_this.plot(color = ["#CCCCCC","#428BCA"], linewidth = 0.5)
fig = ax.get_figure()
plt.xticks(rotation = 0,ha = 'center', fontsize = 8)
plt.yticks(fontsize = 8)
plt.xlabel("")
plt.ylabel('Beta',size = 12,fontweight='bold')
handles, labels = ax.get_legend_handles_labels()
p = plt.Rectangle((0, 0), 1, 1, fc="#ffbb51")
q = plt.Rectangle((0, 0,), 1, 1, fc = "#b3bcc0")
leg = ax.legend([p, q], [label for i,label in enumerate(labels)], handlelength=0.8, handleheight=0.8, frameon = False, fontsize = 8)
ax.xaxis.set_major_formatter(DateFormatter("%b %Y"))
for line in leg.get_lines(): line.set_linewidth(3)
plt.axhline(y = 0, color = '#d5d5d5')
plt.setp(ax.spines.values(), color='#d5d5d5')
plt.setp([ax.get_xticklines(), ax.get_yticklines()], color='#d5d5d5')
plt.xlabel("")
plt.ylabel("")
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
ax.yaxis.grid(True, color = "#ececec")
fig.set_size_inches(width, height)
base64 = self.fig_to_base64(name, fig)
plt.cla()
plt.clf()
plt.close('all')
return base64
def rolling_sharpe(self, name = "rolling-sharpe-ratio(6-month).png",width = 11.5, height = 2.5):
if not self.is_drawable: return str()
# Prepare the dataset to be used for drawing charts
days_S = 126
days_in_one_year = 252
if len(set(self.df.index.date)) > days_S:
df_this = self.df.copy()
df_this.drop("Benchmark",1,inplace = True)
df_this = df_this.groupby([df_this.index.date]).apply(lambda x: x.tail(1))
df_this.index = df_this.index.droplevel(1)
ret_strategy = np.array([self.initStrategyValue] + df_this["Strategy"].tolist())
ret_strategy = ret_strategy[1:]/ret_strategy[:-1] - 1
df_this["Strategy"] = ret_strategy*100
df_this["SharpeRatio"] = float("nan")
for i in range(days_S, len(df_this)):
tmp_ret = np.mean(df_this["Strategy"][(i-days_S):i]) * days_in_one_year
tmp_std = max(np.std(df_this["Strategy"][(i-days_S):i]) * np.sqrt(days_in_one_year), 0.0001)
df_this.iloc[[i],[1]] = tmp_ret/tmp_std
df_this.drop("Strategy",1,inplace = True)
df_this["mean"] = np.mean(df_this["SharpeRatio"])
# Drawing charts
plt.figure()
ax = df_this["SharpeRatio"].plot(color = "#ffbb51", linewidth = 0.5)
ax = df_this["mean"].plot(color = "red", linestyle = "dashed", linewidth = 0.5)
fig = ax.get_figure()
plt.xticks(rotation = 0,ha = 'center', fontsize = 8)
plt.yticks(fontsize = 8)
plt.xlabel("")
plt.ylabel('Sharpe Ratio',size = 12,fontweight='bold')
handles, labels = ax.get_legend_handles_labels()
p = plt.Rectangle((0, 0), 1, 1, fc="#ffbb51")
q = plt.Rectangle((0, 0,), 1, 1, fc = "#b3bcc0")
leg = ax.legend([p, q], [label for i,label in enumerate(labels)], handlelength=0.8, handleheight=0.8, frameon = False, fontsize = 8)
ax.xaxis.set_major_formatter(DateFormatter("%b %Y"))
for line in leg.get_lines(): line.set_linewidth(3)
plt.axhline(y = 0, color = '#d5d5d5')
plt.setp(ax.spines.values(), color='#d5d5d5')
plt.setp([ax.get_xticklines(), ax.get_yticklines()], color='#d5d5d5')
plt.ylabel("")
plt.xlabel("")
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
ax.yaxis.grid(True, color = "#ececec")
fig.set_size_inches(width, height)
base64 = self.fig_to_base64(name, fig)
plt.cla()
plt.clf()
plt.close('all')
return base64
def net_holdings(self, name = "net-holdings.png",width = 11.5, height = 2.5):
if not self.is_drawable: return str()
# Prepare the dataset to be used for drawing charts
df_this = self.df_cash.copy()
df_this["Strategy"] = df_this["Value"]/df_this["Strategy"]*100
df_this.drop(df_this.columns[[1,2]],1,inplace = True)
df_this = df_this.groupby([df_this.index.date,df_this.index.hour,df_this.index.minute], as_index = False).apply(lambda x: x.tail(1))
df_this.index = df_this.index.droplevel(0)
# Drawing charts
plt.figure()
ax = df_this.plot(color = "white", alpha=0)
ax.fill_between(df_this.index.values,0,df_this['Strategy'], where = 0<df_this['Strategy'], color = "#ffbb51",step = "pre")
ax.fill_between(df_this.index.values,0,df_this['Strategy'], where = 0>df_this['Strategy'], color = "#b3bcc0",step = "pre")
fig = ax.get_figure()
plt.xticks(rotation = 0,ha = 'center', fontsize = 8)
plt.yticks(fontsize = 8)
plt.xlabel("")
plt.ylabel('Net Holdings(%)',size = 12,fontweight='bold')
ax.xaxis.set_major_formatter(DateFormatter("%b %Y"))
plt.axhline(y = 0, color = '#d5d5d5')
ax.legend_.remove()
plt.setp(ax.spines.values(), color='#d5d5d5')
plt.setp([ax.get_xticklines(), ax.get_yticklines()], color='#d5d5d5')
plt.ylabel("")
plt.xlabel("")
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
ax.yaxis.grid(True, color = "#ececec")
fig.set_size_inches(width, height)
base64 = self.fig_to_base64(name, fig)
plt.cla()
plt.clf()
plt.close('all')
return base64
def leverage(self, name = "leverage.png",width = 11.5, height = 2.5):
if not self.is_drawable: return str()
# Prepare the dataset to be used for drawing charts
df_this = self.df_cash.copy()
df_this["Strategy"] = abs(df_this["Value"]/df_this["Strategy"]*100)
df_this.drop(df_this.columns[[1,2]],1,inplace = True)
df_this = df_this.groupby([df_this.index.date,df_this.index.hour,df_this.index.minute], as_index = False).apply(lambda x: x.tail(1))
df_this.index = df_this.index.droplevel(0)
# Drawing charts
plt.figure()
ax = df_this.plot(color = "#ffbb51")
ax.fill_between(df_this.index.values,0,df_this['Strategy'], color = "#ffbb51",step = "pre")
fig = ax.get_figure()
plt.xticks(rotation = 0,ha = 'center', fontsize = 8)
plt.yticks(fontsize = 8)
plt.xlabel("")
plt.ylabel('Leverage(%)',size = 12,fontweight='bold')
ax.xaxis.set_major_formatter(DateFormatter("%b %Y"))
plt.axhline(y = 0, color = '#d5d5d5')
ax.legend_.remove()
plt.setp(ax.spines.values(), color='#d5d5d5')
plt.setp([ax.get_xticklines(), ax.get_yticklines()], color='#d5d5d5')
plt.ylabel("")
plt.xlabel("")
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
ax.yaxis.grid(True, color = "#ececec")
fig.set_size_inches(width, height)
base64 = self.fig_to_base64(name, fig)
plt.cla()
plt.clf()
plt.close('all')
return base64
def asset_allocation(self,width = 3.5*2, height = 2.5*2):
if not self.is_drawable: return dict()
df_this = self.df.copy()
df_this.drop("Benchmark",1,inplace = True)
df_values = pd.DataFrame()
df_values["Value"] = [x["Value"] for x in self.orders.values()]
df_values["Symbol"] = [x["Symbol"]["Value"] for x in self.orders.values()]
df_values["Type"] = [x["SecurityType"] for x in self.orders.values()]
df_values = df_values.set_index([[datetime.strptime(x["Time"][0:19], '%Y-%m-%dT%H:%M:%S') for x in self.orders.values()]])
timeBegin = df_this.index[0]
timeEnd = df_this.index[-1]
timeDuration = (timeEnd - timeBegin).total_seconds()
df_cash_tmp = df_values.copy()
df_cash_tmp["Value"] = -df_cash_tmp["Value"]
df_cash_tmp["Symbol"] = "CASH"
df_cash_tmp["Type"] = 0
if timeBegin in df_cash_tmp.index:
df_cash_tmp.loc[timeBegin-timedelta(seconds = 1)] = [df_this["Strategy"][0], "CASH", 0]
timeBegin = timeBegin-timedelta(seconds = 1)
else:
df_cash_tmp.loc[timeBegin] = [df_this["Strategy"][0], "CASH", 0]
df_values = df_values.append(df_cash_tmp)
df_values.sort_index(inplace = True)
self.df_values = df_values
SecurityTypeName = ['Cash','Equity', 'Option', 'Commodity', 'Forex', 'Future', 'Cfd', 'Crypto']
asset_alloc = []
for SecurityType in range(0,7+1):
df_tmp = df_values.where(df_values["Type"] == SecurityType).iloc[:,0].copy()
df_tmp = df_tmp.groupby(df_tmp.index).sum().cumsum()
list_timestamp = list(df_tmp.index)
list_timestamp.append(timeEnd)
timeWeightedValue = sum([(list_timestamp[i+1] - list_timestamp[i]).total_seconds()/timeDuration*df_tmp[i] for i in range(len(df_tmp))])
asset_alloc.append(timeWeightedValue)
df_pie = pd.DataFrame()
df_pie["Value"] = asset_alloc
# df_pie["Weight"] = [round(x/sum(df_pie["Value"])*100,1) for x in df_pie["Value"]]
df_pie["AbsWeight"] = [round(abs(x)/sum(abs(df_pie["Value"]))*100,1) for x in df_pie["Value"]]
df_pie["Labels"] = SecurityTypeName
df_pie = df_pie.where(df_pie["Value"] != 0).dropna(axis = 0, how = "any")
if len([x for x in df_pie["AbsWeight"] if x < 5]) > 1:
df_pie["Labels"] = [ df_pie["Labels"].iloc[i] if df_pie["AbsWeight"].iloc[i] >= 5 else "Others" for i in range(len(df_pie)) ]
df_pie = df_pie.groupby(by = "Labels").sum()
df_pie.reset_index(inplace = True)
df_pie.sort_values(by = ['AbsWeight','Value'],ascending = False, inplace = True)
df_pie["Labels"] = [str(round(df_pie["AbsWeight"].iloc[i],1)) + "%\n" + df_pie["Labels"].iloc[i]
if df_pie["Value"].iloc[i] >= 0 else "(" + str(round(df_pie["AbsWeight"].iloc[i],1)) + "%)\n" + df_pie["Labels"].iloc[i]
for i in range(len(df_pie))]
df_pie["Value"] = abs(df_pie["Value"])
colors = ['#f8c16f', '#ff9d00', '#FFB266', '#FF9933', '#FF8000', '#CC6600','#994C00','#990000']
pies = dict()
fig = plt.figure()
patches, texts, autotexts = plt.pie(df_pie["Value"], labels=df_pie["Labels"], colors=colors, autopct="", startangle=90, labeldistance = 0.5, textprops = {'color':'w'})
for x in texts:
x.set_fontsize(12)
x.set_fontweight("bold")
for x in autotexts:
x.set_fontsize(12)
x.set_fontweight("bold")
plt.axis('equal')
fig.set_size_inches(width, height)
pies.update({"Asset Allocation": self.fig_to_base64("asset-allocation-all.png", fig)})
plt.cla()
plt.clf()
plt.close('all')
for SecurityType in range(1,7+1):
df_tmp = df_values.where(df_values["Type"] == SecurityType).copy()
asset_symbols = list(set(df_tmp["Symbol"].dropna(axis = 0)))
if asset_symbols:
asset_alloc = []
for sym in asset_symbols:
df_tmp2 = df_tmp.where(df_tmp["Symbol"]==sym).iloc[:,0].copy()
df_tmp2 = df_tmp2.groupby(df_tmp2.index).sum().cumsum()
list_timestamp = list(df_tmp2.index)
list_timestamp.append(timeEnd)
timeWeightedValue = sum([(list_timestamp[i+1] - list_timestamp[i]).total_seconds()/timeDuration*df_tmp2[i] for i in range(len(df_tmp2))])
asset_alloc.append(timeWeightedValue)
asset_symbols = [asset_symbols[i] if i < 7 else "Others" for i in range(len(asset_symbols))]
if len(asset_alloc) > 7:
asset_alloc = list(asset_alloc[0:7] + [sum(asset_alloc[7:])])
asset_symbols = asset_symbols[0:8]
if not sum([abs(x) for x in asset_alloc]):
continue
df_pie = pd.DataFrame()
df_pie["Value"] = asset_alloc
# df_pie["Weight"] = [round(x/sum(df_pie["Value"])*100,1) for x in df_pie["Value"]]
df_pie["AbsWeight"] = [round(abs(x)/sum(abs(df_pie["Value"]))*100,1) for x in df_pie["Value"]]
df_pie["Labels"] = asset_symbols
if len([x for x in df_pie["AbsWeight"] if x < 5]) > 1:
df_pie["Labels"] = [ df_pie["Labels"].iloc[i] if df_pie["AbsWeight"].iloc[i] >= 5 else "Others" for i in range(len(df_pie)) ]
df_pie = df_pie.groupby(by = "Labels").sum()
df_pie.reset_index(inplace = True)
df_pie.sort_values(by = ['AbsWeight','Value'],ascending = False, inplace = True)
df_pie["Labels"] = [str(round(df_pie["AbsWeight"].iloc[i],1)) + "%\n" + df_pie["Labels"].iloc[i]
if df_pie["Value"].iloc[i] >= 0 else "(" + str(round(df_pie["AbsWeight"].iloc[i],1)) + "%)\n" + df_pie["Labels"].iloc[i]
for i in range(len(df_pie))]
df_pie = df_pie.where(df_pie["Value"] != 0).dropna(axis = 0, how = "any")
df_pie["Value"] = abs(df_pie["Value"])
colors = ['#FFE5CC', '#FFCC99', '#FFB266', '#FF9933', '#FF8000', '#CC6600','#994C00','#990000']
fig = plt.figure()
patches, texts, autotexts = plt.pie(df_pie["Value"], labels=df_pie["Labels"], colors=colors, autopct="", startangle=90, labeldistance = 0.6)
for x in texts:
x.set_fontsize(12)
x.set_fontweight("bold")
for x in autotexts:
x.set_fontsize(12)
x.set_fontweight("bold")
plt.axis('equal')
fig.set_size_inches(width, height)
pies.update({SecurityTypeName[SecurityType]: self.fig_to_base64(f"asset-allocation-{SecurityTypeName[SecurityType].lower()}.png", fig)})
plt.cla()
plt.clf()
plt.close('all')
return pies
def statistics(self):
output = {"Key Characteristics": {"Significant Period": 0,
"Significant Trading": 0,
"Diversified": 0,
"Risk Control": 0,
"Markets": []},
"Key Statistics": {"CAGR": 0,
"Drawdown": 0,
"Sharpe Ratio": 0,
"Information Ratio": 0,
"Trades Per Day": 0}}
if self.is_drawable and "TotalPerformance" in self.data:
SecurityTypeName = ['Equity', 'Option', 'Commodity', 'Forex', 'Future', 'Cfd', 'Crypto']
output["Key Characteristics"] = {
"Significant Period": (self.df.index[-1] - self.df.index[0]).days/365 > 5,
"Significant Trading": len(self.orders) >= 100,
"Diversified": len(set(self.df_values["Symbol"])) > 7,
"Risk Control": self.data["TotalPerformance"]["PortfolioStatistics"]["Drawdown"] < 0.1,
"Markets": [SecurityTypeName[x-1] for x in list(set(self.df_values["Type"])) if x > 0]
}
stats = self.data.pop("TotalPerformance").pop("PortfolioStatistics")
output["Key Statistics"] = {
"CAGR": str(round(100 * stats.pop('CompoundingAnnualReturn', 0), 2)) + '%',
"Drawdown": str(round(100 * stats.pop('Drawdown', 0), 2)) + '%',
"Sharpe Ratio": round(stats.pop('SharpeRatio', 0), 3),
"Information Ratio": round(stats.pop('InformationRatio', 0), 3),
"Trades Per Day": round(len(self.orders) / max((self.df.index[-1] - self.df.index[0]).days, 1), 6)
}
return output