def _get_results_list(self) -> List[Tuple[str]]: """ Returns ------- List[Tuple[str]] the list of tuples. Each tuple corresponds to one property of the timeseries and it is of the following format: (short_name, long_name, value, unit) short_name: is a short string representation that might be treated as a key and should not have spaces in it long_name: it a nice name of the field value: is the string representation of the value rounded to 2 decimal places unit: is an unit in which the value is expressed. Might be empty. All elements of the tuple are strings (including the value, which is a string representation of the rounded number) """ def num_to_str(value): # represents a default return "{:0.2f}".format(value) result_list = list() result_list.append(('start', 'Start Date', date_to_str(self.start_date), '')) result_list.append(('end', 'End Date', date_to_str(self.end_date), '')) result_list.append(('total_ret', 'Total Return', num_to_str(self.total_return * 100), '%')) result_list.append(('cagr', 'Annualised Return', num_to_str(self.cagr * 100), '%')) result_list.append(('vol', 'Annualised Volatility', num_to_str(self.annualised_vol * 100), '%')) result_list.append(('up_vol', 'Annualised Upside Vol.', num_to_str(self.annualised_upside_vol * 100), '%')) result_list.append(('down_vol', 'Annualised Downside Vol.', num_to_str(self.annualised_downside_vol * 100), '%')) result_list.append(('sharpe', 'Sharpe Ratio', num_to_str(self.sharpe_ratio), '')) result_list.append(('omega', 'Omega Ratio', num_to_str(self.omega_ratio), '')) result_list.append(('calmar', 'Calmar Ratio', num_to_str(self.calmar_ratio), '')) result_list.append(('gain/pain', 'Gain to Pain Ratio', num_to_str(self.gain_to_pain_ratio), '')) result_list.append(('sorino', 'Sorino Ratio', num_to_str(self.sorino_ratio), '')) result_list.append(('cvar', '5% CVaR', num_to_str(self.cvar * 100), '%')) result_list.append(('cvar_an', 'Annualised 5% CVaR', num_to_str(self.annualised_cvar * 100), '%')) result_list.append(('max_dd', 'Max Drawdown', num_to_str(self.max_drawdown * 100), '%')) result_list.append(('avg_dd', 'Avg Drawdown', num_to_str(self.avg_drawdown * 100), '%')) result_list.append(('avg_dd_dur', 'Avg Drawdown Duration', num_to_str(self.avg_drawdown_duration), 'days')) result_list.append(('best_ret', 'Best Return', num_to_str(self.best_return * 100), '%')) result_list.append(('worst_ret', 'Worst Return', num_to_str(self.worst_return * 100), '%')) result_list.append(('avg_pos_ret', 'Avg Positive Return', num_to_str(self.avg_positive_return * 100), '%')) result_list.append(('avg_neg_ret', 'Avg Negative Return', num_to_str(self.avg_negative_return * 100), '%')) result_list.append(('skewness', 'Skewness', num_to_str(self.skewness), '')) # result_list.append(('kurtosis', 'Kurtosis', num_to_str(self.kurtosis), '')) # result_list.append(('kelly', 'Kelly Value', num_to_str(self.kelly), '')) freq_str = str(self.frequency) result_list.append(('#observ', 'No. of {} samples'.format(freq_str), len(self.returns_tms), '')) return result_list
def create_request_history(self, request_id: str, universe_url: str, fieldlist_url: str, start_date: datetime, end_date: datetime, frequency: Frequency): """ Method to create hapi history request Parameters ---------- request_id: str ID of request universe_url: str URL address of the universe fieldlist_url: str URL address of the fields start_date: datetime Start of the history data end_date: datetime End of the history data frequency: Frequency Frequency of data """ request_payload = { '@type': 'HistoryRequest', 'identifier': request_id, 'title': 'Request History Payload', 'description': 'Request History Payload used in creating fields component', 'universe': universe_url, 'fieldList': fieldlist_url, 'trigger': self.trigger_url, 'formatting': { '@type': 'HistoryFormat', 'dateFormat': 'yyyymmdd', 'fileType': 'unixFileType', 'displayPricingSource': True, }, 'runtimeOptions': { '@type': 'HistoryRuntimeOptions', 'historyPriceCurrency': 'USD', 'period': str(frequency).lower(), 'dateRange': { '@type': 'IntervalDateRange', 'startDate': date_to_str(start_date), 'endDate': date_to_str(end_date) } }, 'pricingSourceOptions': { '@type': 'HistoryPricingSourceOptions', 'exclusive': True } } self.logger.info('Request history component payload:\n%s', pprint.pformat(request_payload)) self._create_request_common(request_id, request_payload)
def _add_backtest_description(self): """ Adds verbal description of the backtest to the document. The description will be placed on a single page. """ param_names = self._get_param_names() self.document.add_element( HeadingElement( 1, "Model: {}".format(self.backtest_summary.backtest_name))) self.document.add_element(ParagraphElement("\n")) self.document.add_element( HeadingElement(2, "Tickers tested in this study: ")) ticker_str = "\n".join([ ticker.name if isinstance(ticker, FutureTicker) else ticker.as_string() for ticker in self.backtest_summary.tickers ]) self.document.add_element(ParagraphElement(ticker_str)) self.document.add_element(ParagraphElement("\n")) self.document.add_element(HeadingElement(2, "Dates of the backtest")) self.document.add_element( ParagraphElement("Backtest start date: {}".format( date_to_str(self.backtest_summary.start_date)))) self.document.add_element( ParagraphElement("Backtest end date: {}".format( date_to_str(self.backtest_summary.end_date)))) self.document.add_element(ParagraphElement("\n")) self.document.add_element(HeadingElement(2, "Parameters Tested")) for param_index, param_list in enumerate( self.backtest_summary.parameters_tested): param_list_str = ", ".join(map(str, param_list)) self.document.add_element( ParagraphElement("{} = [{}]".format(param_names[param_index], param_list_str))) self.document.add_element(NewPageElement()) self.document.add_element( HeadingElement(2, "Alpha model implementation")) self.document.add_element(ParagraphElement("\n")) model_type = self.backtest_summary.alpha_model_type with open(inspect.getfile(model_type)) as f: class_implementation = f.read() # Remove the imports section class_implementation = "<pre>class {}".format(model_type.__name__) + \ class_implementation.split("class {}".format(model_type.__name__))[1] + "</pre>" self.document.add_element(CustomElement(class_implementation))
def __str__(self): string_template = "{class_name:s} ({start_date:s} - {end_date:s}) -> " \ "Asset: {asset:>20}, " \ "Direction: {direction:>8}, " \ "P&L %: {percentage_pnl:>10.2%}, " \ "P&L: {pnl:>10.2f}".format(class_name=self.__class__.__name__, start_date=date_to_str(self.start_time), end_date=date_to_str(self.end_time), direction=self.direction, asset=self.contract.symbol, percentage_pnl=self.percentage_pnl, pnl=self.pnl, ) return string_template
def _publish_by_email(self, attachments_dirs: List[str], timestamp): class EmailUser(object): def __init__(self, name, email_address): self.name = name self.email_address = email_address date_str = date_to_str(timestamp.date()) template_path = 'live_trading_report.html' users = { EmailUser("Marcin", "*****@*****.**"), # EmailUser("Olga", "*****@*****.**"), } email_publisher = self.container.resolve(EmailPublisher) for user in users: email_publisher.publish(mail_to=user.email_address, subject="Live Trading Summary: " + date_str, template_path=template_path, attachments=attachments_dirs, context={ 'user': user, 'date': date_str })
def _check_if_no_missing_data(self, start_date, end_date, real_contracts_prices_df): dates = real_contracts_prices_df.index first_available_date = dates[0] last_available_date = dates[-1] if start_date < first_available_date or end_date > last_available_date: real_contract_ticker = real_contracts_prices_df.name # type: Ticker warning_msg = "Missing data for a period [{start_date:s},{end_date:s}]. Data available only " \ "for a period [{first_available_date:s},{last_available_date:s}] " \ "Contract name: {contract_name:s}" \ .format(start_date=date_to_str(start_date), end_date=date_to_str(end_date), first_available_date=date_to_str(first_available_date), last_available_date=date_to_str(last_available_date), contract_name=real_contract_ticker.as_string()) self.logger.warning(warning_msg)
def _insert_table_with_overall_measures(self, prices_df: PricesDataFrame, ticker: Ticker): table = Table(column_names=["Measure", "Value"], css_class="table stats-table") table.add_row(["Instrument", ticker.as_string()]) series = prices_df[PriceField.Close] table.add_row(["Start date", date_to_str(series.index[0])]) table.add_row(["End date", date_to_str(series.index[-1])]) trend_strength_overall = trend_strength(prices_df, self.use_next_open_instead_of_close) table.add_row(["Overall strength of the day trends", trend_strength_overall]) trend_strength_1y = trend_strength(prices_df.tail(252), self.use_next_open_instead_of_close) table.add_row(["Strength of the day trends in last 1Y", trend_strength_1y]) self.ticker_to_trend_dict[ticker] = (trend_strength_1y, trend_strength_overall) table.add_row(["Up trends strength", up_trend_strength(prices_df, self.use_next_open_instead_of_close)]) table.add_row(["Down trends strength", down_trend_strength(prices_df, self.use_next_open_instead_of_close)]) self.document.add_element(table)
def __str__(self): string = "{}\n" \ "\tlive trading date: {}" \ "\tinitial risk: {}" \ "\tmodel type to tickers dict: {}" \ "\tin sample returns statistics: {}" \ "\ttitle: {}".format(self.__class__.__name__, date_to_str(self.live_start_date), self.initial_risk, self.model_type_tickers_dict, self.is_returns_stats, self.title) return string
def _get_history_from_timeseries(self, tickers: Sequence[QuandlTicker], fields: Sequence[str], start_date: datetime, end_date: datetime): """ NOTE: Only use one Quandl Database at the time. Do not mix multiple databases. """ tickers = list(tickers) # allows iterating the sequence more then once tickers_str = [t.as_string() for t in tickers] kwargs = {} if start_date is not None: kwargs['start_date'] = date_to_str(start_date) if end_date is not None: kwargs['end_date'] = date_to_str(end_date) data = quandl.get(tickers_str, **kwargs) # type: pd.DataFrame def extract_ticker_name(column_name): ticker_str, _ = column_name.split(' - ') ticker = QuandlTicker.from_string(ticker_str) return ticker ticker_grouping = data.groupby(extract_ticker_name, axis=1) ticker_to_df = { } # type: Dict[str, pd.DataFrame] # string -> DataFrame[dates, fields] for ticker, ticker_data_df in ticker_grouping: tickers_and_fields = (column_name.split(' - ') for column_name in ticker_data_df.columns) field_names = [field for (ticker, field) in tickers_and_fields] ticker_data_df.columns = field_names if fields is not None: # select only required fields ticker_data_df = self._select_only_required_fields( ticker, ticker_data_df, fields) # if there was no data for the given ticker, skip the ticker if ticker_data_df is None: continue ticker_to_df[ticker] = ticker_data_df return ticker_to_df
def __str__(self): string_template = "{datetime_str:s} - {class_name:<25} -> Contract: {contract_str:>30}," \ "Quantity: {quantity:>7}, Price: {price:>7.2f}, " \ "Commission: {commission:>7.2f}".format(datetime_str=date_to_str(self.time), class_name=self.__class__.__name__, contract_str=str(self.contract), quantity=self.quantity, price=self.price, commission=self.commission) return string_template
def table_for_df(df: QFDataFrame, frequency: Frequency = Frequency.DAILY) -> str: """ df DataFrame of returns or prices of assets to be analysed frequency (optional) frequency of the returns or price sampling in the DataFrame. By default daily frequency is used Returns ---------- returns a table similar to the one below: Analysed period: start_date - end_date, using frequency data Name total_ret cagr vol up_vol down_vol ... Asset1 63.93 28.27 19.15 14.06 14.35 ... Asset2 66.26 29.19 20.74 14.86 15.54 ... Asset3 66.26 29.19 20.74 14.86 15.54 ... ... ... ... ... ... ... ... """ name_ta_list = [(name, TimeseriesAnalysis(asset_tms, frequency)) for name, asset_tms in df.iteritems()] first_ta = name_ta_list[0][1] result = "Analysed period: {} - {}, using {} data\n".format( date_to_str(first_ta.start_date), date_to_str(first_ta.end_date), frequency) header_without_dates = "" for value in first_ta.get_short_names()[2:]: header_without_dates += '{:>12}'.format(value) result += ("{:12} {}\n".format("Name", header_without_dates)) for name, ta in name_ta_list: values = "" for value in ta.get_measures()[2:]: values += '{:>12}'.format(value) result += ("{:12} {}\n".format(name.as_string(), values)) return result
def __str__(self): string_template = "{class_name:s} ({datetime_str:s}) -> " \ "Quantity: {quantity:>8}, " \ "Price: {price:>10.2f}, " \ "Commission: {commission:>7.2f}, " \ "Contract: {contract_str:}".format(class_name=self.__class__.__name__, datetime_str=date_to_str(self.time), quantity=self.quantity, price=self.price, commission=self.commission, contract_str=str(self.contract) ) return string_template
def add_backtest_description(document: Document, backtest_result: BacktestSummary, param_names: List[str]): """ Adds verbal description of the backtest to the document. The description will be placed on a single page. """ document.add_element(ParagraphElement("\n")) document.add_element( HeadingElement(1, "Model: {}".format(backtest_result.backtest_name))) document.add_element(ParagraphElement("\n")) document.add_element(HeadingElement(2, "Tickers tested in this study: ")) ticker_str = "\n".join( [ticker.as_string() for ticker in backtest_result.tickers]) document.add_element(ParagraphElement(ticker_str)) document.add_element(ParagraphElement("\n")) document.add_element(HeadingElement(2, "Dates of the backtest")) document.add_element( ParagraphElement("Backtest start date: {}".format( date_to_str(backtest_result.start_date)))) document.add_element( ParagraphElement("Backtest end date: {}".format( date_to_str(backtest_result.end_date)))) document.add_element(ParagraphElement("\n")) document.add_element(HeadingElement(2, "Parameters Tested")) for param_index, param_list in enumerate( backtest_result.parameters_tested): param_list_str = ", ".join(map(str, param_list)) document.add_element( ParagraphElement("{} = [{}]".format(param_names[param_index], param_list_str))) document.add_element(NewPageElement())
def different_allocations_tms(cls, assets_rets_df: SimpleReturnsDataFrame, allocations_df: QFDataFrame) \ -> SimpleReturnsSeries: """ Calculates the time series of portfolio returns given the allocations on each date. The portfolio returns are calculated by multiplying returns of assets by corresponding allocations' values. Parameters ---------- assets_rets_df simple returns of assets which create the portfolio allocations_df dataframe indexed with dates, showing allocations in time (one column per asset) Returns ------- portfolio_rets_tms timeseries of portfolio's returns """ assert np.all(assets_rets_df.columns.values == allocations_df.columns.values), \ "Different column values for assets and allocation matrix" assert np.all(assets_rets_df.index.values == allocations_df.index.values), \ "Different dates for assets and allocation matrix" # get indices of rows for which: sum of weights is greater than 1. The result of where is a tuple (for a vector # it's a 1-element tuple, for a matrix -- a 2-element tuple and so on). Thus it's necessary to unwrap the result # from a tuple, to get the array of indices (instead of 1-elem. tuple consisted of an array). incorrect_weights_rows = np.abs(allocations_df.sum(axis=1) - 1.0) > cls.EPSILON # type: np.ndarray if np.any(incorrect_weights_rows): dates = allocations_df.index.values[incorrect_weights_rows] dates_str = ", ".join([date_to_str(date) for date in dates]) cls.logger().warning( "Weights don't sum up to 1 for the following dates: " + dates_str) scaled_returns = assets_rets_df * allocations_df # type: np.ndarray portfolio_rets = scaled_returns.sum(axis=1) portfolio_rets_tms = SimpleReturnsSeries( data=portfolio_rets, index=allocations_df.index.copy()) return portfolio_rets_tms
def __init__(self, logo_path: str = None, major_line: str = "", minor_line: str = "", date: datetime = None, grid_proportion: GridProportion = GridProportion.Eight): """ A stylised header element, consists of a major title (on left and right), subtitle and logo (loaded from the specified path). """ super().__init__(grid_proportion) self.logo_path = logo_path self.major_line = major_line self.minor_line = minor_line if date is None: date = datetime.date.today() self.date = date_to_str(date, DateFormat.LONG_DATE)
def test_date_to_str(self): expected_str = '2000-11-30' actual_date = date_to_str(datetime.datetime(year=2000, month=11, day=30)) self.assertEqual(actual_date, expected_str)
def __init__(self, data_provider: DataProvider, start_date, end_date, initial_cash, frequency: Frequency = Frequency.MIN_1): """ Set up the backtest variables according to what has been passed in. """ super().__init__() self.logger = qf_logger.getChild(self.__class__.__name__) self.logger.info( "\n".join([ "Testing the Backtester:", "Start date: {:s}".format(date_to_str(start_date)), "End date: {:s}".format(date_to_str(end_date)), "Initial cash: {:.2f}".format(initial_cash), "Frequency of the simulated execution handler: {}".format(frequency) ]) ) timer = SettableTimer(start_date) notifiers = Notifiers(timer) if frequency <= Frequency.DAILY: data_handler = DailyDataHandler(data_provider, timer) else: data_handler = IntradayDataHandler(data_provider, timer) contract_ticker_mapper = SimulatedBloombergContractTickerMapper() portfolio = Portfolio(data_handler, initial_cash, timer, contract_ticker_mapper) signals_register = BacktestSignalsRegister() backtest_result = BacktestResult(portfolio=portfolio, backtest_name="Testing the Backtester", start_date=start_date, end_date=end_date, signals_register=signals_register) monitor = Mock(spec=BacktestMonitor) commission_model = FixedCommissionModel(0.0) slippage_model = PriceBasedSlippage(0.0, data_provider, contract_ticker_mapper) execution_handler = SimulatedExecutionHandler( data_handler, timer, notifiers.scheduler, monitor, commission_model, contract_ticker_mapper, portfolio, slippage_model) broker = BacktestBroker(portfolio, execution_handler) order_factory = OrderFactory(broker, data_handler, contract_ticker_mapper) event_manager = self._create_event_manager(timer, notifiers) time_flow_controller = BacktestTimeFlowController( notifiers.scheduler, event_manager, timer, notifiers.empty_queue_event_notifier, end_date ) position_sizer = SimplePositionSizer(broker, data_handler, order_factory, contract_ticker_mapper, signals_register) self.logger.info( "\n".join([ "Configuration of components:", "Position sizer: {:s}".format(position_sizer.__class__.__name__), "Timer: {:s}".format(timer.__class__.__name__), "Data Provider: {:s}".format(data_provider.__class__.__name__), "Backtest Result: {:s}".format(backtest_result.__class__.__name__), "Monitor: {:s}".format(monitor.__class__.__name__), "Execution Handler: {:s}".format(execution_handler.__class__.__name__), "Commission Model: {:s}".format(commission_model.__class__.__name__), "Broker: {:s}".format(broker.__class__.__name__), "Contract-Ticker Mapper: {:s}".format(contract_ticker_mapper.__class__.__name__) ]) ) self.broker = broker self.notifiers = notifiers self.initial_cash = initial_cash self.start_date = start_date self.end_date = end_date self.event_manager = event_manager self.contract_ticker_mapper = contract_ticker_mapper self.data_handler = data_handler self.portfolio = portfolio self.execution_handler = execution_handler self.position_sizer = position_sizer self.orders_filters = [] self.monitor = monitor self.timer = timer self.order_factory = order_factory self.time_flow_controller = time_flow_controller