def _receive_reference_response(self, tickers, fields): response_events = get_response_events(self._session) tickers_fields_container = QFDataFrame(index=tickers, columns=fields) for ev in response_events: check_event_for_errors(ev) security_data_array = extract_security_data(ev) check_security_data_for_errors(security_data_array) for i in range(security_data_array.numValues()): security_data = security_data_array.getValueAsElement(i) check_security_data_for_errors(security_data) security_name = security_data.getElementAsString(SECURITY) ticker = BloombergTicker.from_string(security_name) field_data_array = security_data.getElement(FIELD_DATA) for field_name in fields: try: value = field_data_array.getElementAsFloat(field_name) except blpapi.exception.InvalidConversionException: value = field_data_array.getElementAsString(field_name) except blpapi.exception.NotFoundException: value = np.nan tickers_fields_container.loc[ticker, field_name] = value return tickers_fields_container
def _get_exposure(self, regressors_tickers: List, regression_len: int): df = QFDataFrame() for portfolio_date, positions in self.positions_history.iterrows(): positions = positions.dropna() positions_tickers = positions.index.tolist() exposure = QFSeries([x.total_exposure for x in positions]) portfolio_net_liquidation = self.portfolio_nav_history.asof( portfolio_date) positions_allocation = exposure / portfolio_net_liquidation from_date = portfolio_date - RelativeDelta(months=regression_len) coefficients, current_regressors_tickers = self._get_coefficients_and_current_regressors_tickers( regressors_tickers, positions_tickers, positions_allocation, from_date, portfolio_date) df = df.append( QFDataFrame( { col: val for val, col in zip(coefficients, current_regressors_tickers) }, index=[portfolio_date])) # remove missing regressors df = df.dropna(axis=1, how='all') return df
def test_import_custom_dataframe_shifted(self): # This tests issue #79. template_file_path = self.template_file_path(SINGLE_SHEET_CUSTOM_INDEX_DATA_FRAME_SHIFTED) # With index and column names. df = QFDataFrame({"Test": [1, 2, 3, 4, 5], "Test2": [10, 20, 30, 40, 50]}, ["A", "B", "C", "D", "E"]) imported_dataframe = self.xl_importer.import_container(file_path=template_file_path, container_type=QFDataFrame, starting_cell='C10', ending_cell='E15', include_index=True, include_column_names=True) assert_dataframes_equal(df, imported_dataframe) # With index and no column names. df = QFDataFrame({0: [1, 2, 3, 4, 5], 1: [10, 20, 30, 40, 50]}, ["A", "B", "C", "D", "E"]) imported_dataframe = self.xl_importer.import_container(file_path=template_file_path, container_type=QFDataFrame, starting_cell='C11', ending_cell='E15', include_index=True, include_column_names=False) assert_dataframes_equal(df, imported_dataframe) # With column names and no index. df = QFDataFrame({"Test": [1, 2, 3, 4, 5], "Test2": [10, 20, 30, 40, 50]}) imported_dataframe = self.xl_importer.import_container(file_path=template_file_path, container_type=QFDataFrame, starting_cell='D10', ending_cell='E15', include_index=False, include_column_names=True) assert_dataframes_equal(df, imported_dataframe) # With no column names and no index. df = QFDataFrame({0: [1, 2, 3, 4, 5], 1: [10, 20, 30, 40, 50]}) imported_dataframe = self.xl_importer.import_container(file_path=template_file_path, container_type=QFDataFrame, starting_cell='D11', ending_cell='E15', include_index=False, include_column_names=False) assert_dataframes_equal(df, imported_dataframe)
def convert_dataframe_frequency( dataframe: QFDataFrame, frequency: Frequency) -> SimpleReturnsDataFrame: """ Converts each column in the dataframe to the specified frequency. ValueError is raised when a column has a lower frequency than the one we are converting to. """ # Verify that all columns in the dataframe have a lower frequency. data_frequencies = dataframe.get_frequency() for column, col_frequency in data_frequencies.items(): if col_frequency < frequency: raise ValueError( "Column '{}' cannot be converted to '{}' frequency because its frequency is '{}'." .format(column, frequency, col_frequency)) if frequency == Frequency.DAILY: return dataframe.to_simple_returns() filled_df = dataframe.to_prices().fillna(method="ffill") new_columns = {} for column in filled_df: new_columns[column] = get_aggregate_returns(filled_df[column], frequency) return SimpleReturnsDataFrame(new_columns)
def _receive_intraday_response(self, requested_ticker: BloombergTicker, requested_fields): """ The response for intraday bar is related to a single ticker. """ response_events = get_response_events(self._session) tickers_data_dict = defaultdict( lambda: QFDataFrame(columns=requested_fields)) for event in response_events: try: check_event_for_errors(event) bar_data = extract_bar_data(event) bar_tick_data_array = bar_data.getElement(BAR_TICK_DATA) dates = [ to_datetime(e.getElementAsDatetime("time")) for e in bar_tick_data_array.values() ] dates_fields_values = QFDataFrame(np.nan, index=dates, columns=requested_fields) for field_name in requested_fields: dates_fields_values.loc[:, field_name] = [ self._get_float_or_nan_intraday( data_of_date_elem, field_name) for data_of_date_elem in bar_tick_data_array.values() ] df = tickers_data_dict[requested_ticker] tickers_data_dict[requested_ticker] = df.append( dates_fields_values) except BloombergError as e: self.logger.error(e) return tickers_data_dict[requested_ticker]
def test_tickers_dict_to_data_array(self): ticker_1 = BloombergTicker("Example 1") ticker_2 = BloombergTicker("Example 2") fields = [PriceField.Open, PriceField.Close] index = self.index[:3] data = [[[4., 1.], [nan, 5.]], [[5., 2.], [nan, 7.]], [[6., 3.], [nan, 8.]]] prices_df_1 = QFDataFrame(data={ PriceField.Close: [1., 2., 3.], PriceField.Open: [4., 5., 6.] }, index=index) prices_df_2 = QFDataFrame(data={PriceField.Close: [5., 7., 8.]}, index=index) data_array = tickers_dict_to_data_array( { ticker_1: prices_df_1, ticker_2: prices_df_2 }, [ticker_1, ticker_2], fields) self.assertEqual(dtype("float64"), data_array.dtype) expected_data_array = QFDataArray.create(index, [ticker_1, ticker_2], fields, data) assert_equal(data_array, expected_data_array)
def _add_up_and_down_trend_strength(self, prices_df: QFDataFrame): def _down_trend_fun(df): return down_trend_strength(df, self.use_next_open_instead_of_close) def _up_trend_fun(df): return up_trend_strength(df, self.use_next_open_instead_of_close) up_trend_strength_tms = prices_df.rolling_time_window(window_length=self.window_len, step=1, func=_down_trend_fun) down_trend_strength_tms = prices_df.rolling_time_window(window_length=self.window_len, step=1, func=_up_trend_fun) chart = LineChart() up_trend_elem = DataElementDecorator(up_trend_strength_tms) down_trend_elem = DataElementDecorator(down_trend_strength_tms) chart.add_decorator(up_trend_elem) chart.add_decorator(down_trend_elem) legend = LegendDecorator(legend_placement=Location.BEST, key='legend') legend.add_entry(up_trend_elem, 'Up trend strength') legend.add_entry(down_trend_elem, 'Down trend strength') chart.add_decorator(legend) title = "Strength of the up and down trend - rolling {} days".format(self.window_len) title_decorator = TitleDecorator(title, key="title") chart.add_decorator(title_decorator) self._add_axes_position_decorator(chart) self.document.add_element(ChartElement(chart, figsize=self.image_size, dpi=self.dpi))
def __init__(self, data=None, index=None, columns=None, dtype=None, copy=False): self.logger = qf_logger.getChild(self.__class__.__name__) # Data Frame containing the table data self.data = QFDataFrame(data, index, columns, dtype, copy) # Dictionary containing a mapping from column names onto ColumnStyles self._columns_styles = { column_name: self.ColumnStyle(column_name) for column_name in self.data.columns.tolist() } # Series containing the styles for each of the rows self._rows_styles = QFSeries( data=[self.RowStyle(loc) for loc in self.data.index], index=self.data.index) # Data Frame containing styles for all cells in the table, based upon columns_styles and rows_styles self._styles = QFDataFrame(data={ column_name: [ self.CellStyle(row_style, column_style) for row_style in self.rows_styles ] for column_name, column_style in self.columns_styles.items() }, index=self.data.index, columns=self.data.columns) self.table_styles = self.Style()
def _get_expiration_dates(self, dir_path: str, future_tickers: Sequence[FutureTicker]): tickers_dates_dict = {} for future_ticker in future_tickers: for path in list(Path(dir_path).glob('**/{}.txt'.format(future_ticker.family_id.replace("{}", "")))): try: df = pd.read_csv(path, names=['Contract', 'Expiration Date'], parse_dates=['Expiration Date'], date_parser=lambda date: datetime.strptime(date, '%Y%m%d'), index_col="Contract") df = df.rename(columns={'Expiration Date': ExpirationDateField.LastTradeableDate}) df.index = PortaraTicker.from_string(df.index, security_type=future_ticker.security_type, point_value=future_ticker.point_value) if all(future_ticker.belongs_to_family(x) for x in df.index): tickers_dates_dict[future_ticker] = QFDataFrame(df) else: self.logger.info(f"Not all tickers belong to family {future_ticker}") except Exception: self.logger.debug(f"File {path} does not contain valid expiration dates and therefore will be " f"excluded.") # Log all the future tickers, which could not have been mapped correctly tickers_without_matching_files = set(future_tickers).difference(tickers_dates_dict.keys()) for ticker in tickers_without_matching_files: tickers_dates_dict[ticker] = QFDataFrame(columns=[ExpirationDateField.LastTradeableDate]) self.logger.warning(f"No expiration dates were found for ticker {ticker}. Check if file " f"{ticker.family_id.replace('{}', '')}.txt exists in the {dir_path} and if it contains" f"valid expiration dates for the ticker.") return tickers_dates_dict
def test_compute_container_hash__data_array(self): ticker_1 = BloombergTicker("Example 1") ticker_2 = BloombergTicker("Example 2") prices_df_1 = QFDataFrame(data={ PriceField.Close: [1, 2, 3], PriceField.Open: [4, 5, 6] }) prices_df_2 = QFDataFrame(data={PriceField.Close: [5, 7, 8]}) data_array_1 = tickers_dict_to_data_array( { ticker_1: prices_df_1, ticker_2: prices_df_2 }, [ticker_1, ticker_2], [PriceField.Open, PriceField.Close]) data_array_2 = tickers_dict_to_data_array( { ticker_1: prices_df_1, ticker_2: prices_df_2, }, [ticker_1, ticker_2], [PriceField.Open, PriceField.Close]) data_array_3 = tickers_dict_to_data_array( { ticker_2: prices_df_2, ticker_1: prices_df_1, }, [ticker_1, ticker_2], [PriceField.Open, PriceField.Close]) self.assertEqual(compute_container_hash(data_array_1), compute_container_hash(data_array_2)) self.assertNotEqual(compute_container_hash(data_array_1), compute_container_hash(data_array_3))
def rank_strategies(self, df: SimpleReturnsDataFrame, ascending: bool = True) -> QFDataFrame: """ Rank strategies using the ranking function. The worst strategy should be marked as 1. Parameters ---------- df: SimpleReturnsDataFrame dataframe containing different strategies returns in the columns ascending: bool if True - the smaller the measure, the worse is the strategy Returns ------- QFDataFrame data frame indexed by strategy names with two columns: quality (containing the quality measure computed for the given strategy, e.g. sharpe ratio) and rank. """ rank_df = QFDataFrame(data={ "quality": [self.ranking_function(df[col]) for col in df.columns] }, index=df.columns) rank_df = rank_df.replace([np.inf, -np.inf], np.nan) if rank_df.isna().values.any(): raise ValueError( "There exist nan or infinite values in the rank_df") rank_df["rank"] = rank_df["quality"].rank(method="min", ascending=ascending) return rank_df
def get_signals(self) -> QFDataFrame: df = QFDataFrame.from_records(self._signals_data, columns=["Date", "Ticker", "Signal"]) # Modify the dataframe to move all signals for certain tickers to separate columns and set the index to date df = df.pivot_table(index='Date', columns='Ticker', values='Signal', aggfunc='first') return QFDataFrame(df)
def test_stats_functions(self): qf_df = QFDataFrame(data=self.prices_values, index=self.dates, columns=self.column_names) max_qf_df = qf_df.max() expected_max = QFSeries([5, 5, 5, 5, 5], index=['a', 'b', 'c', 'd', 'e']) self.assertEqual(type(max_qf_df), QFSeries) assert_series_equal(max_qf_df, expected_max) self.assertEqual(dtype("float64"), max_qf_df.dtypes)
def _create_mse_debug_chart(self, alphas, chosen_solution, mean_square_errors, min_se_solution): mse_chart = LineChart() mean_square_errors_paths = QFDataFrame(data=mean_square_errors, index=pd.Index(alphas)) mse_chart.add_decorator(TitleDecorator("Cross-validated avg. MSE of Elastic Net fit")) for _, path in mean_square_errors_paths.iteritems(): mse_chart.add_decorator(DataElementDecorator(path)) mse_chart.add_decorator(VerticalLineDecorator(x=min_se_solution, linestyle='-.')) mse_chart.add_decorator(VerticalLineDecorator(x=chosen_solution, linestyle='-')) mse_chart.plot() mse_chart.axes.invert_xaxis() return mse_chart
def _rolling_stress_indicator(self, data_frame_window: QFDataFrame): zscore_df = QFDataFrame() for name, series in data_frame_window.items(): zscore_df[name] = (series - series.mean()) / series.std() last_row = zscore_df.tail(1) result = last_row.dot( self.weights) # produces a weighted sum of the z-scored values result = result[0] / sum( self.weights ) # result was a single element series, return the value only return result
def _create_coeffs_debug_chart(self, alphas, chosen_solution, coeffs_path, min_se_solution): coeffs_chart = LineChart() coefficients_paths = QFDataFrame(data=coeffs_path, index=pd.Index(alphas)) for _, path in coefficients_paths.iteritems(): coeffs_chart.add_decorator(DataElementDecorator(path)) coeffs_chart.add_decorator(TitleDecorator("Elastic Net (using Cross Validation)")) coeffs_chart.add_decorator(VerticalLineDecorator(x=min_se_solution, linestyle='-.')) coeffs_chart.add_decorator(VerticalLineDecorator(x=chosen_solution, linestyle='-')) coeffs_chart.plot() coeffs_chart.axes.invert_xaxis() return coeffs_chart
def _add_performance_statistics(self): """ For each ticker computes its overall performance (PnL of short positions, PnL of long positions, total PnL). It generates a table containing final PnL values for each of the ticker nad optionally plots the performance throughout the backtest. """ closed_positions = self.backtest_result.portfolio.closed_positions() closed_positions_pnl = QFDataFrame.from_records( data=[(self._ticker_name(p.contract()), p.end_time, p.direction(), p.total_pnl) for p in closed_positions], columns=["Tickers name", "Time", "Direction", "Realised PnL"] ) closed_positions_pnl = closed_positions_pnl.sort_values(by="Time") # Get all open positions history open_positions_history = self.backtest_result.portfolio.positions_history() open_positions_history = open_positions_history.reset_index().melt( id_vars='index', value_vars=open_positions_history.columns, var_name='Contract', value_name='Position summary') open_positions_pnl = QFDataFrame(data={ "Tickers name": open_positions_history["Contract"].apply(lambda contract: self._ticker_name(contract)), "Time": open_positions_history["index"], "Direction": open_positions_history["Position summary"].apply( lambda p: p.direction if isinstance(p, BacktestPositionSummary) else 0), "Total PnL of open position": open_positions_history["Position summary"].apply( lambda p: p.total_pnl if isinstance(p, BacktestPositionSummary) else 0) }) all_positions_pnl = pd.concat([closed_positions_pnl, open_positions_pnl], sort=False) performance_dicts_series = all_positions_pnl.groupby(by=["Tickers name"]).apply( self._performance_series_for_ticker) performance_df = QFDataFrame(performance_dicts_series.tolist(), index=performance_dicts_series.index) self.document.add_element(NewPageElement()) self.document.add_element(HeadingElement(level=2, text="Performance of each asset")) final_performance = performance_df. \ applymap(lambda pnl_series: pnl_series.iloc[-1] if not pnl_series.empty else 0.0). \ sort_values(by="Overall performance", ascending=False). \ applymap(lambda p: '{:,.2f}'.format(p)). \ reset_index() table = DFTable(final_performance, css_classes=['table', 'left-align']) table.add_columns_classes(["Tickers name"], 'wide-column') self.document.add_element(table) # Add performance plots if self.generate_pnl_chart_per_ticker: self.document.add_element(NewPageElement()) self.document.add_element( HeadingElement(level=2, text="Performance of each asset during the whole backtest")) for ticker_name, performance in performance_df.iterrows(): self._plot_ticker_performance(ticker_name, performance)
def _receive_historical_response( self, requested_tickers: Sequence[BloombergTicker], requested_fields: Sequence[str]): ticker_str_to_ticker: Dict[str, BloombergTicker] = { t.as_string(): t for t in requested_tickers } response_events = get_response_events(self._session) tickers_data_dict = defaultdict( lambda: QFDataFrame(columns=requested_fields)) for event in response_events: try: check_event_for_errors(event) security_data = extract_security_data(event) check_security_data_for_errors(security_data) field_data_array = security_data.getElement(FIELD_DATA) dates = [ to_datetime(x.getElementAsDatetime(DATE)) for x in field_data_array.values() ] dates_fields_values = QFDataFrame(np.nan, index=dates, columns=requested_fields) for field_name in requested_fields: dates_fields_values.loc[:, field_name] = [ self._get_float_or_nan(data_of_date_elem, field_name) for data_of_date_elem in field_data_array.values() ] security_name = security_data.getElementAsString(SECURITY) try: ticker = ticker_str_to_ticker[security_name] tickers_data_dict[ticker] = tickers_data_dict[ ticker].append(dates_fields_values) except KeyError: self.logger.warning( f"Received data for a ticker which was not present in the request: " f"{security_name}. The data for that ticker will be excluded from parsing." ) except BloombergError as e: self.logger.error(e) return tickers_dict_to_data_array(tickers_data_dict, list(tickers_data_dict.keys()), requested_fields)
def _generate_exposure_values(self, config: FastAlphaModelTesterConfig, data_handler: FastDataHandler, tickers: Sequence[Ticker]): """ For the given Alpha model and its parameters, generates the dataframe containing all exposure values, that will be returned by the model through signals. """ model = config.generate_model(data_handler) current_exposures_values = QFSeries( index=pd.Index(tickers, name=TICKERS)) current_exposures_values[:] = 0.0 backtest_dates = pd.date_range(self._start_date, self._end_date, freq="B") exposure_values_df = QFDataFrame(index=backtest_dates, columns=pd.Index(tickers, name=TICKERS)) for ticker in tickers: if isinstance(ticker, FutureTicker): # Even if the tickers were already initialize, during pickling process, the data handler and timer # information is lost ticker.initialize_data_provider(self._timer, data_handler) for i, curr_datetime in enumerate(backtest_dates): new_exposures = QFSeries(index=tickers) self._timer.set_current_time(curr_datetime) for j, ticker, curr_exp_value in zip(count(), tickers, current_exposures_values): curr_exp = Exposure(curr_exp_value) if is_finite_number( curr_exp_value) else None try: new_exp = model.calculate_exposure(ticker, curr_exp) except NoValidTickerException: new_exp = None new_exposures.iloc[ j] = new_exp.value if new_exp is not None else None # assuming that we always follow the new_exposures from strategy, disregarding confidence levels # and expected moves, looking only at the suggested exposure current_exposures_values = new_exposures exposure_values_df.iloc[i, :] = current_exposures_values.iloc[:] exposure_values_df = exposure_values_df.dropna(axis=1, how="all") return exposure_values_df
def _calculate_portfolio_returns_tms(self, tickers, open_to_open_returns_df: QFDataFrame, exposure_values_df: QFDataFrame) \ -> SimpleReturnsSeries: """ SimpleReturnsSeries of the portfolio - for each date equal to the portfolio performance over the last open-to-open period, ex. value indexed as 2010-02-15 would refer to the portfolio value change between open at 14th and open at 15th, and would be based on the signal from 2010-02-13; the first index of the series is the Day 3 of the backtest, as the first signal calculation occurs after Day 1 (see ORDER OF ACTIONS below) the last index of the series is test_end_date and the portfolio exposure is being set to zero on the opening of the test_end_date ORDER OF ACTIONS: -- Day 1 -- signal is generated, based on the historic data INCLUDING prices from Day 1 suggested exposure for Day 2 is calculated -- Day 2 -- a trade is entered, held or exited (or nothing happens) regarding the suggested exposure this action is performed on the opening of the day -- Day 3 -- at the opening the open-to-open return is calculated now it is possible to estimate current portfolio value the simple return of the portfolio (Day 3 to Day 2) is saved and indexed with Day 3 date """ open_to_open_returns_df = open_to_open_returns_df.dropna(how="all") shifted_signals_df = exposure_values_df.shift(2, axis=0) shifted_signals_df = shifted_signals_df.iloc[2:] daily_returns_of_strategies_df = shifted_signals_df * open_to_open_returns_df daily_returns_of_strategies_df = daily_returns_of_strategies_df.dropna( axis=0, how='all') daily_returns_of_strategies_df = cast_dataframe( daily_returns_of_strategies_df, SimpleReturnsDataFrame) # type: SimpleReturnsDataFrame weights = Portfolio.one_over_n_weights(tickers) # for strategies based on more than one ticker (ex. VolLongShort) use the line below: # weights = QFSeries(np.ones(daily_returns_of_strategies_df.num_of_columns)) portfolio_rets_tms, _ = Portfolio.constant_weights( daily_returns_of_strategies_df, weights) return portfolio_rets_tms
def _get_current_bars(self, tickers: Sequence[Ticker]) -> QFDataFrame: """ Gets the current bars for given Tickers. If the bars are not available yet, NaNs are returned. The result is a QFDataFrame with Tickers as an index and PriceFields as columns. In case of daily trading, the current bar is returned only at the Market Close Event time, as the get_price function will not return data for the current date until the market closes. In case of intraday trading (for N minutes frequency) the current bar can be returned in the time between (inclusive) N minutes after MarketOpenEvent and the MarketCloseEvent. Important: If current time ("now") contains non-zero seconds or microseconds, NaNs will be returned. """ if not tickers: return QFDataFrame() assert self._frequency >= Frequency.DAILY, "Lower than daily frequency is not supported by the simulated " \ "executor" current_datetime = self._timer.now() market_close_time = current_datetime + MarketCloseEvent.trigger_time( ) == current_datetime if self._frequency == Frequency.DAILY: # In case of daily trading, the current bar can be returned only at the Market Close if not market_close_time: return QFDataFrame(index=tickers, columns=PriceField.ohlcv()) else: current_datetime = date_to_datetime(current_datetime.date()) start_date = current_datetime - self._frequency.time_delta() current_bar_start = current_datetime else: # In case of intraday trading the current full bar is always indexed by the left side of the time range start_date = current_datetime - self._frequency.time_delta() current_bar_start = start_date prices_data_array = self._data_handler.get_price( tickers=tickers, fields=PriceField.ohlcv(), start_date=start_date, end_date=current_datetime, frequency=self._frequency) try: current_bars = cast_data_array_to_proper_type( prices_data_array.loc[current_bar_start]) except KeyError: current_bars = QFDataFrame(index=tickers, columns=PriceField.ohlcv()) return current_bars
def create_holdings_chart(positions: QFDataFrame) -> LineChart: """ Creates a line chart showing holdings per day based on the specified positions. Parameters ---------- positions: QFDataFrame Positions as returned by the extract_rets_pos_txn_from_zipline function. Returns ------- LineChart Created line chart """ # Based on: # https://github.com/quantopian/pyfolio/blob/5d63df4ca6e0ead83f4bebf9860732d37f532026/pyfolio/plotting.py#L323 result = LineChart() # Perform some calculations. positions = positions.copy().drop("cash", axis="columns") holdings = positions.apply(lambda x: np.sum(x != 0), axis="columns") holdings_by_month = holdings.resample("1M").mean() holdings_decorator = DataElementDecorator(holdings, color="steelblue", linewidth=1.5) result.add_decorator(holdings_decorator) holdings_by_month_decorator = DataElementDecorator(holdings_by_month, color="orangered", alpha=0.5, linewidth=2) result.add_decorator(holdings_by_month_decorator) hline_decorator = HorizontalLineDecorator(holdings.values.mean(), linestyle="--") result.add_decorator(hline_decorator) legend = LegendDecorator() legend.add_entry(holdings_decorator, "Daily Holdings") legend.add_entry(holdings_by_month_decorator, "Average Daily Holdings, by month") legend.add_entry(hline_decorator, "Average Daily Holdings, net") result.add_decorator(legend) result.add_decorator(TitleDecorator("Holdings per Day")) result.add_decorator( AxesLabelDecorator(y_label="Amount of holdings per Day")) return result
def get_factor_return_attribution(cls, fund_tms: QFSeries, fit_tms: QFSeries, regressors_df: QFDataFrame, coefficients: QFSeries, alpha: float) -> Tuple[QFSeries, float]: """ Returns performance attribution for each factor in given regressors and also calculates the unexplained return. """ fund_returns = fund_tms.to_simple_returns() regressors_returns = regressors_df.to_simple_returns() annualised_fund_return = cagr(fund_returns) annualised_fit_return = cagr(fit_tms) total_nav = fit_tms.to_prices(initial_price=1.0) def calc_factors_profit(series) -> float: factor_ret = regressors_returns.loc[:, series.name].values return coefficients.loc[series.name] * (total_nav[:-1].values * factor_ret).sum() factors_profits = regressors_returns.apply(calc_factors_profit) alpha_profit = total_nav[:-1].sum() * alpha total_profit = factors_profits.sum() + alpha_profit regressors_return_attribution = factors_profits * annualised_fit_return / total_profit regressors_return_attribution = cast_series( regressors_return_attribution, QFSeries) unexplained_return = annualised_fund_return - regressors_return_attribution.sum( ) return regressors_return_attribution, unexplained_return
def _create_performance_tables( self, performance_df: QFDataFrame) -> List[DFTable]: """ Create a formatted DFTable out of the performance_df data frame. """ numeric_columns = [ col for col in performance_df.columns if is_numeric_dtype(performance_df[col]) ] performance_df[numeric_columns] = performance_df[ numeric_columns].applymap(lambda x: '{:,.0f}'.format(x)) performance_df = performance_df.set_index("Asset").sort_index() # Divide the performance df into a number of data frames, so that each of them contains up to # self.max_col_per_page columns, but keep the first column of the original df in all of them split_dfs = np.array_split(performance_df, np.ceil(performance_df.num_of_columns / self._max_columns_per_page), axis=1) df_tables = [ DFTable(df.reset_index(), css_classes=[ 'table', 'shrink-font', 'right-align', 'wide-first-column' ]) for df in split_dfs ] return df_tables
def setUp(self): self.initial_risk_stats_factory = InitialRiskStatsFactory( max_accepted_dd=0.2, target_return=0.05) self.sample_trades_df = QFDataFrame( data=[[-0.11, 0.0, -0.105], [-0.11, 0.05, 0.19], [0.1, 0.0, 0.0], [0.0, 0.0, 0.0]])
def get_liquid_hours(self, contract: IBContract) -> QFDataFrame: """ Returns a QFDataFrame containing information about liquid hours of the given contract. """ with self.lock: self._reset_action_lock() request_id = 3 self.client.reqContractDetails(request_id, contract) if self._wait_for_results(): contract_details = self.wrapper.contract_details liquid_hours = contract_details.tradingHours.split(";") liquid_hours_df = QFDataFrame.from_records([ hours.split("-") for hours in liquid_hours if not hours.endswith("CLOSED") ], columns=[ "FROM", "TO" ]) for col in liquid_hours_df.columns: liquid_hours_df[col] = to_datetime(liquid_hours_df[col], format="%Y%m%d:%H%M") liquid_hours_df.name = contract_details.contract.symbol return liquid_hours_df else: error_msg = 'Time out while getting contract details' self.logger.error(error_msg) raise BrokerException(error_msg)
def __init__(self, risk_estimation_factor: float, data_provider: DataHandler, start_date: datetime, end_date: datetime, tickers: Sequence[Ticker], number_of_trades: int, time_in_the_market: float, exposure: Exposure = Exposure.LONG, frequency: Frequency = Frequency.DAILY, seed: Optional[int] = None): super().__init__(risk_estimation_factor, data_provider) self.timer = data_provider.timer self.start_date = start_date self.end_date = end_date self.frequency = frequency scenarios_generator = ScenariosGenerator() self.trading_scenarios = QFDataFrame( columns=tickers, data={ ticker: scenarios_generator.make_exposure_scenarios( start_date, end_date, number_of_trades, time_in_the_market, exposure, frequency, seed and seed + i) for i, ticker in enumerate(tickers) })
def compute_container_hash( data_container: Union[QFSeries, QFDataFrame, QFDataArray]) -> str: """ For the given data container returns the hexadecimal digest of the data. Parameters ---------- data_container: QFSeries, QFDataFrame, QFDataArray container, which digest should be computed Returns ------- str hexadecimal digest of data in the passed data container """ if isinstance(data_container, QFSeries): hashed_container = hash_pandas_object(data_container) elif isinstance(data_container, QFDataFrame): hashed_container = hash_pandas_object(data_container) elif isinstance(data_container, QFDataArray): hash_data_frame = QFDataFrame([ hash_pandas_object(data_container.loc[:, :, field].to_pandas()) for field in data_container.fields ]) hashed_container = hash_pandas_object(hash_data_frame) else: raise ValueError("Unsupported type of data container") return hashlib.sha1(hashed_container.values).hexdigest()
def __init__(self, ts: TradingSession, model_tickers_dict: Dict[AlphaModel, Sequence[Ticker]], use_stop_losses=True): """ Parameters ---------- ts Trading session model_tickers_dict Dict mapping models to list of tickers that the model trades. (The tickers for which the model gives recommendations) use_stop_losses flag indicating if the stop losses should be used or not. If False, all stop orders are ignored """ self._broker = ts.broker self._order_factory = ts.order_factory self._data_handler = ts.data_handler self._contract_ticker_mapper = ts.contract_ticker_mapper self._position_sizer = ts.position_sizer self._timer = ts.timer self._model_tickers_dict = model_tickers_dict self._use_stop_losses = use_stop_losses self.signals_df = QFDataFrame( ) # rows indexed by date and columns by "Ticker@AlphaModel" string ts.notifiers.scheduler.subscribe(BeforeMarketOpenEvent, listener=self) self.logger = qf_logger.getChild(self.__class__.__name__) self._log_configuration()
def test_asof_multiple_dates_gaps_at_the_end(self): actual_result = self.qf_data_array.asof(str_to_date('2018-02-06')) expected_result = QFDataFrame( index=self.qf_data_array.tickers.to_index(), columns=self.qf_data_array.fields.to_index(), data=[[16, 17, 18, 19], [12, 13, 14, 15]]) assert_dataframes_equal(expected_result, actual_result)