Пример #1
0
def test_aggregation_with_empty_measures(mocker):
    with MockCalc(mocker):
        swaptions = (IRSwaption(notional_currency='EUR',
                                termination_date='7y',
                                expiration_date='1y',
                                pay_or_receive='Receive',
                                strike='ATM+35',
                                name='EUR 1y7y'),
                     IRSwaption(notional_currency='EUR',
                                termination_date='10y',
                                expiration_date='2w',
                                pay_or_receive='Receive',
                                strike='ATM+50',
                                name='EUR 2w10y'))
        portfolio = Portfolio(swaptions)

        from_date = dt.date(2021, 11, 18)
        to_date = dt.date(2021, 11, 19)
        explain_2d = PnlExplain(CloseMarket(date=to_date))

        with PricingContext(pricing_date=from_date, visible_to_gs=True):
            portfolio.resolve()
            result_explain = portfolio.calc(explain_2d)

        total_risk = aggregate_risk(result_explain[explain_2d])['value'].sum()
        risk_swaption_1 = result_explain[0]['value'].sum()
        risk_swaption_2 = result_explain[1]['value'].sum()

        assert total_risk == risk_swaption_1 + risk_swaption_2
Пример #2
0
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
Пример #3
0
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
Пример #4
0
"""
`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())
Пример #5
0
    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
Пример #6
0
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
Пример #7
0
    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
Пример #8
0
    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
Пример #9
0
    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()}')

        dates = RelativeDateSchedule(frequency,
                                     start,
                                     end).apply_rule(holiday_calendar=holiday_calendar) if states is None else states

        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, dates, risks)

        logging.info('Resolving Initial Portfolio')
        if len(strategy.initial_portfolio):
            init_port = Portfolio(strategy.initial_portfolio)
            with PricingContext(pricing_date=dates[0], csa_term=csa_term, show_progress=show_progress,
                                visible_to_gs=visible_to_gs):
                init_port.resolve()
            for d in 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 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('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)
                        p.results = Portfolio([p.trade]).calc(tuple(risks))

        logging.info('Scaling Semi-deterministic Triggers and Actions and Calculating Path Dependent Triggers '
                     'and Actions')
        for d in dates:

            # 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(csa_term=csa_term, show_progress=show_progress, visible_to_gs=visible_to_gs):
                if len(port) > 0:
                    with PricingContext(pricing_date=d):
                        backtest.add_results(d, 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)
                            sp.results = Portfolio([sp.trade]).calc(tuple(risks))

            # 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
                    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('Calculate Cash')
        # run any additional calcs to handle cash scaling (e.g. unwinds)
        cash_calcs = defaultdict(list)
        with PricingContext(is_batch=True, show_progress=show_progress, csa_term=csa_term, visible_to_gs=visible_to_gs):
            backtest.calc_calls += 1
            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:
                        if cp.effective_date not in backtest.results or \
                                cp.trade not in backtest.results[cp.effective_date]:
                            with PricingContext(cp.effective_date):
                                backtest.calculations += len(risks)
                                cash_calcs[cp.effective_date].append(Portfolio([cp.trade]).calc(price_risk))
                        else:
                            cp.scale_date = None

        cash_results = {}
        for day, risk_results in cash_calcs.items():
            if day <= end:
                cash_results[day] = risk_results[0]
                if len(risk_results) > 1:
                    for i in range(1, len(risk_results)):
                        cash_results[day] += risk_results[i]

        # handle cash
        current_value = None
        for d in sorted(set(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]:
                        value = cash_results.get(cp.effective_date, {}).get(price_risk, {}).get(cp.trade.name, {})
                        try:
                            value = backtest.results[cp.effective_date][price_risk][cp.trade.name] \
                                if value == {} else value
                        except ValueError:
                            raise RuntimeError(f'failed to get cash value for {cp.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 {cp.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 = value * cp.direction
                            backtest.cash_dict[d][ccy] += cp.cash_paid
                    current_value = backtest.cash_dict[d]

        logging.info(f'Finished Backtest:- {dt.datetime.now()}')
        return backtest