def send(self, data: pd.DataFrame, attachments=[]): emaillist = [elem.strip().split(',') for elem in self.receivers] msg = MIMEMultipart() msg['Subject'] = "Test Report" msg['From'] = self.sender ### Email format ### html = """\ <html> <head></head> <body> {0} </body> </html> """.format(data.to_html()) part1 = MIMEText(html, 'html') msg.attach(part1) ### Attachment ### if attachments: for path in attachments: filename = os.path.split(path)[-1] with open(path, "rb") as attachment: # Add file as application/octet-stream # Email client can usually download this automatically as attachment part = MIMEBase("application", "octet-stream") part.set_payload(attachment.read()) # Encode file in ASCII characters to send by email encoders.encode_base64(part) # Add header as key/value pair to attachment part part.add_header( "Content-Disposition", f"attachment; filename={filename}", ) # Add attachment to message and convert message to string msg.attach(part) # Create a secure SSL context context = ssl.create_default_context() setting = Setting.get() # Try to log in to server and send email try: server = smtplib.SMTP(self.smtp_server, self.tls_port) server.ehlo() # Can be omitted server.starttls(context=context) # Secure the connection server.ehlo() # Can be omitted server.login(self.sender, setting.sender_email_password) # Send email here server.sendmail(msg['From'], emaillist, msg.as_string()) logger.info(f"Successfully send email to {emaillist}.") except Exception as e: # Print any error messages to stdout logger.error(str(e)) finally: server.quit()
def evaluate(assetId: str, strategyId: str, df: pd.DataFrame, evaluationPeriod: EvaluationPeriod = None, deleteTemp=False) -> bool: """ Evaluate the performance of an arbitrary strategy. Columns required in df for evaluation process as below :param assetId: strategyId: ID of the strategy with the specific set of parameters that generate 'position' for data df: assume the existence of these columns after running through a Strategy position: determine the position of the asset at each point in time :return: the enriched df with evaluation parameters if pass evaluation criteria, else return None """ name = '_'.join([assetId, strategyId, evaluationPeriod.name]) ### evaluation parameters df['asset_ret'] = df['Adj Close'].pct_change() df['return'] = df['asset_ret'] * df['position'] df['unit_asset_ret'] = (1 + df['asset_ret']).cumprod() df['value'] = (1 + df['return']).cumprod() ### evaluate if not deleteTemp: tempDir = None tempPath = get_temp_path() if not os.path.exists(tempPath): os.makedirs(tempPath) else: tempDir = tempfile.TemporaryDirectory() tempPath = tempDir.name graph_file_extension = Setting.get().graph_file_extension tempPath = os.path.join(tempPath, name) fig = plt.figure(figsize=(15, 10)) s1 = fig.add_subplot(2, 1, 1) s1.plot(df['value'], label='Portfolio unit value') s1.plot(df['unit_asset_ret'], label='Asset unit value') s1.legend() s1.set_title("Return") s2 = fig.add_subplot(2, 1, 2) alpha = df['value'] - df['unit_asset_ret'] s2.plot(alpha, label='Excess return compared to asset', color='r') s2.legend() s2.set_title("Alpha") fig.savefig(tempPath + f'.{graph_file_extension}', format=graph_file_extension) plt.close(fig) logger.info(f"Graph for {name} generated at {tempPath}.") if tempDir: tempDir.cleanup() ### TODO: define the criteria for a pass if not True: return None return df
:param n_jobs: the number of parallelized workers :return: list of results from workers, should have length == len(partitions) Note: can be implemented to submit jobs to distributed computing engine like a Spark cluster. For now, just implemented to utilize multicore in the host machine ''' # p = Pool(n_jobs) # results = p.map(func, partitions) # TODO: apply parallelized version, results should maintain order as input partitions results = [func(*params) for params in partitions] return results if __name__ == '__main__': # TODO: create Setting object and Context object settings = Setting.get() config = json.loads(open(settings.account_config_path).read()) # TODO: error handling watchlist = list( filter(lambda wl: wl['watchlist_id'] == 'default', config['watchlists']))[0] asset_ids = watchlist['assets'] ''' pass asset ids to Analyzer ''' # 1. single-asset analyzer # 2. multi-asset analyzer ''' Analyzer askes Strategy for DataConfig (duration, frequency, parameters, etc.) to specify what to fetch from MarketData/Portfolio ''' ''' Analyzer gets data from MarketData for these assets ''' # what information you want ? # 1. trading info (price, volume, etc.) # 2. company info (BS, news, etc.) # assuming we get these config from Strategy
import typing import pandas as pd import os from reporting.reporter import EmailReporter from settings import Setting from utils import get_temp_path graph_file_extension = Setting.get().graph_file_extension def getGraphs(columns: pd.MultiIndex): index = columns.reorder_levels(['StrategyId', 'AssetId']) graphNames = map('_'.join, index.values.tolist()) basePath = get_temp_path() return [ os.path.join(basePath, f"{filename}.{graph_file_extension}") for filename in graphNames ] def generateReport(data: pd.DataFrame): """ :param data: mapping of asset_id to its signal data frame :return: """ signals = data.xs('signal', axis=1, level='Attributes') ### email the following detail emailReporter = EmailReporter(['*****@*****.**']) attachments = getGraphs(signals.columns) emailReporter.send(signals.tail(10).sort_index(ascending=False), attachments=attachments)
def __init__(self, receivers: typing.List[str]): super().__init__() self.sender = Setting.get().sender_email self.receivers = receivers