menu
Pricing and Risk

Backtesting

The backtesting framework is a language for describing backtests that's (1) intuitive (2) cross-asset and instrument agnostic (3) available natively in python for users to change and extend. It's compatible with a variety of calculation engines - eliminating need for specific syntax knowledge - and uses the gs risk API as the default calculation engine.

The language consists of several basic components:

  • Trigger
  • Action
  • Strategy
  • Calculation Engine
  • Backtest

In this tutorial, we will examine the components in the context of looking at a basic vol selling strategy where we sell a 1m10y USD straddle daily.

info

Note

Examples require an initialized GsSession and relevant entitlements. Please refer to Sessions for details.

Trigger

A trigger can be used to amend a strategy based on a set of requirements and the current state of the backtest. Each trigger has an associated action or set of actions that are applied when the trigger is active. A backtest can have multiple triggers.

Below are the triggers we currently offer:

  • PeriodicTrigger: triggers at a regular interval (time/date). e.g.: every month end
  • MktTrigger: triggers when market data, absolute or change, hits some target e.g. whenever SPX 1m vol crosses 10. Check out our data catalog for sources of market data that can be leveraged here
  • StrategyRiskTrigger: triggers when the current risk of the strategy hits some target e.g. delta crosses $10m

Since in our example, we are selling an instrument periodically e.g. (daily), we will use a single PeriodicTrigger. Let's define the PeriodicTriggerRequirements first and then talk about actions, since we need to specify at least a single action to happen when this trigger is triggered.

from gs_quant.backtests.triggers import PeriodicTrigger, PeriodicTriggerRequirements
from datetime import date

start_date, end_date = date(2020, 1, 1), date(2020, 12, 1)
# define dates on which actions will be triggered (B=every business day here)
trig_req = PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='B')

Action

Actions define how the portfolio is updated when a trigger is triggered. Below are the actions we currently offer:

  • AddTradeAction: adds trade to a strategy
  • HedgeAction: add trade that's a hedge for a risk

Since in our example we are selling a straddle, we only want a single AddTradeAction to supply to our trigger. Let's define it.

from gs_quant.backtests.actions import AddTradeAction
from gs_quant.common import PayReceive, Currency
from gs_quant.instrument import IRSwaption

# straddle is the position we'll be adding (note it's a short position since buy_sell='Sell')
straddle = IRSwaption(PayReceive.Straddle, '10y', Currency.USD, expiration_date='1m', notional_amount=1e8, buy_sell='Sell')

# this action specifies we will add the straddle when this action is triggered and hold it until expiration_date
action = AddTradeAction(straddle, 'expiration_date')

# we will now combine our trigger requirement and action to produce a PeriodicTrigge
# Note, action can be a list if there are multiple actions
trigger = PeriodicTrigger(trig_req, action)

Strategy

A Strategy combines all the information needed to run a backtest. It has 2 components: an initial trade or portfolio and a trigger or set of triggers.

In our example, we don't have a starting portfolio and we have a single trigger we defined in the previous step. Let's put these together.

from gs_quant.backtests.strategy import Strategy

# in this case we have a single trigger but this can be a list of triggers as well
strategy = Strategy(None, trigger)

Calculation engine

The final piece is calculate engine - the underlying engine/source of calculations you want to run your backtest.

Once you have defined your backtest in the steps above, you can view which available engines support your strategy. There may be multiple as different engines have been developed and optimized for certain types of backtesting. For example, the EquityVolEngine is optimized for fast volatility backtesting for a specific set of equity underlyers. That said, GenericEngine will support majority of use cases, running calculations using gs-quant's risk apis. You can check which available engines support your strategy using the function below:

strategy.get_available_engines()

Output:

[gs_quant.backtests.generic_engine.GenericEngine,
gs_quant.backtests.generic_price_engine.GenericPriceEngine]

Alternatively, you check whether a particular engine supports your strategy:

from gs_quant.backtests.generic_engine import GenericEngine

ge = GenericEngine()
ge.supports_strategy(strategy)

Output:

True

As a final step, let's put everything together to run the backtest. The frequency here indicates the frequency of calculations. You can also supply additional risks you may want to calculate as part of your backtest (greeks or dollar price, for example).

backtest = ge.run_backtest(strategy, start=start_date, end=end_date, frequency='B', show_progress=True)
backtest

Output:

Backtest

run_backtest returns an object which can be used in several ways:

  • to view the results of backtest.risks (by default, Price) evaluated on each backtested date: backtest.results
  • to view and further evaluate the portfolio on any given backtested date: backtest.portfolio_dict
  • to view the number of total api calls and number of calculations performed: backtest.calc_calls and backtest.calculations

In this case, we can use backtest.results and backtest.portfolio_dict to construct timeseries for premia paid, payoff collected and the mark-to-market of our strategy.

from collections import defaultdict
import pandas as pd

results = backtest.results
g_premia, g_payoff, g_mtm = defaultdict(int), defaultdict(int), defaultdict(int)
for day, portfolio in backtest.portfolio_dict.items():
    for inst in portfolio:
        if inst.premium_payment_date == day:
            g_premia[day] += results[day][inst]
        if inst.expiration_date == day:
            g_payoff[day] += results[day][inst]
        if inst.expiration_date != day:
            g_mtm[day] += results[day][inst]

g_overview = pd.concat([pd.Series(g_premia).cumsum(), pd.Series(g_payoff).cumsum(), pd.Series(g_mtm)], axis=1,
                     sort=True).fillna(method='ffill').fillna(0)
g_overview.columns = ['Premium Received at Inception', 'Paid at Expiry', 'Mark to Market']
g_overview.plot(figsize=(12, 8), title='Cumulative Payoff, Premium and Mark-to-Market')

Output:


Related Content


GS DAP® is owned and operated by Goldman Sachs. This site is for informational purposes only and does not constitute an offer to provide, or the solicitation of an offer to provide access to or use of GS DAP®. Any subsequent commitment by Goldman Sachs to provide access to and / or use of GS DAP® would be subject to various conditions, including, amongst others, (i) satisfactory determination and legal review of the structure of any potential product or activity, (ii) receipt of all internal and external approvals (including potentially regulatory approvals); (iii) execution of any relevant documentation in a form satisfactory to Goldman Sachs; and (iv) completion of any relevant system / technology / platform build or adaptation required or desired to support the structure of any potential product or activity. All GS DAP® features may not be available in certain jurisdictions. Not all features of GS DAP® will apply to all use cases. Use of terms (e.g., "account") on GS DAP® are for convenience only and does not imply any regulatory or legal status by such term.
¹ Real-time data can be impacted by planned system maintenance, connectivity or availability issues stemming from related third-party service providers, or other intermittent or unplanned technology issues.
Transaction Banking services are offered by Goldman Sachs Bank USA (“GS Bank”) and its affiliates. GS Bank is a New York State chartered bank, a member of the Federal Reserve System and a Member FDIC. For additional information, please see Bank Regulatory Information.
Certain solutions and Institutional Services described herein are provided via our Marquee platform. The Marquee platform is for institutional and professional clients only. This site is for informational purposes only and does not constitute an offer to provide the Marquee platform services described, nor an offer to sell, or the solicitation of an offer to buy, any security. Some of the services and products described herein may not be available in certain jurisdictions or to certain types of clients. Please contact your Goldman Sachs sales representative with any questions. Any data or market information presented on the site is solely for illustrative purposes. There is no representation that any transaction can or could have been effected on such terms or at such prices. Please see https://www.goldmansachs.com/disclaimer/sec-div-disclaimers-for-electronic-comms.html for additional information.
Mosaic is a service mark of Goldman Sachs & Co. LLC. This service is made available in the United States by Goldman Sachs & Co. LLC and outside of the United States by Goldman Sachs International, or its local affiliates in accordance with applicable law and regulations. Goldman Sachs International and Goldman Sachs & Co. LLC are the distributors of the Goldman Sachs Funds. Depending upon the jurisdiction in which you are located, transactions in non-Goldman Sachs money market funds are affected by either Goldman Sachs & Co. LLC, a member of FINRA, SIPC and NYSE, or Goldman Sachs International. For additional information contact your Goldman Sachs representative. Goldman Sachs & Co. LLC, Goldman Sachs International, Goldman Sachs Liquidity Solutions, Goldman Sachs Asset Management, L.P., and the Goldman Sachs funds available through Goldman Sachs Liquidity Solutions and other affiliated entities, are under the common control of the Goldman Sachs Group, Inc.
© 2025 Goldman Sachs. All rights reserved.