def test_portfolio_overrides(mocker): swap_1 = IRSwap("Pay", "5y", "EUR", fixed_rate=-0.005, name="5y") swap_2 = IRSwap("Pay", "10y", "EUR", fixed_rate=-0.005, name="10y") swap_3 = IRSwap("Pay", "5y", "GBP", fixed_rate=-0.005, name="5y") swap_4 = IRSwap("Pay", "10y", "GBP", fixed_rate=-0.005, name="10y") eur_port = Portfolio([swap_1, swap_2], name="EUR") gbp_port = Portfolio([swap_3, swap_4], name="GBP") # override instruments after portfolio construction for idx in range(len(eur_port)): eur_port[idx].fixed_rate = eur_port[idx].fixed_rate - 0.0005 assert eur_port[swap_1] is not None with MockCalc(mocker): # override instruments after portfolio construction and resolution gbp_port.resolve() for idx in range(len(gbp_port)): gbp_port[idx].notional_amount = gbp_port[idx].notional_amount - 1 with PricingContext(dt.date(2020, 1, 14)): r1 = eur_port.calc(risk.Price) r2 = eur_port.calc((risk.Price, risk.DollarPrice)) r3 = gbp_port.calc(risk.Price) r4 = gbp_port.calc((risk.DollarPrice, risk.Price)) assert gbp_port[swap_3] is not None assert r1[eur_port[0]] is not None assert r1['5y'] is not None assert r1.to_frame() is not None assert r2[eur_port[0]] is not None assert r2[risk.Price][0] is not None assert r2[0][risk.Price] is not None assert r3[gbp_port[0]] is not None assert r3.to_frame() is not None assert r4[gbp_port[0]] is not None assert r4[risk.DollarPrice][0] is not None assert r4[0][risk.DollarPrice] is not None
def _raise_order(self, state: Union[date, Iterable[date]], trigger_info: Optional[Union[AddTradeActionInfo, Iterable[AddTradeActionInfo]]] = None): with PricingContext(is_batch=True, show_progress=True): state_list = make_list(state) orders = {} if trigger_info is None or isinstance(trigger_info, AddTradeActionInfo): trigger_info = [trigger_info for _ in range(len(state_list))] for s, ti in zip_longest(state_list, trigger_info): active_portfolio = self.action.dated_priceables.get(s) or self.action.priceables with PricingContext(pricing_date=s): orders[s] = (Portfolio(active_portfolio).resolve(in_place=False), ti) final_orders = {} for d, p in orders.items(): new_port = [] for t in p[0].result(): t.name = f'{t.name}_{d}' new_port.append(t) new_port = Portfolio(new_port) final_orders[d] = new_port.scale(None if p[1] is None else p[1].scaling, in_place=False) return final_orders
def test_portfolio(mocker): with MockCalc(mocker): with PricingContext(pricing_date=dt.date(2020, 10, 15)): swap1 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.001, name='swap_10y@10bp') swap2 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.002, name='swap_10y@20bp') swap3 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.003, name='swap_10y@30bp') portfolio = Portfolio((swap1, swap2, swap3)) prices: PortfolioRiskResult = portfolio.dollar_price() result = portfolio.calc((risk.DollarPrice, risk.IRDelta)) assert tuple(sorted(map(lambda x: round(x, 0), prices))) == (4439478.0, 5423405.0, 6407332.0) assert round(prices.aggregate(), 2) == 16270214.48 assert round(prices[0], 0) == 6407332.0 assert round(prices[swap2], 0) == 5423405.0 assert round(prices['swap_10y@30bp'], 0) == 4439478.0 assert tuple(map(lambda x: round(x, 0), result[risk.DollarPrice])) == (6407332.0, 5423405.0, 4439478.0) assert round(result[risk.DollarPrice].aggregate(), 0) == 16270214.0 assert round(result[risk.DollarPrice]['swap_10y@30bp'], 0) == 4439478.0 assert round(result[risk.DollarPrice]['swap_10y@30bp'], 0) == round(result['swap_10y@30bp'][risk.DollarPrice], 0) assert round(result[risk.IRDelta].aggregate().value.sum(), 0) == 278977.0 prices_only = result[risk.DollarPrice] assert tuple(map(lambda x: round(x, 0), prices)) == tuple(map(lambda x: round(x, 0), prices_only)) swap4 = IRSwap('Pay', '10y', 'USD', fixed_rate=-0.001, name='swap_10y@-10bp') portfolio.append(swap4) assert len(portfolio.instruments) == 4 extracted_swap = portfolio.pop('swap_10y@20bp') assert extracted_swap == swap2 assert len(portfolio.instruments) == 3 swap_dict = {'swap_5': swap1, 'swap_6': swap2, 'swap_7': swap3} portfolio = Portfolio(swap_dict) assert len(portfolio) == 3
def __getitem__(self, item): futures = [] if isinstance(item, RiskMeasure) or (isinstance(item, list) and isinstance(item[0], RiskMeasure)): '''Slicing a list of risk measures''' if isinstance(item, list): if any([it not in self.risk_measures for it in item]): raise ValueError('{} not computed'.format(item)) else: if item not in self.risk_measures: raise ValueError('{} not computed'.format(item)) if len(self.risk_measures) == 1: return self elif isinstance(item, list): return PortfolioRiskResult(self.__portfolio, tuple([it for it in item]), self.futures) else: return PortfolioRiskResult(self.__portfolio, (item, ), self.futures) elif isinstance(item, list) and isinstance(item[0], InstrumentBase): '''Slicing a list of instruments''' from gs_quant.markets.portfolio import Portfolio portfolio = Portfolio(self.__portfolio[item]) for idx, result in enumerate(self): instr = self.portfolio[idx] futures.extend( [PricingFuture(result) for it in item if instr == it]) return PortfolioRiskResult(portfolio, self.risk_measures, futures) elif isinstance(item, dt.date): for result in self: if isinstance( result, (MultipleRiskMeasureResult, PortfolioRiskResult)): futures.append(PricingFuture(result[item])) elif isinstance(result, (DataFrameWithInfo, SeriesWithInfo)): futures.append(PricingFuture(_value_for_date(result, item))) else: raise RuntimeError( 'Can only index by date on historical results') return PortfolioRiskResult(self.__portfolio, self.risk_measures, futures) else: return self.__results(items=item)
def apply_action(self, state: Union[date, Iterable[date]], backtest: BackTest): with HistoricalPricingContext(dates=make_list(state)): backtest.calc_calls += 1 backtest.calculations += len(make_list(state)) f = Portfolio(make_list( self.action.priceable)).resolve(in_place=False) for create_date, portfolio in f.result().items(): active_dates = [ s for s in backtest.states if get_final_date(portfolio.instruments[0], create_date, self. action.trade_duration) >= s >= create_date ] if len(active_dates): for t in portfolio: t.name = f'{t.name}_{create_date.strftime("%Y-%m-%d")}' backtest.scaling_portfolios[create_date].append( ScalingPortfolio(trade=portfolio.instruments[0], dates=active_dates, risk=self.action.risk)) return backtest
def test_backtothefuture_pricing(mocker): with MockCalc(mocker): swap1 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.01, name='swap1') swap2 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.02, name='swap2') swap3 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.03, name='swap3') portfolio = Portfolio((swap1, swap2, swap3)) pricing_date = dt.date(2021, 2, 10) with PricingContext(pricing_date=pricing_date): with BackToTheFuturePricingContext(dates=business_day_offset(pricing_date, [-1, 0, 1], roll='forward')) as hpc: risk_key = hpc._PricingContext__risk_key(risk.DollarPrice, swap1.provider) results = portfolio.calc(risk.DollarPrice) expected = risk.SeriesWithInfo( pd.Series( data=[-22711963.80864744, -22655907.930484552, -21582551.58922608], index=business_day_offset(pricing_date, [-1, 0, 1], roll='forward') ), risk_key=historical_risk_key(risk_key), ) actual = results[risk.DollarPrice].aggregate() assert actual.equals(expected)
def _raise_order(self, state: Union[date, Iterable[date]], trigger_info: Optional[Union[AddTradeActionInfo, Iterable[AddTradeActionInfo]]] = None): with PricingContext(is_batch=True): state_list = make_list(state) orders = {} if trigger_info is None or isinstance(trigger_info, AddTradeActionInfo): trigger_info = [trigger_info for _ in range(len(state_list))] for s, ti in zip_longest(state_list, trigger_info): active_portfolio = self.action.dated_priceables.get(s) or self.action.priceables with PricingContext(pricing_date=s): orders[s] = (Portfolio(active_portfolio).resolve(in_place=False), ti) orders = {k: v[0].result().scale(None if v[1] is None else v[1].scaling, in_place=False) for k, v in orders.items()} return orders
def __add__(self, other): if isinstance(other, (int, float)): return self.__op(op.add, other) elif isinstance(other, MultipleRiskMeasureResult): if not _risk_keys_compatible(self, other): raise ValueError( 'Results must have matching scenario and location') instruments_equal = self.__instrument == other.__instrument self_dt = [list(self.values())[0].risk_key.date] if len( self.dates) == 0 else self.dates other_dt = [list(other.values())[0].risk_key.date] if len( other.dates) == 0 else other.dates dates_overlap = not set(self_dt).isdisjoint(other_dt) if not set(self.keys()).isdisjoint( other.keys()) and instruments_equal and dates_overlap: raise ValueError( 'Results overlap on risk measures, instruments or dates') all_keys = set(chain(self.keys(), other.keys())) if not instruments_equal: from gs_quant.markets.portfolio import Portfolio return PortfolioRiskResult( Portfolio((self.__instrument, other.__instrument)), all_keys, tuple( MultipleRiskMeasureFuture( r.__instrument, { k: PricingFuture(r[k]) if k in r else None for k in all_keys }) for r in (self, other))) else: results = {} for result in (self, other): for key in all_keys: if key in result: results[key] = _compose( results[key], result[key]) if key in results else result[key] return MultipleRiskMeasureResult(self.__instrument, results) else: raise ValueError( 'Can only add instances of MultipleRiskMeasureResult or int, float' )
def apply_action(self, state: Union[datetime.date, Iterable[datetime.date]], backtest: BackTest): with PricingContext(is_batch=True): f = {} for s in state: active_portfolio = self._dated_priceables.get(s) or self._priceables with PricingContext(pricing_date=s): f[s] = Portfolio(active_portfolio).resolve(in_place=False) for s in backtest.states: pos = [] for create_date, portfolio in f.items(): pos += [inst for inst in portfolio.result().instruments if get_final_date(inst, create_date, self.trade_duration) >= s >= create_date] backtest.portfolio_dict[s].append(pos) return backtest
def test_nested_portfolios(mocker): swap1 = IRSwap('Pay', '10y', 'USD', name='USD-swap') swap2 = IRSwap('Pay', '10y', 'EUR', name='EUR-swap') swap3 = IRSwap('Pay', '10y', 'GBP', name='GBP-swap') swap4 = IRSwap('Pay', '10y', 'JPY', name='JPY-swap') swap5 = IRSwap('Pay', '10y', 'HUF', name='HUF-swap') swap6 = IRSwap('Pay', '10y', 'CHF', name='CHF-swap') portfolio2_1 = Portfolio((swap1, swap2, swap3), name='portfolio2_1') portfolio2_2 = Portfolio((swap1, swap2, swap3), name='portfolio2_2') portfolio1_1 = Portfolio((swap4, portfolio2_1), name='portfolio1_1') portfolio1_2 = Portfolio((swap5, portfolio2_2), name='USD-swap') portfolio = Portfolio((swap6, portfolio1_1, portfolio1_2), name='portfolio') assert portfolio.paths('USD-swap') == (PortfolioPath(2), PortfolioPath((1, 1, 0)), PortfolioPath((2, 1, 0)))
def apply_action(self, state: datetime.date, backtest: BackTest): with PricingContext(pricing_date=state): f = self._priceable.resolve(in_place=False) hedge_delta = self._priceable.calc(self.risk) hedge = f.result() ratio = backtest.results[state][self.risk].aggregate() / hedge_delta.result() hedge = scale_trade(hedge, ratio) active_dates = [pricing_date for pricing_date in backtest.states if get_final_date(hedge, state, self.trade_duration) >= pricing_date >= state] with HistoricalPricingContext(dates=active_dates): backtest.calc_calls += 1 backtest.calculations += len(active_dates) * len(backtest.risks) hedge_res = Portfolio(hedge).calc(backtest.risks) for pricing_date in active_dates: results = hedge_res[pricing_date] backtest.add_results(pricing_date, results)
def test_nested_portfolio(mocker): with MockCalc(mocker): cols1, res1, frame1 = get_attributes(port1, (risk.DollarPrice, risk.Price)) cols2, res2, frame2 = get_attributes(port, (risk.DollarPrice, risk.Price)) _, swap1_6_res, frame3 = get_attributes( Portfolio((swap_1, swap_6), name='swap_1_6'), risk.DollarPrice) _, res4, frame4 = get_attributes( port1, (risk.DollarPrice, risk.Price, risk.Theta)) price_values_test(res1, frame1) price_values_test(res2, frame2) dollar_eur_frame1 = frame1[(frame1['portfolio_name_0'] == 'EUR') & ( frame1['risk_measure'] == risk.DollarPrice)]['value'].values dollar_eur_frame2 = frame2[(frame2['portfolio_name_0'] == 'EURGBP') & (frame2['portfolio_name_1'] == 'EUR') & (frame2['risk_measure'] == risk.DollarPrice)]['value'].values default_pivot_table_test(res1) default_pivot_table_test(res2) # test slicing # slice multiple instruments slice_res2 = res1[[swap_1, swap_6 ]][risk.DollarPrice].to_frame(None, None, None)['value'].values assert all( slice_res2 == swap1_6_res.to_frame(None, None, None)['value'].values) sub_frame1 = res1[risk.DollarPrice][swap_1].to_frame() assert sub_frame1 == dollar_eur_frame1[0] assert sub_frame1 == dollar_eur_frame2[0] sub_frame2 = res2[eur_port][risk.DollarPrice].to_frame( None, None, None)['value'].values assert all(dollar_eur_frame1 == sub_frame2) assert all(dollar_eur_frame2 == sub_frame2) # slice multiple risk measures sub_res4 = res4[[risk.Price, risk.DollarPrice]] assert all(sub_res4.to_frame() == res1.to_frame())
def __add__(self, other): if isinstance(other, (int, float)): return self.__op(op.add, other) elif isinstance(other, MultipleRiskMeasureResult): if sorted(self.keys()) == sorted(other.keys()): from gs_quant.markets.portfolio import Portfolio return PortfolioRiskResult( Portfolio((self.__instrument, other.__instrument)), self.keys(), tuple(MultipleRiskMeasureFuture(r.__instrument, dict((k, PricingFuture(v)) for k, v in r)) for r in (self, other)) ) elif set(self.keys()).isdisjoint(other.keys()) and self.__instrument == other.__instrument: if set(self.keys()).intersection(other.keys()): raise ValueError('Keys must be disjoint') return MultipleRiskMeasureResult(self.__instrument, chain(self.items(), other.items())) else: raise ValueError('Can only add where risk_measures match or instrument identical &' + 'risk_measures disjoint') else: raise ValueError('Can only add instances of MultipleRiskMeasureResult or int, float')
def test_pull_from_marquee(mocker): portfolio_search_results = { 'results': [ MQPortfolio(id='portfolio_id', name='Test Portfolio', currency='USD', entitlements=Entitlements(admin=('guid:12345', ))) ] } mocker.patch.object(GsSession.current, '_get', return_value=portfolio_search_results) mocker.patch.object(User, 'get_many', return_value=([ User(user_id='12345', name='Fake User', email='*****@*****.**', company='Goldman Sachs') ])) portfolio = Portfolio.get(name='Test Portfolio') assert portfolio.id == 'portfolio_id' return portfolio
def test_historical_pricing(mocker): set_session() dollar_price_ir_delta_values = [[[{ '$type': 'Risk', 'val': 0.01 }, { '$type': 'Risk', 'val': 0.011 }, { '$type': 'Risk', 'val': 0.012 }], [{ '$type': 'Risk', 'val': 0.02 }, { '$type': 'Risk', 'val': 0.021 }, { '$type': 'Risk', 'val': 0.022 }], [{ '$type': 'Risk', 'val': 0.03 }, { '$type': 'Risk', 'val': 0.031 }, { '$type': 'Risk', 'val': 0.032 }]], [[{ '$type': 'RiskVector', 'asset': [0.01, 0.015], 'points': [{ 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '1y' }, { 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '2y' }] }, { '$type': 'RiskVector', 'asset': [0.011, 0.0151], 'points': [{ 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '1y' }, { 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '2y' }] }, { '$type': 'RiskVector', 'asset': [0.012, 0.0152], 'points': [{ 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '1y' }, { 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '2y' }] }], [{ '$type': 'RiskVector', 'asset': [0.02, 0.025], 'points': [{ 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '1y' }, { 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '2y' }] }, { '$type': 'RiskVector', 'asset': [0.021, 0.0251], 'points': [{ 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '1y' }, { 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '2y' }] }, { '$type': 'RiskVector', 'asset': [0.022, 0.0252], 'points': [{ 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '1y' }, { 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '2y' }] }], [{ '$type': 'RiskVector', 'asset': [0.03, 0.035], 'points': [{ 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '1y' }, { 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '2y' }] }, { '$type': 'RiskVector', 'asset': [0.031, 0.0351], 'points': [{ 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '1y' }, { 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '2y' }] }, { '$type': 'RiskVector', 'asset': [0.032, 0.0352], 'points': [{ 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '1y' }, { 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '2y' }] }]]] mocker.return_value = [dollar_price_ir_delta_values] swap1 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.01, name='swap1') swap2 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.02, name='swap2') swap3 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.03, name='swap3') portfolio = Portfolio((swap1, swap2, swap3)) with HistoricalPricingContext(dates=(dt.date(2019, 10, 7), dt.date(2019, 10, 8), dt.date(2019, 10, 9))) as hpc: risk_key = hpc._PricingContext__risk_key(risk.DollarPrice, swap1.provider()) results = portfolio.calc((risk.DollarPrice, risk.IRDelta)) expected = risk.SeriesWithInfo( pd.Series(data=[0.06, 0.063, 0.066], index=[ dt.date(2019, 10, 7), dt.date(2019, 10, 8), dt.date(2019, 10, 9) ]), risk_key=risk_key.base, ) actual = results[risk.DollarPrice].aggregate() assert actual.equals(expected)
from gs_quant.common import PayReceive, Currency # import constants from gs_quant.instrument import IRSwaption # import instruments from gs_quant.markets.portfolio import Portfolio from gs_quant.session import Environment, GsSession # import sessions client_id = None # Supply your application id client_secret = None # Supply your client secret scopes = ('run_analytics', ) GsSession.use(Environment.PROD, client_id, client_secret, scopes) swaption1 = IRSwaption(PayReceive.Pay, '5y', Currency.EUR, expiration_date='3m', name='EUR-3m5y') swaption2 = IRSwaption(PayReceive.Pay, '5y', Currency.EUR, expiration_date='6m', name='EUR-6m5y') portfolio = Portfolio((swaption1, swaption2)) # price of individual instrument price_result = portfolio.price() print(price_result['EUR-3m5y']) # price for entire portfolio price_agg = price_result.aggregate() print(price_agg)
def run_backtest(self, strategy, start=None, end=None, frequency='BM', window=None, states=None, risks=Price, show_progress=True): dates = pd.date_range(start=start, end=end, freq=frequency).date.tolist() risks = make_list(risks) + strategy.risks backtest = BackTest(strategy, dates, risks) if len(strategy.initial_portfolio): init_port = Portfolio(strategy.initial_portfolio) with PricingContext(pricing_date=dates[0]): init_port.resolve() for d in dates: backtest.portfolio_dict[d].append(init_port.instruments) for trigger in strategy.triggers: if trigger.calc_type != CalcType.path_dependent: triggered_dates = [ d for d in dates if trigger.has_triggered(d, backtest) ] for action in trigger.actions: if action.calc_type != CalcType.path_dependent: self.get_action_handler(action).apply_action( triggered_dates, backtest) with PricingContext(is_batch=True, show_progress=show_progress): for day, portfolio in backtest.portfolio_dict.items(): with PricingContext(day): backtest.calc_calls += 1 backtest.calculations += len(portfolio) * len(risks) backtest.add_results(day, portfolio.calc(tuple(risks))) # semi path dependent initial calc for _, scaling_list in backtest.scaling_portfolios.items(): for p in scaling_list: with HistoricalPricingContext(dates=p.dates): backtest.calc_calls += 1 backtest.calculations += len(p.dates) * len(risks) p.results = Portfolio([p.trade]).calc(tuple(risks)) for d in dates: # semi path dependent scaling if d in backtest.scaling_portfolios: initial_portfolio = backtest.scaling_portfolios[d][0] scale_date = initial_portfolio.dates[0] current_risk = backtest.results[scale_date][ initial_portfolio.risk].aggregate() hedge_risk = initial_portfolio.results[scale_date][ initial_portfolio.risk][0] scaling_factor = current_risk / hedge_risk for p in backtest.scaling_portfolios[d]: new_notional = p.trade.notional_amount * -scaling_factor scaled_trade = p.trade.as_dict() scaled_trade['notional_amount'] = new_notional scaled_trade = Instrument.from_dict(scaled_trade) for day in p.dates: backtest.add_results(day, p.results[day] * -scaling_factor) backtest.portfolio_dict[day] += Portfolio(scaled_trade) # path dependent for trigger in strategy.triggers: if trigger.calc_type == CalcType.path_dependent: if trigger.has_triggered(d, backtest): for action in trigger.actions: self.get_action_handler(action).apply_action( d, backtest) else: for action in trigger.actions: if action.calc_type == CalcType.path_dependent: if trigger.has_triggered(d, backtest): self.get_action_handler(action).apply_action( d, backtest) return backtest
# Access instruments within a portfolio from gs_quant.common import PayReceive, Currency # import constants from gs_quant.instrument import IRSwaption # import instruments from gs_quant.markets.portfolio import Portfolio from gs_quant.session import Environment, GsSession # import sessions client_id = None # Supply your application id client_secret = None # Supply your client secret scopes = ('run_analytics',) GsSession.use(Environment.PROD, client_id, client_secret, scopes) swaption1 = IRSwaption(PayReceive.Pay, '5y', Currency.EUR, expiration_date='3m', name='EUR-3m5y') swaption2 = IRSwaption(PayReceive.Pay, '5y', Currency.EUR, expiration_date='6m', name='EUR-6m5y') portfolio = Portfolio((swaption1, swaption2)) print(portfolio[0]) # index print(portfolio[swaption2]) # instrument object print(portfolio['EUR-6m5y']) # instrument name
cols = [col for col in frame.columns] return cols, res, frame else: return res swap_1 = IRSwap("Pay", "5y", "EUR", fixed_rate=-0.005, name="5y") swap_2 = IRSwap("Pay", "10y", "EUR", fixed_rate=-0.005, name="10y") swap_3 = IRSwap("Pay", "5y", "USD", fixed_rate=-0.005, name="5y") swap_4 = IRSwap("Pay", "10y", "USD", fixed_rate=-0.005, name="10y") swap_5 = IRSwap("Pay", "5y", "GBP", fixed_rate=-0.005, name="5y") swap_6 = IRSwap("Pay", "10y", "GBP", fixed_rate=-0.005, name="10y") swap_7 = IRSwap("Pay", "5y", "JPY", fixed_rate=-0.005, name="5y") swap_8 = IRSwap("Pay", "10y", "JPY", fixed_rate=-0.005, name="10y") eur_port = Portfolio([swap_1, swap_2], name="EUR") usd_port = Portfolio([swap_3, swap_4], name="USD") gbp_port = Portfolio([swap_5, swap_6], name="GBP") jpy_port = Portfolio([swap_7, swap_8], name='JPY') port1 = Portfolio([eur_port, gbp_port], name='EURGBP') port2 = Portfolio([jpy_port, usd_port], name='USDJPY') port = Portfolio([port1, port2]) swaption_port = Portfolio([ IRSwaption("Receive", '5y', 'USD', expiration_date='2m', strike='atm', name='Swaption1'), IRSwaption("Receive", '10y',
""" `Portfolio` supports the same methods as `Instrument` including `resolve()`, `calc()`, and `price()` Resolving the portfolio resolves each individual instrument within the portfolio. """ from gs_quant.common import PayReceive, Currency # import constants from gs_quant.instrument import IRSwaption # import instruments from gs_quant.markets.portfolio import Portfolio from gs_quant.session import Environment, GsSession # import sessions client_id = None # Supply your application id client_secret = None # Supply your client secret scopes = ('run_analytics', ) GsSession.use(Environment.PROD, client_id, client_secret, scopes) swaption1 = IRSwaption(PayReceive.Pay, '5y', Currency.EUR, expiration_date='3m', name='EUR-3m5y') swaption2 = IRSwaption(PayReceive.Pay, '5y', Currency.EUR, expiration_date='6m', name='EUR-6m5y') portfolio = Portfolio((swaption1, swaption2)) portfolio.resolve() print(portfolio.as_dict())
def apply_action( self, state: Union[date, Iterable[date]], backtest: BackTest, trigger_info: Optional[Union[ExitTradeActionInfo, Iterable[ExitTradeActionInfo]]] = None): for s in make_list(state): trades_to_remove = [] fut_dates = list( filter(lambda d: d >= s and type(d) is dt.date, backtest.states)) for port_date in fut_dates: pos_fut = list( backtest.portfolio_dict[port_date].all_instruments) # We expect tradable names to be defined as <ActionName>_<TradeName>_<TradeDate> if self.action.priceable_names: # List of trade names provided -> TradeDate <= exit trigger date and TradeName is present in list indexes_to_remove = [ i for i, x in enumerate(pos_fut) if dt.datetime.strptime( x.name.split('_')[-1], '%Y-%m-%d').date() <= s and x.name.split('_')[-2] in self.action.priceable_names ] else: # List of trade names not provided -> TradeDate <= exit trigger date indexes_to_remove = [ i for i, x in enumerate(pos_fut) if dt.datetime.strptime( x.name.split('_')[-1], '%Y-%m-%d').date() <= s ] for index in sorted(indexes_to_remove, reverse=True): # Get list of trades' names that have been removed to check for their future cash flow date if pos_fut[index].name not in trades_to_remove: trades_to_remove.append(pos_fut[index].name) del pos_fut[index] backtest.portfolio_dict[port_date] = Portfolio(tuple(pos_fut)) for cp_date, cp_list in list(backtest.cash_payments.items()): if cp_date > s: indexes_to_remove = [ i for i, cp in enumerate(cp_list) if cp.trade.name in trades_to_remove ] for index in sorted(indexes_to_remove, reverse=True): cp = cp_list[index] prev_pos = [ i for i, x in enumerate(backtest.cash_payments[s]) if cp.trade.name == x.trade.name ] # If trade already exists in exit trigger date cash payments, net out the position if prev_pos: backtest.cash_payments[s][ prev_pos[0]].direction += cp.direction else: cp.effective_date = s backtest.cash_payments[s].append(cp) del backtest.cash_payments[cp_date][index] if not backtest.cash_payments[cp_date]: del backtest.cash_payments[cp_date] return backtest
def test_portfolio(mocker): set_session() dollar_price_values = [[[{ 'date': '2019-10-07', 'value': 0.01 }], [{ 'date': '2019-10-07', 'value': 0.02 }], [{ 'date': '2019-10-07', 'value': 0.03 }]]] dollar_price_ir_delta_values = [[[{ 'date': '2019-10-07', 'value': 0.01 }], [{ 'date': '2019-10-07', 'value': 0.02 }], [{ 'date': '2019-10-07', 'value': 0.03 }]], [[{ 'date': '2019-10-07', 'marketDataType': 'IR', 'assetId': 'USD', 'pointClass': 'Swap', 'point': '1y', 'value': 0.01 }, { 'date': '2019-10-07', 'marketDataType': 'IR', 'assetId': 'USD', 'pointClass': 'Swap', 'point': '2y', 'value': 0.015 }], [{ 'date': '2019-10-07', 'marketDataType': 'IR', 'assetId': 'USD', 'pointClass': 'Swap', 'point': '1y', 'value': 0.02 }, { 'date': '2019-10-07', 'marketDataType': 'IR', 'assetId': 'USD', 'pointClass': 'Swap', 'point': '2y', 'value': 0.025 }], [{ 'date': '2019-10-07', 'marketDataType': 'IR', 'assetId': 'USD', 'pointClass': 'Swap', 'point': '1y', 'value': 0.03 }, { 'date': '2019-10-07', 'marketDataType': 'IR', 'assetId': 'USD', 'pointClass': 'Swap', 'point': '2y', 'value': 0.035 }]]] swap1 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.01, name='swap1') swap2 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.02, name='swap2') swap3 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.03, name='swap3') portfolio = Portfolio((swap1, swap2, swap3)) mocker.return_value = dollar_price_values prices: PortfolioRiskResult = portfolio.dollar_price() assert tuple(sorted(prices)) == (0.01, 0.02, 0.03) assert round(prices.aggregate(), 2) == 0.06 assert prices[0] == 0.01 assert prices[swap2] == 0.02 assert prices['swap3'] == 0.03 mocker.return_value = dollar_price_ir_delta_values result = portfolio.calc((risk.DollarPrice, risk.IRDelta)) assert tuple(result[risk.DollarPrice]) == (0.01, 0.02, 0.03) assert result[risk.DollarPrice].aggregate() == 0.06 assert result[risk.DollarPrice]['swap3'] == 0.03 assert result[risk.DollarPrice]['swap3'] == result['swap3'][ risk.DollarPrice] expected = risk.aggregate_risk( [pd.DataFrame(v) for v in dollar_price_ir_delta_values[1]]) assert result[risk.IRDelta].aggregate().equals(expected) prices_only = result[risk.DollarPrice] assert tuple(prices) == tuple(prices_only)
def test_historical_pricing(mocker): set_session() dollar_price_ir_delta_values = [[[{ 'date': '2019-10-07', 'value': 0.01 }, { 'date': '2019-10-08', 'value': 0.011 }, { 'date': '2019-10-09', 'value': 0.012 }], [{ 'date': '2019-10-07', 'value': 0.02 }, { 'date': '2019-10-08', 'value': 0.021 }, { 'date': '2019-10-09', 'value': 0.022 }], [{ 'date': '2019-10-07', 'value': 0.03 }, { 'date': '2019-10-08', 'value': 0.031 }, { 'date': '2019-10-09', 'value': 0.032 }]], [[{ 'date': '2019-10-07', 'marketDataType': 'IR', 'assetId': 'USD', 'pointClass': 'Swap', 'point': '1y', 'value': 0.01 }, { 'date': '2019-10-07', 'marketDataType': 'IR', 'assetId': 'USD', 'pointClass': 'Swap', 'point': '2y', 'value': 0.015 }, { 'date': '2019-10-08', 'marketDataType': 'IR', 'assetId': 'USD', 'pointClass': 'Swap', 'point': '1y', 'value': 0.011 }, { 'date': '2019-10-08', 'marketDataType': 'IR', 'assetId': 'USD', 'pointClass': 'Swap', 'point': '2y', 'value': 0.0151 }, { 'date': '2019-10-09', 'marketDataType': 'IR', 'assetId': 'USD', 'pointClass': 'Swap', 'point': '1y', 'value': 0.012 }, { 'date': '2019-10-09', 'marketDataType': 'IR', 'assetId': 'USD', 'pointClass': 'Swap', 'point': '2y', 'value': 0.0152 }], [{ 'date': '2019-10-07', 'marketDataType': 'IR', 'assetId': 'USD', 'pointClass': 'Swap', 'point': '1y', 'value': 0.02 }, { 'date': '2019-10-07', 'marketDataType': 'IR', 'assetId': 'USD', 'pointClass': 'Swap', 'point': '2y', 'value': 0.025 }, { 'date': '2019-10-08', 'marketDataType': 'IR', 'assetId': 'USD', 'pointClass': 'Swap', 'point': '1y', 'value': 0.021 }, { 'date': '2019-10-08', 'marketDataType': 'IR', 'assetId': 'USD', 'pointClass': 'Swap', 'point': '2y', 'value': 0.0251 }, { 'date': '2019-10-09', 'marketDataType': 'IR', 'assetId': 'USD', 'pointClass': 'Swap', 'point': '1y', 'value': 0.022 }, { 'date': '2019-10-09', 'marketDataType': 'IR', 'assetId': 'USD', 'pointClass': 'Swap', 'point': '2y', 'value': 0.0252 }], [{ 'date': '2019-10-07', 'marketDataType': 'IR', 'assetId': 'USD', 'pointClass': 'Swap', 'point': '1y', 'value': 0.03 }, { 'date': '2019-10-07', 'marketDataType': 'IR', 'assetId': 'USD', 'pointClass': 'Swap', 'point': '2y', 'value': 0.035 }, { 'date': '2019-10-08', 'marketDataType': 'IR', 'assetId': 'USD', 'pointClass': 'Swap', 'point': '1y', 'value': 0.031 }, { 'date': '2019-10-08', 'marketDataType': 'IR', 'assetId': 'USD', 'pointClass': 'Swap', 'point': '2y', 'value': 0.0351 }, { 'date': '2019-10-09', 'marketDataType': 'IR', 'assetId': 'USD', 'pointClass': 'Swap', 'point': '1y', 'value': 0.032 }, { 'date': '2019-10-09', 'marketDataType': 'IR', 'assetId': 'USD', 'pointClass': 'Swap', 'point': '2y', 'value': 0.0352 }]]] mocker.return_value = dollar_price_ir_delta_values swap1 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.01, name='swap1') swap2 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.02, name='swap2') swap3 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.03, name='swap3') portfolio = Portfolio((swap1, swap2, swap3)) with HistoricalPricingContext(3): results = portfolio.calc((risk.DollarPrice, risk.IRDelta)) expected = pd.Series(data=[0.06, 0.063, 0.066], index=[ dt.date(2019, 10, 7), dt.date(2019, 10, 8), dt.date(2019, 10, 9) ]) assert results[risk.DollarPrice].aggregate().equals(expected)
def test_portfolio(mocker): set_session() dollar_price_values = [[[{ '$type': 'Risk', 'val': 0.01 }], [{ '$type': 'Risk', 'val': 0.02 }], [{ '$type': 'Risk', 'val': 0.03 }]]] dollar_price_ir_delta_values = [[[{ '$type': 'Risk', 'val': 0.01 }], [{ '$type': 'Risk', 'val': 0.02 }], [{ '$type': 'Risk', 'val': 0.03 }]], [[{ '$type': 'RiskVector', 'asset': [0.01, 0.015], 'points': [{ 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '1y' }, { 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '2y' }] }], [{ '$type': 'RiskVector', 'asset': [0.02, 0.025], 'points': [{ 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '1y' }, { 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '2y' }] }], [{ '$type': 'RiskVector', 'asset': [0.03, 0.035], 'points': [{ 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '1y' }, { 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '2y' }] }]]] swap1 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.01, name='swap1') swap2 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.02, name='swap2') swap3 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.03, name='swap3') portfolio = Portfolio((swap1, swap2, swap3)) mocker.return_value = [dollar_price_values] prices: PortfolioRiskResult = portfolio.dollar_price() assert tuple(sorted(prices)) == (0.01, 0.02, 0.03) assert round(prices.aggregate(), 2) == 0.06 assert prices[0] == 0.01 assert prices[swap2] == 0.02 assert prices['swap3'] == 0.03 mocker.return_value = [dollar_price_ir_delta_values] result = portfolio.calc((risk.DollarPrice, risk.IRDelta)) assert tuple(result[risk.DollarPrice]) == (0.01, 0.02, 0.03) assert result[risk.DollarPrice].aggregate() == 0.06 assert result[risk.DollarPrice]['swap3'] == 0.03 assert result[risk.DollarPrice]['swap3'] == result['swap3'][ risk.DollarPrice] assert result[risk.IRDelta].aggregate().value.sum() == 0.135 prices_only = result[risk.DollarPrice] assert tuple(prices) == tuple(prices_only) swap4 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.01, name='swap4') portfolio.append(swap4) assert len(portfolio.instruments) == 4 extracted_swap = portfolio.pop('swap2') assert extracted_swap == swap2 assert len(portfolio.instruments) == 3 swap_dict = {'swap_5': swap1, 'swap_6': swap2, 'swap_7': swap3} portfolio = Portfolio(swap_dict) assert len(portfolio) == 3
def test_results_with_resolution(): set_session() dollar_price_ir_delta_values = [[[{ '$type': 'Risk', 'val': 0.01 }], [{ '$type': 'Risk', 'val': 0.02 }], [{ '$type': 'Risk', 'val': 0.03 }]], [[{ '$type': 'RiskVector', 'asset': [0.01, 0.015], 'points': [{ 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '1y' }, { 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '2y' }] }], [{ '$type': 'RiskVector', 'asset': [0.02, 0.025], 'points': [{ 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '1y' }, { 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '2y' }] }], [{ '$type': 'RiskVector', 'asset': [0.03, 0.035], 'points': [{ 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '1y' }, { 'type': 'IR', 'asset': 'USD', 'class_': 'Swap', 'point': '2y' }] }]]] swap1 = IRSwap('Pay', '10y', 'USD', name='swap1') swap2 = IRSwap('Pay', '10y', 'GBP', name='swap2') swap3 = IRSwap('Pay', '10y', 'EUR', name='swap3') portfolio = Portfolio((swap1, swap2, swap3)) with mock.patch('gs_quant.api.gs.risk.GsRiskApi._exec') as mocker: mocker.return_value = [dollar_price_ir_delta_values] result = portfolio.calc((risk.DollarPrice, risk.IRDelta)) # Check that we've got results assert result[swap1][risk.DollarPrice] is not None # Now resolve portfolio and assert that we can still get the result resolution_values = [[[{ '$type': 'LegDefinition', 'fixedRate': 0.01 }], [{ '$type': 'LegDefinition', 'fixedRate': 0.007 }], [{ '$type': 'LegDefinition', 'fixedRate': 0.05 }]]] orig_swap1 = swap1.clone() with mock.patch('gs_quant.api.gs.risk.GsRiskApi._exec') as mocker: mocker.return_value = [resolution_values] portfolio.resolve() # Assert that the resolved swap is indeed different and that we can retrieve results by both assert swap1 != orig_swap1 assert result[swap1][risk.DollarPrice] is not None assert result[orig_swap1][risk.DollarPrice] is not None # Now reset the instruments and portfolio swap1 = IRSwap('Pay', '10y', 'USD', name='swap1') swap2 = IRSwap('Pay', '10y', 'GBP', name='swap2') swap3 = IRSwap('Pay', '10y', 'EUR', name='swap3') portfolio = Portfolio((swap1, swap2, swap3, swap1)) with mock.patch('gs_quant.api.gs.risk.GsRiskApi._exec') as mocker: with PricingContext(dt.date(1066, 11, 14)): # Resolve under a different pricing date mocker.return_value = [resolution_values] portfolio.resolve() # Assert that after resolution under a different context, we cannot retrieve the result try: _ = result[swap1][risk.DollarPrice] assert False except KeyError: assert True
def test_backtothefuture_pricing(mocker): set_session() day1 = [[ [{ '$type': 'Risk', 'val': 0.01 }], [{ '$type': 'Risk', 'val': 0.02 }], [{ '$type': 'Risk', 'val': 0.03 }], ]] day2 = [[ [{ '$type': 'Risk', 'val': 0.011 }], [{ '$type': 'Risk', 'val': 0.021 }], [{ '$type': 'Risk', 'val': 0.031 }], ]] day3 = [[ [{ '$type': 'Risk', 'val': 0.012 }], [{ '$type': 'Risk', 'val': 0.022 }], [{ '$type': 'Risk', 'val': 0.032 }], ]] mocker.return_value = [day1, day2, day3] swap1 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.01, name='swap1') swap2 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.02, name='swap2') swap3 = IRSwap('Pay', '10y', 'USD', fixed_rate=0.03, name='swap3') portfolio = Portfolio((swap1, swap2, swap3)) with BackToTheFuturePricingContext(dates=business_day_offset( dt.datetime.today().date(), [-1, 0, 1], roll='forward')) as hpc: risk_key = hpc._PricingContext__risk_key(risk.DollarPrice, swap1.provider) results = portfolio.calc(risk.DollarPrice) expected = risk.SeriesWithInfo( pd.Series(data=[0.06, 0.063, 0.066], index=business_day_offset(dt.datetime.today().date(), [-1, 0, 1], roll='forward')), risk_key=risk_key.ex_date_and_market, ) actual = results[risk.DollarPrice].aggregate() assert actual.equals(expected)
def run_backtest(self, strategy, start=None, end=None, frequency='1m', states=None, risks=Price, show_progress=True, csa_term=None, visible_to_gs=False, initial_value=0, result_ccy=None, holiday_calendar=None): """ run the backtest following the triggers and actions defined in the strategy. If states are entered run on those dates otherwise build a schedule from the start, end, frequency using gs_quant.datetime.relative_date.RelativeDateSchedule :param strategy: the strategy object :param start: a datetime :param end: a datetime :param frequency: str, default '1m' :param states: a list of dates will override the start, end, freq if provided :param risks: risks to run :param show_progress: boolean default true :param csa_term: the csa term to use :param visible_to_gs: are the contents of risk requests visible to GS (defaults to False) :param initial_value: initial cash value of strategy defaults to 0 :param result_ccy: ccy of all risks, pvs and cash :param holiday_calendar for date maths - list of dates :return: a backtest object containing the portfolios on each day and results which show all risks on all days """ logging.info( f'Starting Backtest: Building Date Schedule - {dt.datetime.now()}') strategy_pricing_dates = RelativeDateSchedule(frequency, start, end).apply_rule(holiday_calendar=holiday_calendar) \ if states is None else states strategy_start_date = strategy_pricing_dates[0] strategy_end_date = strategy_pricing_dates[-1] risks = list(set(make_list(risks) + strategy.risks)) if result_ccy is not None: risks = [(r( currency=result_ccy) if isinstance(r, ParameterisedRiskMeasure) else raiser(f'Unparameterised risk: {r}')) for r in risks] price_risk = Price( currency=result_ccy) if result_ccy is not None else Price backtest = BackTest(strategy, strategy_pricing_dates, risks) logging.info('Resolving initial portfolio') if len(strategy.initial_portfolio): for index in range(len(strategy.initial_portfolio)): old_name = strategy.initial_portfolio[index].name strategy.initial_portfolio[ index].name = f'{old_name}_{strategy_start_date.strftime("%Y-%m-%d")}' entry_payment = CashPayment(strategy.initial_portfolio[index], effective_date=strategy_start_date, direction=-1) backtest.cash_payments[strategy_start_date].append( entry_payment) final_date = get_final_date(strategy.initial_portfolio[index], strategy_start_date, None) exit_payment = CashPayment(strategy.initial_portfolio[index], effective_date=final_date) backtest.cash_payments[final_date].append(exit_payment) init_port = Portfolio(strategy.initial_portfolio) with PricingContext(pricing_date=strategy_start_date, csa_term=csa_term, show_progress=show_progress, visible_to_gs=visible_to_gs): init_port.resolve() for d in strategy_pricing_dates: backtest.portfolio_dict[d].append(init_port.instruments) logging.info( 'Building simple and semi-deterministic triggers and actions') for trigger in strategy.triggers: if trigger.calc_type != CalcType.path_dependent: triggered_dates = [] trigger_infos = defaultdict(list) for d in strategy_pricing_dates: t_info = trigger.has_triggered(d, backtest) if t_info: triggered_dates.append(d) if t_info.info_dict: for k, v in t_info.info_dict.items(): trigger_infos[k].append(v) for action in trigger.actions: if action.calc_type != CalcType.path_dependent: self.get_action_handler(action).apply_action( triggered_dates, backtest, trigger_infos[type(action)] if type(action) in trigger_infos else None) logging.info( f'Filtering strategy calculations to run from {strategy_start_date} to {strategy_end_date}' ) backtest.portfolio_dict = defaultdict( Portfolio, { k: backtest.portfolio_dict[k] for k in backtest.portfolio_dict if strategy_start_date <= k <= strategy_end_date }) backtest.scaling_portfolios = defaultdict( list, { k: backtest.scaling_portfolios[k] for k in backtest.scaling_portfolios if strategy_start_date <= k <= strategy_end_date }) logging.info( 'Pricing simple and semi-deterministic triggers and actions') with PricingContext(is_batch=True, show_progress=show_progress, csa_term=csa_term, visible_to_gs=visible_to_gs): backtest.calc_calls += 1 for day, portfolio in backtest.portfolio_dict.items(): if isinstance(day, dt.date): with PricingContext(day): backtest.calculations += len(portfolio) * len(risks) backtest.add_results(day, portfolio.calc(tuple(risks))) # semi path dependent initial calc for _, scaling_list in backtest.scaling_portfolios.items(): for p in scaling_list: with HistoricalPricingContext(dates=p.dates): backtest.calculations += len(risks) * len(p.dates) port = p.trade if isinstance( p.trade, Portfolio) else Portfolio([p.trade]) p.results = port.calc(tuple(risks)) logging.info( 'Scaling semi-deterministic triggers and actions and calculating path dependent triggers ' 'and actions') for d in strategy_pricing_dates: logging.info(f'{d}: Processing triggers and actions') # path dependent for trigger in strategy.triggers: if trigger.calc_type == CalcType.path_dependent: if trigger.has_triggered(d, backtest): for action in trigger.actions: self.get_action_handler(action).apply_action( d, backtest) else: for action in trigger.actions: if action.calc_type == CalcType.path_dependent: if trigger.has_triggered(d, backtest): self.get_action_handler(action).apply_action( d, backtest) # test to see if new trades have been added and calc port = [] for t in backtest.portfolio_dict[d]: if t.name not in list(backtest.results[d].to_frame().index): port.append(t) with PricingContext(is_batch=True, csa_term=csa_term, show_progress=show_progress, visible_to_gs=visible_to_gs): if len(port): with PricingContext(pricing_date=d): results = Portfolio(port).calc(tuple(risks)) for sp in backtest.scaling_portfolios[d]: if sp.results is None: with HistoricalPricingContext(dates=sp.dates): backtest.calculations += len(risks) * len(sp.dates) port_sp = sp.trade if isinstance( sp.trade, Portfolio) else Portfolio([sp.trade]) sp.results = port_sp.calc(tuple(risks)) # results should be added outside of pricing context and not in the same call as valuating them if len(port): backtest.add_results(d, results) # semi path dependent scaling if d in backtest.scaling_portfolios: for p in backtest.scaling_portfolios[d]: current_risk = backtest.results[d][p.risk].aggregate( allow_mismatch_risk_keys=True) hedge_risk = p.results[d][p.risk].aggregate() if current_risk.unit != hedge_risk.unit: raise RuntimeError( 'cannot hedge in a different currency') scaling_factor = current_risk / hedge_risk if isinstance(p.trade, Portfolio): # Scale the portfolio by risk target scaled_portfolio_position = copy.deepcopy(p.trade) scaled_portfolio_position.name = f'Scaled_{scaled_portfolio_position.name}' for instrument in scaled_portfolio_position.all_instruments: instrument.name = f'Scaled_{instrument.name}' # trade hedge in opposite direction scale_direction = -1 scaled_portfolio_position.scale(scaling_factor * scale_direction) for day in p.dates: # add scaled hedge position to portfolio for day. NOTE this adds leaves, not the portfolio backtest.portfolio_dict[day] += copy.deepcopy( scaled_portfolio_position) # now apply scaled portfolio to cash payments for d, payments in backtest.cash_payments.items(): for payment in payments: if payment.trade == p.trade: payment.trade = copy.deepcopy( scaled_portfolio_position) payment.scale_date = None else: new_notional = getattr( p.trade, p.scaling_parameter) * -scaling_factor scaled_trade = p.trade.as_dict() scaled_trade[p.scaling_parameter] = new_notional scaled_trade = Instrument.from_dict(scaled_trade) scaled_trade.name = p.trade.name for day in p.dates: backtest.add_results( day, p.results[day] * -scaling_factor) backtest.portfolio_dict[day] += Portfolio( scaled_trade) logging.info('Calculating and scaling newly added portfolio positions') # test to see if new trades have been added and calc with PricingContext(is_batch=True, show_progress=show_progress, csa_term=csa_term, visible_to_gs=visible_to_gs): backtest.calc_calls += 1 leaves_by_date = {} for day, portfolio in backtest.portfolio_dict.items(): results_for_date = backtest.results[day] if len(results_for_date) == 0: continue trades_for_date = list(results_for_date.to_frame().index) leaves = [] for leaf in portfolio: if leaf.name not in trades_for_date: logging.info( f'{day}: new portfolio position {leaf} scheduled for calculation' ) leaves.append(leaf) if len(leaves): with PricingContext(pricing_date=day): leaves_by_date[day] = Portfolio(leaves).calc( tuple(risks)) backtest.calculations += len(leaves) * len(risks) logging.info('Processing results for newly added portfolio positions') for day, leaves in leaves_by_date.items(): backtest.add_results(day, leaves) logging.info('Calculating prices for cash payments') # run any additional calcs to handle cash scaling (e.g. unwinds) cash_results = {} with PricingContext(is_batch=True, show_progress=show_progress, csa_term=csa_term, visible_to_gs=visible_to_gs): backtest.calc_calls += 1 cash_trades_by_date = defaultdict(list) for _, cash_payments in backtest.cash_payments.items(): for cp in cash_payments: # only calc if additional point is required trades = cp.trade.all_instruments if isinstance( cp.trade, Portfolio) else [cp.trade] for trade in trades: if cp.effective_date and cp.effective_date <= end: if cp.effective_date not in backtest.results or \ trade not in backtest.results[cp.effective_date]: cash_trades_by_date[cp.effective_date].append( trade) else: cp.scale_date = None for cash_date, trades in cash_trades_by_date.items(): with PricingContext(cash_date): backtest.calculations += len(risks) cash_results[cash_date] = Portfolio(trades).calc( price_risk) # handle cash current_value = None for d in sorted( set(strategy_pricing_dates + list(backtest.cash_payments.keys()))): if d <= end: if current_value is not None: backtest.cash_dict[d] = current_value if d in backtest.cash_payments: for cp in backtest.cash_payments[d]: trades = cp.trade.all_instruments if isinstance( cp.trade, Portfolio) else [cp.trade] for trade in trades: value = cash_results.get( cp.effective_date, {}).get(price_risk, {}).get(trade.name, {}) try: value = backtest.results[cp.effective_date][price_risk][trade.name] \ if value == {} else value except (KeyError, ValueError): raise RuntimeError( f'failed to get cash value for {trade.name} on ' f'{cp.effective_date} received value of {value}' ) if not isinstance(value, float): raise RuntimeError( f'failed to get cash value for {trade.name} on ' f'{cp.effective_date} received value of {value}' ) ccy = next(iter(value.unit)) if d not in backtest.cash_dict: backtest.cash_dict[d] = {ccy: initial_value} if ccy not in backtest.cash_dict[d]: backtest.cash_dict[d][ccy] = 0 if cp.scale_date: scale_notional = backtest.portfolio_dict[ cp.scale_date][ cp.trade.name].notional_amount scale_date_adj = scale_notional / cp.trade.notional_amount cp.cash_paid = value * scale_date_adj * cp.direction backtest.cash_dict[d][ccy] += cp.cash_paid else: cp.cash_paid = ( 0 if cp.cash_paid is None else cp.cash_paid) + value * cp.direction backtest.cash_dict[d][ccy] += cp.cash_paid current_value = copy.deepcopy(backtest.cash_dict[d]) logging.info(f'Finished Backtest:- {dt.datetime.now()}') return backtest
def run_backtest(cls, strategy, start=None, end=None, frequency='BM', window=None, states=None, risks=Price, show_progress=True): dates = pd.date_range(start=start, end=end, freq=frequency).date.tolist() risks = make_list(risks) + strategy.risks backtest = BackTest(strategy, dates, risks) for trigger in strategy.triggers: if trigger.calc_type != CalcType.path_dependent: triggered_dates = [ date for date in dates if trigger.has_triggered(date, backtest) ] for action in trigger.actions: if action.calc_type != CalcType.path_dependent: action.apply_action(triggered_dates, backtest) with PricingContext(is_batch=True, show_progress=show_progress): for day, portfolio in backtest.portfolio_dict.items(): with PricingContext(day): backtest.calc_calls += 1 backtest.calculations += len(portfolio) * len(risks) backtest.add_results(day, portfolio.calc(tuple(risks))) # semi path dependent initial calc for _, scaling_list in backtest.scaling_portfolios.items(): for p in scaling_list: with HistoricalPricingContext(dates=p.dates): backtest.calc_calls += 1 backtest.calculations += len(p.dates) * len(risks) p.results = Portfolio([p.trade]).calc(tuple(risks)) for date in dates: # semi path dependent scaling if date in backtest.scaling_portfolios: for p in backtest.scaling_portfolios[date]: scale_date = p.dates[0] scaling_factor = backtest.results[scale_date][ p.risk][0] / p.results[scale_date][p.risk][0] scaled_trade = p.trade.as_dict() scaled_trade['notional_amount'] *= scaling_factor scaled_trade = Instrument.from_dict(scaled_trade) for day in p.dates: backtest.add_results(day, p.results[day] * scaling_factor) backtest.portfolio_dict[day] += Portfolio(scaled_trade) # path dependent for trigger in strategy.triggers: if trigger.calc_type == CalcType.path_dependent: if trigger.has_triggered(date, backtest): for action in trigger.actions: action.apply_action(date, backtest) else: for action in trigger.actions: if action.calc_type == CalcType.path_dependent: if trigger.has_triggered(date, backtest): action.apply_action(date, backtest) return backtest
def test_results_with_resolution(mocker): with MockCalc(mocker): swap1 = IRSwap('Pay', '10y', 'USD', name='swap1') swap2 = IRSwap('Pay', '10y', 'GBP', name='swap2') swap3 = IRSwap('Pay', '10y', 'EUR', name='swap3') portfolio = Portfolio((swap1, swap2, swap3)) with PricingContext(pricing_date=dt.date(2020, 10, 15)): result = portfolio.calc((risk.DollarPrice, risk.IRDelta)) # Check that we've got results assert result[swap1][risk.DollarPrice] is not None # Now resolve portfolio and assert that we can still get the result orig_swap1 = swap1.clone() with PricingContext(pricing_date=dt.date(2020, 10, 15)): portfolio.resolve() # Assert that the resolved swap is indeed different and that we can retrieve results by both assert swap1 != orig_swap1 assert result[swap1][risk.DollarPrice] is not None assert result[orig_swap1][risk.DollarPrice] is not None # Now reset the instruments and portfolio swap1 = IRSwap('Pay', '10y', 'USD', name='swap1') swap2 = IRSwap('Pay', '10y', 'GBP', name='swap2') swap3 = IRSwap('Pay', '10y', 'EUR', name='swap3') portfolio = Portfolio((swap1, swap2, swap3, swap1)) with PricingContext(dt.date(2020, 10, 14)): # Resolve under a different pricing date portfolio.resolve() assert portfolio.instruments[0].termination_date == dt.date( 2030, 10, 16) assert portfolio.instruments[1].termination_date == dt.date( 2030, 10, 14) assert round(swap1.fixed_rate, 4) == 0.0075 assert round(swap2.fixed_rate, 4) == 0.004 assert round(swap3.fixed_rate, 4) == -0.0027 # Assert that after resolution under a different context, we cannot retrieve the result try: _ = result[swap1][risk.DollarPrice] assert False except KeyError: assert True # Resolve again and check we get the same values with PricingContext(dt.date(2020, 10, 14)): # Resolve under a different pricing date portfolio.resolve() assert portfolio.instruments[0].termination_date == dt.date( 2030, 10, 16) assert portfolio.instruments[1].termination_date == dt.date( 2030, 10, 14) assert round(swap1.fixed_rate, 4) == 0.0075 assert round(swap2.fixed_rate, 4) == 0.004 assert round(swap3.fixed_rate, 4) == -0.0027
def run_backtest(self, strategy, start=None, end=None, frequency='BM', states=None, risks=Price, show_progress=True, csa_term=None): """ run the backtest following the triggers and actions defined in the strategy. If states are entered run on those dates otherwise build a schedule from the start, end, frequency using pd.date_range :param strategy: the strategy object :param start: a datetime :param end: a datetime :param frequency: str or DateOffset, default 'D'. Frequency strings can have multiples, e.g. '5H'. :param window: not used yet - intended for running a strategy over a series of potentially overlapping dates :param states: a list of dates will override the start, end, freq if provided :param risks: risks to run :param show_progress: boolean default true :param csa_term: the csa term to use :return: a backtest object containing the portfolios on each day and results which show all risks on all days """ dates = pd.date_range( start=start, end=end, freq=frequency).date.tolist() if states is None else states risks = make_list(risks) + strategy.risks backtest = BackTest(strategy, dates, risks) if len(strategy.initial_portfolio): init_port = Portfolio(strategy.initial_portfolio) with PricingContext(pricing_date=dates[0], csa_term=csa_term): init_port.resolve() for d in dates: backtest.portfolio_dict[d].append(init_port.instruments) for trigger in strategy.triggers: if trigger.calc_type != CalcType.path_dependent: triggered_dates = [] trigger_infos = defaultdict(list) for d in dates: t_info = trigger.has_triggered(d, backtest) if t_info: triggered_dates.append(d) if t_info.info_dict: for k, v in t_info.info_dict.items(): trigger_infos[k].append(v) for action in trigger.actions: if action.calc_type != CalcType.path_dependent: self.get_action_handler(action).apply_action( triggered_dates, backtest, trigger_infos[type(action)] if type(action) in trigger_infos else None) with PricingContext(is_batch=True, show_progress=show_progress, csa_term=csa_term): for day, portfolio in backtest.portfolio_dict.items(): with PricingContext(day): backtest.calc_calls += 1 backtest.calculations += len(portfolio) * len(risks) backtest.add_results(day, portfolio.calc(tuple(risks))) # semi path dependent initial calc for _, scaling_list in backtest.scaling_portfolios.items(): for p in scaling_list: with HistoricalPricingContext(dates=p.dates): backtest.calc_calls += 1 backtest.calculations += len(p.dates) * len(risks) p.results = Portfolio([p.trade]).calc(tuple(risks)) for d in dates: # semi path dependent scaling if d in backtest.scaling_portfolios: for p in backtest.scaling_portfolios[d]: current_risk = backtest.results[d][p.risk].aggregate( allow_mismatch_risk_keys=True) hedge_risk = p.results[d][p.risk].aggregate() scaling_factor = current_risk / hedge_risk new_notional = p.trade.notional_amount * -scaling_factor scaled_trade = p.trade.as_dict() scaled_trade['notional_amount'] = new_notional scaled_trade = Instrument.from_dict(scaled_trade) scaled_trade.name = p.trade.name for day in p.dates: backtest.add_results(day, p.results[day] * -scaling_factor) backtest.portfolio_dict[day] += Portfolio(scaled_trade) # path dependent for trigger in strategy.triggers: if trigger.calc_type == CalcType.path_dependent: if trigger.has_triggered(d, backtest): for action in trigger.actions: self.get_action_handler(action).apply_action( d, backtest) else: for action in trigger.actions: if action.calc_type == CalcType.path_dependent: if trigger.has_triggered(d, backtest): self.get_action_handler(action).apply_action( d, backtest) # run any additional calcs to handle cash scaling (e.g. unwinds) cash_results = defaultdict(list) with PricingContext(is_batch=True, csa_term=csa_term): for _, cash_payments in backtest.cash_payments.items(): for cp in cash_payments: # only calc if additional point is required if cp.effective_date and cp.effective_date <= end and \ cp.trade not in backtest.results[cp.effective_date]: with PricingContext(cp.effective_date): backtest.calc_calls += 1 backtest.calculations += len(risks) cash_results[cp.effective_date].append( Portfolio([cp.trade]).calc(tuple(risks))) # add cash to risk results for day, risk_results in cash_results.items(): for rr in risk_results: backtest.add_results(day, rr) # handle cash for day, cash_payments in backtest.cash_payments.items(): if day <= end: for cp in cash_payments: if cp.scale_date: scale_notional = backtest.portfolio_dict[ cp.scale_date][cp.trade.name].notional_amount scale_date_adj = scale_notional / cp.trade.notional_amount backtest.cash_dict[cp.effective_date] += \ backtest.results[cp.effective_date][Price][cp.trade] * scale_date_adj * cp.direction else: backtest.cash_dict[day] += backtest.results[day][ Price][cp.trade] * cp.direction return backtest