# 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. import os import json from quantconnect.LeanOutputReader import LeanOutputReader class LeanReportCreator(object): def __init__(self, argv, save_images = True): input, self.output, user_data = self.read_input(argv) self.user = self.read_user_data(user_data) self.hash = self.user.pop('backtestHash', '') self.count = 0 # Read input file and pass it to the LeanOutputReader data = dict() with open(input, 'r') as fp: data = json.load(fp) outdir = os.path.dirname(self.output) if save_images else None self.reader = LeanOutputReader(data, 200, outdir) def read_input(self, args): if type(args) is str: args = args.split(' ') tmp = next((x for x in args if x.strip().startswith('--backtest')), None) if tmp is None: raise KeyError('Please provide --backtest=file.json argument') input = os.path.abspath(tmp[11:]) if not os.path.isfile(input): raise FileNotFoundError(f'Backtest file not found: {input}') tmp = next((x for x in args if x.strip().startswith('--user')), None) if tmp is None: tmp = '--user=user_data.json' user = tmp[7:] tmp = next((x for x in args if x.strip().startswith('--output')), None) if tmp is None: tmp = f'--output={input[:-5]}.html' output = os.path.abspath(tmp[9:]) # create output directory os.makedirs(os.path.dirname(output), exist_ok = True) return input, output, user def read_user_data(self, file): if os.path.isfile(file): with open(file, 'r', encoding = "utf-8") as fp: return json.load(fp) return { "authorName": "QuantConnect User", "authorPicture": "AuthorProfile.png", "authorBiography": "Put your biography here.", "projectName": "Basic Template Algorithm", "projectDescription": "Basic Template Algorithm", } def create(self): assets = self.reader.asset_allocation() for title, image in assets.items(): assets[title] = self.get_image_box(title, image) crisis = self.reader.crisis_events() for title, image in crisis.items(): crisis[title] = self.get_image_box(title, image) chartAssetAllocation = assets.pop("Asset Allocation", str()) chartAnnualReturns = self.reader.annual_returns() chartCumulativeReturns = self.reader.cumulative_return() chartMonthlyReturns = self.get_image_box('Monthly Returns', self.reader.monthly_returns()) chartReturnsHistogram = self.get_image_box('Return Histogram', self.reader.monthly_return_distribution()) chartDrawdown = self.get_image_box('Drawdown', self.reader.drawdown(), 12) chartDailyReturns = self.get_image_box('Daily Returns', self.reader.daily_returns(), 12) chartRollingBeta = self.get_image_box('Rolling Portfolio Beta to Equity', self.reader.rolling_beta(), 12) chartRollingSP = self.get_image_box('Rolling Sharpe Ratio (6 Months)', self.reader.rolling_sharpe(), 12) chartNetHoldings = self.get_image_box('Net Holdings', self.reader.net_holdings(), 12) chartLeverage = self.get_image_box('Leverage', self.reader.leverage(), 12) tmp = self.reader.statistics() keyStatistics = self.get_table('Key Statistics', tmp.get("Key Statistics")) keyCharacteristics = self.get_table('Key Characteristics', tmp.get("Key Characteristics")) locationPrefix = "https://www.quantconnect.com/terminal" downloadButton = '' if len(self.hash) == 0 else f'''
''' html = ''' ''' + downloadButton + '''
Strategy Report Summary: ''' + self.user['projectName'] + '''

Strategy Report

|Strategy Description

''' + self.user['projectDescription'] + '''

''' + keyCharacteristics + ''' ''' + keyStatistics + ''' ''' + chartMonthlyReturns + '''
''' + self.get_image_box('Cumulative Returns', chartCumulativeReturns, 12) + '''
''' + self.get_image_box('Annual Returns', chartAnnualReturns) + ''' ''' + chartReturnsHistogram + ''' ''' + chartAssetAllocation + '''
''' + chartDrawdown + '''
Strategy Report Summary: ''' + self.user['projectName'] + '''
''' + chartDailyReturns + '''
''' + chartRollingBeta + '''
''' + chartRollingSP + '''
''' + chartNetHoldings + '''
''' + chartLeverage + '''
''' + self.get_pages_from_two_dict(crisis, assets) + ''' ''' with open(self.output, 'w', encoding = "utf-8") as fp: fp.write(html) return html def clean(self): outdir = os.path.dirname(self.output) items = os.listdir(outdir) for item in items: if item.endswith(".png"): os.remove(os.path.join(outdir, item)) def get_table(self, title, dict): ret = f'''
''' for title, value in dict.items(): if isinstance(value, list): value = ", ".join(value) if isinstance(value, bool): value = '''''' if value else '''''' if title == 'Markets': ret += f'''''' else: ret += f'''''' return ret + '''
{title}
{title}{value}
{title}{value}
''' def get_image_box(self, title, url, col = 4): return "" if not url else '''
''' + title + '''
''' + (( '''''' ) if url else "") + '''
''' def get_image_from_dict(self, dict, len_dict1, num_empty_block): ret = '''
''' titles = list(dict.keys()) stop = min(15, len(titles) + num_empty_block) index = 0; for i in range(0, stop, 3): ret += '''
''' for j in range(0, 3): if i + j >= stop: continue if i + j >= len_dict1 and i + j < len_dict1 + num_empty_block: ret += '''
''' else: ret += dict.pop(titles[index]) index += 1 ret += '''
''' return ret + '''
''' def get_pages_from_two_dict(self, dict1, dict2): num_empty_block = 0 if (len(dict1) % 15) % 3 != 0: num_empty_block = 3 - (len(dict1) % 15) % 3 len_og_dict1 = len(dict1) dict1.update(dict2) ret = '''''' while(len(dict1) > 0): ret += '''
Strategy Report Summary: ''' + self.user['projectName'] + '''
''' + self.get_image_from_dict(dict1, len_og_dict1, num_empty_block) + '''
''' return ret