def test_get_volume_weighted_average_price(self): original_datetimes = date_range('2015-01-01', periods=12, freq='10Min') prices_tms = PricesSeries(data=[1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4], index=original_datetimes) volumes_tms = QFSeries(data=[1, 2, 3, 4, 1, 2, 4, 3, 2, 1, 3, 4], index=original_datetimes) interval = Timedelta('40 min') expected_datetimes = date_range('2015-01-01 00:40', periods=2, freq=interval) expected_avg_weighted_prices = [3, 2.9] expected_prices_tms = PricesSeries(data=expected_avg_weighted_prices, index=expected_datetimes) actual_prices_tms = volume_weighted_average_price(prices_tms, volumes_tms, interval) assert_series_equal(expected_prices_tms, actual_prices_tms, absolute_tolerance=0.0)
def setUp(self): self.return_dates = date_range('2015-01-01', periods=20, freq='D') prices_values = [100, 101, 103.02, 106.1106, 108.232812, 109.31514012, 109.31514012, 108.2219887188, 106.057548944424, 107.118124433868, 110.331668166884, 115.848251575229, 120.482181638238, 124.096647087385, 126.578580029132, 127.844365829424, 127.844365829424, 129.1228094877180, 132.9964937723500, 135.656423647797, 141.082680593708] prices_dates = date_range('2014-12-31', periods=1, freq='D').append(self.return_dates) self.test_prices_tms = PricesSeries(data=prices_values, index=prices_dates) self.test_dd_prices_tms = PricesSeries(data=[100, 90, 80, 70, 95, 100, 100, 200, 100, 50, 100, 200, 150], index=date_range('2015-01-01', periods=13, freq='M')) self.test_returns = [0.01, 0.02, 0.03, 0.02, 0.01, 0, -0.01, -0.02, 0.01, 0.03, 0.05, 0.04, 0.03, 0.02, 0.01, 0, 0.01, 0.03, 0.02, 0.04] self.test_simple_returns_tms = SimpleReturnsSeries(data=self.test_returns, index=self.return_dates, dtype=float)
def test_max_drawdown(self): expected_max_drawdown = 0.0298 actual_max_drawdown = max_drawdown(self.test_prices_tms) self.assertAlmostEqual(expected_max_drawdown, actual_max_drawdown, delta=0.00000001) expected_max_drawdown = 0.3 prices_tms = PricesSeries(data=[100, 90, 80, 85, 70, 100], index=date_range('2015-01-01', periods=6)) actual_max_drawdown = max_drawdown(prices_tms) self.assertAlmostEqual(expected_max_drawdown, actual_max_drawdown, places=10) expected_max_drawdown = 0.35 prices_tms = PricesSeries(data=[100, 90, 80, 85, 70, 100, 90, 95, 65], index=date_range('2015-01-01', periods=9)) actual_max_drawdown = max_drawdown(prices_tms) self.assertEqual(expected_max_drawdown, actual_max_drawdown)
def volume_weighted_average_price(prices_tms: PricesSeries, volumes_tms: QFSeries, interval: Timedelta) -> PricesSeries: """ Aggregates prices in the prices_tms by calculating the average weighted price for each period. The average weighted prices are weighted by the volumes traded in each period. Parameters ---------- prices_tms: PricesSeries timeseries of prices which should be aggregated volumes_tms: QFSeries timeseries of volumes traded; must correspond to the prices_tms interval: Timedelta the length of each period from which prices should be aggregated Returns ------- PricesSeries timeseries of aggregated prices; first datetimes are: first_price_datetime + interval, first_price_datetime + 2*interval, ..., first_price_datetime + i*interval, where first_price_datetime is the datetime of the first price in the original prices_tms The last datetime is always <= last datetime in the prices_tms """ assert prices_tms.index.equals(volumes_tms.index) last_date = prices_tms.index[-1] beginning_of_window = prices_tms.index[0] end_of_window = beginning_of_window + interval weighted_avg_price_tms = PricesSeries(name=prices_tms.name) while end_of_window < last_date: prices_in_window = prices_tms.loc[ beginning_of_window:end_of_window].drop([end_of_window]).values volumes_in_window = volumes_tms.loc[ beginning_of_window:end_of_window].drop([end_of_window]).values # if there are no prices in the window then skip try with the next window if prices_in_window.size == 0: continue # if there are no volumes to use -> assume that volume in each step is the same if count_nonzero(volumes_in_window) == 0: # if all the volumes are set to 0 than assume that volume for each asset is the same weighted_avg_price = mean(prices_in_window) else: # calculate volume-weighted average price weighted_price_sum = prices_in_window.dot(volumes_in_window) volume_sum = sum(volumes_in_window) weighted_avg_price = weighted_price_sum / volume_sum # if the weighted average price is equal exactly 0, it means that there were missing data if is_finite_number(weighted_avg_price) and weighted_avg_price != 0: weighted_avg_price_tms[end_of_window] = weighted_avg_price beginning_of_window = end_of_window end_of_window = end_of_window + interval return weighted_avg_price_tms
def _compute_pnl_for_ticker(self, prices_df: PricesDataFrame, transactions_series: QFSeries, start_date: datetime, end_date: datetime) -> PricesSeries: pnl_values = [] current_realised_pnl = 0 ticker_to_position = {} # type: Dict[Ticker, BacktestPosition] prices_df = prices_df.ffill() for timestamp in date_range(start_date, end_date, freq="B"): timestamp = timestamp + AfterMarketCloseEvent.trigger_time() previous_after_market_close = timestamp - RelativeDelta(days=1) transactions_for_past_day = transactions_series.loc[previous_after_market_close:timestamp] transactions_for_past_day = transactions_for_past_day \ .where(transactions_for_past_day.index > previous_after_market_close).dropna(how="all") for t in transactions_for_past_day: position = ticker_to_position.get(t.ticker, BacktestPositionFactory.create_position(t.ticker)) ticker_to_position[t.ticker] = position position.transact_transaction(t) if position.is_closed(): ticker_to_position.pop(t.ticker) current_realised_pnl += position.total_pnl # update prices of all existing positions and get their unrealised pnl current_unrealised_pnl = 0.0 for ticker, position in ticker_to_position.items(): price = prices_df.loc[:timestamp, ticker].iloc[-1] position.update_price(price, price) current_unrealised_pnl += position.total_pnl pnl_values.append(current_unrealised_pnl + current_realised_pnl) return PricesSeries(data=pnl_values, index=date_range(start_date, end_date, freq="B"))
def test_log_returns_to_prices(self): prices_values = array([1, exp(1), exp(2), exp(-1), exp(2)]) prices_dates = pd.date_range('2015-01-01', periods=5) expected = PricesSeries(data=prices_values, index=prices_dates) returns_tms = LogReturnsSeries(data=[1, 1, -3, 3], index=expected.index[1::]) actual = returns_tms.to_prices() assert_series_equal(expected, actual)
def calculate_aggregated_cone_oos_only( self, oos_series: QFSeries, is_mean_return: float, is_sigma: float, number_of_std: float) -> QFDataFrame: """ This functions does not need the IS history, only the IS statistics. Parameters ---------- oos_series series that is plotted on the cone - corresponds to the oos returns is_mean_return mean daily log return of the strategy In Sample is_sigma std of daily log returns of the strategy In Sample number_of_std corresponds to the randomness of the stochastic process. reflects number of standard deviations to get expected values for. For example 1.0 means 1 standard deviation above the expected value. Returns ------- QFDataFrame: contains values corresponding to Strategy, Mean and Std. Values are indexed by number of days from which given cone was evaluated """ log_returns_tms = oos_series.to_log_returns() nr_of_data_points = oos_series.size strategy_values = np.empty(nr_of_data_points) expected_values = np.empty(nr_of_data_points) for i in range(nr_of_data_points): cone_start_idx = i + 1 # calculate total return of the strategy oos_log_returns = log_returns_tms[cone_start_idx:] total_strategy_return = exp( oos_log_returns.sum()) # 1 + percentage return # calculate expectation number_of_steps = len(oos_log_returns) starting_price = 1 # we take 1 as a base value total_expected_return = self.get_expected_value( is_mean_return, is_sigma, starting_price, number_of_steps, number_of_std) # writing to the array starting from the last array element and then moving towards the first one strategy_values[-i - 1] = total_strategy_return expected_values[-i - 1] = total_expected_return index = Int64Index(range(0, nr_of_data_points)) strategy_values_tms = PricesSeries(index=index, data=strategy_values) expected_tms = QFSeries(index=index, data=expected_values) return QFDataFrame({ 'Strategy': strategy_values_tms, 'Expectation': expected_tms, })
def test_portfolio_eod_series(self): expected_portfolio_eod_series = PricesSeries() # Empty portfolio portfolio, dh, timer = self.get_portfolio_and_data_handler() portfolio.update(record=True) expected_portfolio_eod_series[timer.time] = self.initial_cash contract = self.fut_contract ticker = portfolio.contract_ticker_mapper.contract_to_ticker(contract) # Buy contract self._shift_timer_to_next_day(timer) transaction_1 = Transaction(timer.time, contract, quantity=50, price=250, commission=7) portfolio.transact_transaction(transaction_1) self.data_handler_prices = self.prices_series portfolio.update(record=True) position = portfolio.open_positions_dict[contract] price_1 = dh.get_last_available_price(ticker) pnl = contract.contract_size * transaction_1.quantity * (price_1 - transaction_1.price) nav = self.initial_cash + pnl - transaction_1.commission expected_portfolio_eod_series[timer.time] = nav # Contract goes up in value self._shift_timer_to_next_day(timer) self.data_handler_prices = self.prices_up portfolio.update(record=True) price_2 = dh.get_last_available_price(ticker) # == 270 pnl = contract.contract_size * transaction_1.quantity * (price_2 - price_1) nav += pnl expected_portfolio_eod_series[timer.time] = nav # Sell part of the contract self._shift_timer_to_next_day(timer) transaction_2 = Transaction(timer.time, contract, quantity=-25, price=price_2, commission=19) portfolio.transact_transaction(transaction_2) self.data_handler_prices = self.prices_up portfolio.update(record=True) pnl = (transaction_2.price - price_2) * transaction_2.quantity * contract.contract_size - transaction_2.commission nav += pnl expected_portfolio_eod_series[timer.time] = nav # Price goes down self._shift_timer_to_next_day(timer) self.data_handler_prices = self.prices_down portfolio.update(record=True) price_3 = dh.get_last_available_price(ticker) # == 210 pnl2 = contract.contract_size * position.quantity() * (price_3 - price_2) nav += pnl2 expected_portfolio_eod_series[timer.time] = nav tms = portfolio.portfolio_eod_series() assert_series_equal(expected_portfolio_eod_series, tms)
def test_drawdown_tms(self): test_prices = [100, 90, 80, 85, 70, 100, 90, 95, 65] prices_tms = PricesSeries(data=test_prices, index=date_range('2015-01-01', periods=9)) expected_drawdown_values = [0, 0.1, 0.2, 0.15, 0.3, 0, 0.1, 0.05, 0.35] expected_drawdowns_tms = QFSeries(expected_drawdown_values, date_range('2015-01-01', periods=9)) actual_drawdowns_tms = drawdown_tms(prices_tms) assert_series_equal(expected_drawdowns_tms, actual_drawdowns_tms)
def portfolio_eod_series(self) -> PricesSeries: """ Returns a timeseries of value of the portfolio expressed in currency units """ end_of_day_date = list( map(lambda x: datetime(x.year, x.month, x.day), self._dates)) # remove time component portfolio_timeseries = PricesSeries(data=self._portfolio_values, index=end_of_day_date) return portfolio_timeseries
def _compute_volatility(self, prices_tms) -> float: """Compute the annualised volatility of the last self._number_of_samples days""" prices_tms = prices_tms.dropna().iloc[-self._number_of_samples:] prices_tms = PricesSeries(prices_tms) try: volatility = get_volatility(prices_tms, frequency=Frequency.DAILY, annualise=True) except (AssertionError, AttributeError): volatility = float('nan') return volatility
def setUp(self): return_dates = pd.date_range('2015-01-01', periods=20, freq='D') test_returns = [0.01, 0.02, 0.03, 0.02, 0.01, 0, -0.01, -0.02, 0.01, 0.03, 0.05, 0.04, 0.03, 0.02, 0.01, 0, 0.01, 0.03, 0.02, 0.04] self.test_simple_returns_tms = SimpleReturnsSeries(data=test_returns, index=return_dates, dtype=float, name='Test Name') prices_values = [100, 101, 103.02, 106.1106, 108.232812, 109.31514012, 109.31514012, 108.2219887188, 106.057548944424, 107.118124433868, 110.331668166884, 115.848251575229, 120.482181638238, 124.096647087385, 126.578580029132, 127.844365829424, 127.844365829424, 129.1228094877180, 132.9964937723500, 135.656423647797, 141.082680593708] prices_dates = pd.date_range('2014-12-31', periods=1, freq='D').append(return_dates) self.test_prices_tms = PricesSeries(data=prices_values, index=prices_dates, name='Test Name') test_log_returns = [0.009950331, 0.019802627, 0.029558802, 0.019802627, 0.009950331, 0, -0.010050336, -0.020202707, 0.009950331, 0.029558802, 0.048790164, 0.039220713, 0.029558802, 0.019802627, 0.009950331, 0, 0.009950331, 0.029558802, 0.019802627, 0.039220713] self.test_log_returns_tms = LogReturnsSeries(data=test_log_returns, index=return_dates, dtype=float, name='Test Name')
def test_historical_price__single_ticker__single_field__daily(self): self.current_time = str_to_date("2021-05-03 00:00:00.000000", DateFormat.FULL_ISO) # Test when the current day does not have the open price actual_series = self.data_provider.historical_price( self.ticker_2, PriceField.Open, 2, frequency=Frequency.DAILY) expected_series = PricesSeries( data=[27, 29], index=[str_to_date('2021-05-01'), str_to_date('2021-05-02')]) assert_series_equal(actual_series, expected_series, check_names=False) # Test when the previous day does not have the open price actual_series = self.data_provider.historical_price( self.ticker_1, PriceField.Open, 2, frequency=Frequency.DAILY) expected_series = PricesSeries( data=[25, 31], index=[str_to_date('2021-05-01'), str_to_date('2021-05-03')]) assert_series_equal(actual_series, expected_series, check_names=False)
def test_compute_container_hash__series(self): list_of_data = list(range(200)) qfseries_1 = QFSeries(data=list_of_data) qfseries_2 = QFSeries(data=list_of_data) returns_series = ReturnsSeries(data=list_of_data) prices_series = PricesSeries(data=list_of_data) self.assertEqual(compute_container_hash(qfseries_1), compute_container_hash(qfseries_2)) self.assertEqual(compute_container_hash(qfseries_1), compute_container_hash(returns_series)) self.assertEqual(compute_container_hash(qfseries_1), compute_container_hash(prices_series))
def _generate_buy_and_hold_returns(self, ticker: Ticker) -> SimpleReturnsSeries: """ Computes series of simple returns, which would be returned by the Buy and Hold strategy. """ if isinstance(ticker, FutureTicker): try: ticker.initialize_data_provider(SettableTimer(self._end_date), self._data_provider) futures_chain = FuturesChain( ticker, self._data_provider, FuturesAdjustmentMethod.BACK_ADJUSTED) prices_series = futures_chain.get_price( PriceField.Close, self._start_date, self._end_date) except NoValidTickerException: prices_series = PricesSeries() else: prices_series = self._data_provider.get_price( ticker, PriceField.Close, self._start_date, self._end_date) returns_tms = prices_series.to_simple_returns().replace( [-np.inf, np.inf], np.nan).fillna(0.0) returns_tms.name = "Buy and Hold" return returns_tms
def calculate_simple_cone(self, live_start_date: datetime, number_of_std: float) -> PricesSeries: """ Creates a simple cone starting from a given date using the solution to the stochastic equation: S(t) = S(0)*exp( (mu-0.5*sigma^2)*t + sigma*N(0,1)*sqrt(t) ) Parameters ---------- live_start_date: datetime datetime or string with date, corresponds to the cone start date number_of_std: float corresponds to the randomness of the stochastic process. reflects number of standard deviations to get expected values for. For example 1.0 means 1 standard deviation above the expected value. Returns ------- PriceSeries expected values """ is_log_tms = self.log_returns_tms.loc[ self.log_returns_tms.index < live_start_date] oos_log_tms = self.log_returns_tms.loc[ self.log_returns_tms.index >= live_start_date] mu = is_log_tms.mean() sigma = is_log_tms.std() days_oos = range(len(oos_log_tms) + 1) # a list [0, 1, 2, ... N] initial_price = self.series.asof( is_log_tms.index[-1]) # price at last in-sample date # for each day OOS calculate the expected value at different point in time using the _get_expected_value() # function that gives expectation in single point in time. expected_values = list( map( lambda nr_of_days: self.get_expected_value( mu, sigma, initial_price, nr_of_days, number_of_std), days_oos)) # We need to add last IS index value to connect the cone to the line. It will correspond to 0 days cone index = oos_log_tms.index.copy() index = index.insert(0, is_log_tms.index[-1]) return PricesSeries(index=index, data=expected_values)
def calculate_simple_cone_for_process(self, mu: float, sigma: float, number_of_std: float, number_of_steps: int, starting_value=1) -> PricesSeries: """ Creates a simple cone starting from a given date using the solution to the stochastic equation: S(t) = S(0)*exp( (mu-0.5*sigma^2)*t + sigma*N(0,1)*sqrt(t) ) Parameters ---------- mu mean return of the process. expressed in the frequency of samples (not annualised) sigma std of returns of the process. expressed in the frequency of samples (not annualised) number_of_std corresponds to the randomness of the stochastic process. reflects number of standard deviations to get expected values for. For example 1.0 means 1 standard deviation above the expected value. number_of_steps length of the cone that we are creating starting_value corresponds to the starting price of the instrument Returns ------- PriceSeries expected values """ steps = range(number_of_steps + 1) # a list [0, 1, 2, ... N] # for each day OOS calculate the expected value at different point in time using the _get_expected_value() # function that gives expectation in single point in time. expected_values = list( map( lambda nr_of_days: self.get_expected_value( mu, sigma, starting_value, nr_of_days, number_of_std), steps)) return PricesSeries(index=steps, data=expected_values)
def to_prices(self, initial_price: float = None, suggested_initial_date: Union[datetime, int, float] = None, frequency=None) -> "PricesSeries": if suggested_initial_date is None: suggested_initial_date = self._get_initial_date(frequency) if initial_price is None: initial_price = 1.0 if suggested_initial_date is not datetime: prices_dates = Index([suggested_initial_date]).append( self.index.copy()) # if it is numeric or string based else: prices_dates = DatetimeIndex([suggested_initial_date ]).append(self.index.copy()) prices_values = self._to_prices_values(initial_price) from qf_lib.containers.series.prices_series import PricesSeries return PricesSeries(data=prices_values, index=prices_dates).__finalize__(self)
def import_data(frequency: Frequency, file_path: str): xlsx = ExcelImporter() df = xlsx.import_container(file_path, 'A1', 'J1888', sheet_name="Data", include_index=True, include_column_names=True) weights = xlsx.import_container(file_path, 'M15', 'N23', sheet_name="Data", include_index=True, include_column_names=False) simple_ret_df = DataFrame() for column in df: prices = PricesSeries(df[column]) simple_returns = get_aggregate_returns(prices, frequency) simple_ret_df[column] = simple_returns return simple_ret_df, weights
def _get_underwater_chart(self, series: QFSeries, title="Drawdown", benchmark_series: QFSeries = None, rotate_x_axis: bool = False): underwater_chart = LineChart(start_x=series.index[0], end_x=series.index[-1], log_scale=False, rotate_x_axis=rotate_x_axis) underwater_chart.add_decorator(UnderwaterDecorator(series)) underwater_chart.add_decorator(TitleDecorator(title)) if benchmark_series is not None: legend = LegendDecorator() benchmark_dd = PricesSeries(drawdown_tms(benchmark_series)) benchmark_dd *= -1 benchmark_dd_elem = DataElementDecorator(benchmark_dd, color="black", linewidth=0.5) legend.add_entry(benchmark_dd_elem, "Benchmark DD") underwater_chart.add_decorator(benchmark_dd_elem) underwater_chart.add_decorator(legend) return underwater_chart
def get_portfolio_timeseries(self) -> PricesSeries: """ Returns a timeseries of value of the portfolio expressed in currency units """ portfolio_timeseries = PricesSeries(data=self.portfolio_values, index=self.dates) return portfolio_timeseries
class TestSeries(TestCase): def setUp(self): return_dates = pd.date_range('2015-01-01', periods=20, freq='D') test_returns = [0.01, 0.02, 0.03, 0.02, 0.01, 0, -0.01, -0.02, 0.01, 0.03, 0.05, 0.04, 0.03, 0.02, 0.01, 0, 0.01, 0.03, 0.02, 0.04] self.test_simple_returns_tms = SimpleReturnsSeries(data=test_returns, index=return_dates, dtype=float, name='Test Name') prices_values = [100, 101, 103.02, 106.1106, 108.232812, 109.31514012, 109.31514012, 108.2219887188, 106.057548944424, 107.118124433868, 110.331668166884, 115.848251575229, 120.482181638238, 124.096647087385, 126.578580029132, 127.844365829424, 127.844365829424, 129.1228094877180, 132.9964937723500, 135.656423647797, 141.082680593708] prices_dates = pd.date_range('2014-12-31', periods=1, freq='D').append(return_dates) self.test_prices_tms = PricesSeries(data=prices_values, index=prices_dates, name='Test Name') test_log_returns = [0.009950331, 0.019802627, 0.029558802, 0.019802627, 0.009950331, 0, -0.010050336, -0.020202707, 0.009950331, 0.029558802, 0.048790164, 0.039220713, 0.029558802, 0.019802627, 0.009950331, 0, 0.009950331, 0.029558802, 0.019802627, 0.039220713] self.test_log_returns_tms = LogReturnsSeries(data=test_log_returns, index=return_dates, dtype=float, name='Test Name') def test_prices_to_simple_returns(self): actual_returns_tms = self.test_prices_tms.to_simple_returns() expected_returns_tms = self.test_simple_returns_tms assert_series_equal(expected_returns_tms, actual_returns_tms) def test_prices_to_log_returns(self): actual_log_returns_tms = self.test_prices_tms.to_log_returns() expected_log_returns_tms = self.test_log_returns_tms assert_series_equal(expected_log_returns_tms, actual_log_returns_tms) def test_simple_returns_to_prices(self): expected_tms = self.test_prices_tms actual_tms = self.test_simple_returns_tms.to_prices(initial_price=100) assert_series_equal(expected_tms, actual_tms, absolute_tolerance=1e-5) def test_simple_returns_to_log_returns(self): expected_tms = self.test_log_returns_tms actual_tms = self.test_simple_returns_tms.to_log_returns() assert_series_equal(expected_tms, actual_tms) def test_log_returns_to_prices(self): prices_values = array([1, exp(1), exp(2), exp(-1), exp(2)]) prices_dates = pd.date_range('2015-01-01', periods=5) expected = PricesSeries(data=prices_values, index=prices_dates) returns_tms = LogReturnsSeries(data=[1, 1, -3, 3], index=expected.index[1::]) actual = returns_tms.to_prices() assert_series_equal(expected, actual) def test_log_returns_to_simple_returns(self): expected_tms = self.test_simple_returns_tms actual_tms = self.test_log_returns_tms.to_simple_returns() assert_series_equal(expected_tms, actual_tms) def test_infer_interval(self): expected_interval = pd.Timedelta("1 day") expected_frequency = 1 actual_interval, actual_frequency = self.test_log_returns_tms.infer_interval() self.assertEqual(expected_interval, actual_interval) self.assertEqual(expected_frequency, actual_frequency) expected_interval = pd.Timedelta("1 day") expected_frequency = 2 / 3 dates = pd.date_range('2016-04-01', periods=4, freq='b') test_series = QFSeries(data=[0, 0, 0, 0], index=dates) actual_interval, actual_frequency = test_series.infer_interval() self.assertEqual(expected_interval, actual_interval) self.assertEqual(expected_frequency, actual_frequency) def test_cast_series(self): actual_casted_series = cast_series(self.test_simple_returns_tms, PricesSeries) self.assertEqual(PricesSeries, type(actual_casted_series)) self.assertEqual(list(self.test_simple_returns_tms.values), list(actual_casted_series.values)) def test_rolling_window(self): strategy_dates = pd.date_range('2015-01-01', periods=20, freq='D') benchmark_dates = pd.date_range('2015-01-10', periods=20, freq='D') data = [0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01] strategy = SimpleReturnsSeries(data=data, index=strategy_dates) benchmark = SimpleReturnsSeries(data=data, index=benchmark_dates) rolling = strategy.rolling_window_with_benchmark(benchmark, 1, lambda x, y: x.mean() + y.mean()) self.assertEqual(rolling.iloc[0], 0.02) self.assertEqual(rolling.index[0], benchmark_dates[1]) self.assertEqual(rolling.index[9], benchmark_dates[10]) self.assertEqual(len(rolling), 10) # Test with missing values in the middle. strategy_dates = pd.date_range('2015-01-02', periods=3, freq='D') benchmark_dates = pd.DatetimeIndex(['2015-01-01', '2015-01-02', '2015-01-04']) strategy = SimpleReturnsSeries(data=[0.01, 0.50, 0.01], index=strategy_dates) benchmark = SimpleReturnsSeries(data=[0.50, 0.01, 0.01], index=benchmark_dates) rolling = strategy.rolling_window_with_benchmark(benchmark, 1, lambda x, y: x.mean() + y.mean()) self.assertEqual(rolling.iloc[0], 0.02)
def calculate_aggregated_cone(self, nr_of_days_to_evaluate: int, is_end_date: datetime, number_of_std: float) -> QFDataFrame: """ Evaluates many simple cones and saves the end values of every individual simple cone. While using a simple cone (e.g. LineChart with Cone decorator) the results of the evaluation may be very different depending on the starting point. To be immune to this, calculate_aggregated_cone plots only the ends of simple cones which start at 1 period, 2 periods, ..., n periods before the end of the series. The period length depends on the frequency of the data provided for the chart. If it has daily frequency, then the length of one period will be 1 day. Parameters ---------- nr_of_days_to_evaluate max number of days in the past, from when all the cones are evaluated is_end_date the end od in-sample date. Makes sure that in-sample doesn't move with the cone. number_of_std corresponds to the randomness of the stochastic process. reflects number of standard deviations to get expected values for. For example 1.0 means 1 standard deviation above the expected value. Returns ------- QFDataFrame contains values corresponding to Strategy, Mean and Std. Values are indexed by number of days from which given cone was evaluated """ nr_of_data_points = nr_of_days_to_evaluate + 1 # there is a point for 0 days at the beginning of the cone # if nr_of_days_to_evaluate is too large and goes into In-Sample, we need to reduce the value is_end_date_int_index = self.series.index.get_loc(is_end_date, method='pad') first_cone_index = len(self.log_returns_tms) - nr_of_data_points if first_cone_index < is_end_date_int_index: first_cone_index = is_end_date_int_index nr_of_data_points = len(self.log_returns_tms) - first_cone_index strategy_values = np.empty(nr_of_data_points) expected_values = np.empty(nr_of_data_points) mean_return, sigma = self._get_is_statistics(self.log_returns_tms, is_end_date) for i in range(nr_of_data_points): cone_start_idx = first_cone_index + i + 1 # calculate total return of the strategy oos_log_returns = self.log_returns_tms[cone_start_idx:] total_strategy_return = exp( oos_log_returns.sum()) # 1 + percentage return # calculate expectation number_of_steps = len(oos_log_returns) starting_price = 1 # we take 1 as a base value total_expected_return = self.get_expected_value( mean_return, sigma, starting_price, number_of_steps, number_of_std) # writing to the array starting from the last array element and then moving towards the first one strategy_values[-i - 1] = total_strategy_return expected_values[-i - 1] = total_expected_return index = Int64Index(range(0, nr_of_data_points)) strategy_values_tms = PricesSeries(index=index, data=strategy_values) expected_tms = QFSeries(index=index, data=expected_values) return QFDataFrame({ 'Strategy': strategy_values_tms, 'Expectation': expected_tms, })
def volatility(window): return get_volatility(PricesSeries(window), freq)
def tot_return(window): return PricesSeries(window).total_cumulative_return()
def volatility(window): return get_volatility(PricesSeries(window), Frequency.DAILY)
def test_total_cumulative_return(self): actual_result = self.test_prices_df.total_cumulative_return() expected_result = PricesSeries(index=self.test_prices_df.columns, data=[4.0, 4.0, 4.0, 4.0, 4.0]) assert_series_equal(expected_result, actual_result) self.assertEqual(dtype("float64"), actual_result.dtypes)
def volatility(window): return get_volatility(PricesSeries(window), self.frequency, annualise=True)
def volatility(window): return get_volatility(PricesSeries(window), Frequency.DAILY, annualise=True)