Skip to content

Commit

Permalink
Chore: Make release 1.0.102
Browse files Browse the repository at this point in the history
  • Loading branch information
martinroberson authored and Vanden Bon, David V [GBM Public] committed Jul 19, 2024
1 parent 0c9d44a commit 3949947
Show file tree
Hide file tree
Showing 10 changed files with 155 additions and 27 deletions.
3 changes: 2 additions & 1 deletion gs_quant/api/gs/hedges.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from gs_quant.target.hedge import PerformanceHedgeParameters, ClassificationConstraint, AssetConstraint, Target

_logger = logging.getLogger(__name__)
CALCULATION_TIMEOUT = 180


class GsHedgeApi:
Expand Down Expand Up @@ -176,4 +177,4 @@ def calculate_hedge(cls, hedge_query: dict) -> dict:
:param hedge_query: dict, hedge data that is sent to the Marquee API as input to the performance hedger
:return: dict, the results of calling the Marquee performance hedger
"""
return GsSession.current._post('/hedges/calculations', payload=hedge_query)
return GsSession.current._post('/hedges/calculations', payload=hedge_query, timeout=CALCULATION_TIMEOUT)
6 changes: 5 additions & 1 deletion gs_quant/backtests/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,10 @@ class AddScaledTradeAction(Action):
:param trade_duration: an instrument attribute eg. 'expiration_date' or a date or a tenor or timedelta
if left as None the
trade will be added for all future dates
:param scaling_parameters: a ScaledActionParameters object which defines the type and level of scaling
:param name: optional additional name to the priceable name
:param scaling_type: the type of scaling we are doing
:param scaling_risk: if the scaling type is a measure then this is the definition of the measure
:param scaling_level: the level of scaling to be done
:param transaction_cost: optional a cash amount paid for each transaction, paid on both enter and exit
"""
priceables: Union[Priceable, Iterable[Priceable]] = field(default=None,
Expand All @@ -171,6 +173,8 @@ class AddScaledTradeAction(Action):
scaling_type: ScalingActionType = ScalingActionType.size
scaling_risk: RiskMeasure = None
scaling_level: Union[float, int] = 1
transaction_cost: TransactionModel = field(default_factory=default_transaction_cost,
metadata=config(decoder=dc_decode(ConstantTransactionModel)))
class_type: str = static_field('add_scaled_trade_action')

def __post_init__(self):
Expand Down
2 changes: 2 additions & 0 deletions gs_quant/backtests/backtest_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ def result_summary(self):
for name, cash_dict in cash_summary.items()], axis=1, sort=True)
transaction_costs = pd.Series(self.transaction_costs, name='Transaction Costs')
df = pd.concat([summary, cash, transaction_costs], axis=1, sort=True).ffill().fillna(0)
# cum sum the transaction_costs
df['Transaction Costs'] = df['Transaction Costs'].cumsum()
df['Total'] = df[self.price_measure] + df['Cumulative Cash'] + df['Transaction Costs']
return df[:self.states[-1]]

Expand Down
3 changes: 3 additions & 0 deletions gs_quant/backtests/equity_vol_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from gs_quant.backtests import triggers as t
from gs_quant.backtests import actions as a
from gs_quant.backtests.strategy_systematic import StrategySystematic, DeltaHedgeParameters, TradeInMethod
from gs_quant.backtests.backtest_objects import ConstantTransactionModel
from gs_quant.instrument import EqOption, EqVarianceSwap
from gs_quant.markets.portfolio import Portfolio
from gs_quant.risk import EqDelta, EqSpot, EqGamma, EqVega
Expand Down Expand Up @@ -178,6 +179,8 @@ def check_strategy(cls, strategy):
if isinstance(action, a.AddScaledTradeAction):
if action.scaling_level is None or action.scaling_type is None:
check_results.append('Error: AddScaledTradeAction scaling_level or scaling_type is None')
if hasattr(action, 'transaction_cost') and action.transaction_cost != ConstantTransactionModel(0):
check_results.append('Error: Transaction costs not supported')
expiry_date_modes = map(lambda x: ExpirationDateParser(x.expirationDate).get_mode(),
action.priceables)
expiry_date_modes = list(set(expiry_date_modes))
Expand Down
5 changes: 5 additions & 0 deletions gs_quant/backtests/generic_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,13 @@ def apply_action(self,
for create_date, portfolio in orders.items():
for inst in portfolio.all_instruments:
backtest.cash_payments[create_date].append(CashPayment(inst, effective_date=create_date, direction=-1))
backtest.transaction_costs[create_date] -= self.action.transaction_cost.get_cost(create_date, backtest,
trigger_info, inst)
final_date = get_final_date(inst, create_date, self.action.trade_duration)
backtest.cash_payments[final_date].append(CashPayment(inst, effective_date=final_date))
backtest.transaction_costs[final_date] -= self.action.transaction_cost.get_cost(final_date,
backtest,
trigger_info, inst)

for s in backtest.states:
pos = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@
"trade_action_scaled = AddScaledTradeAction(priceables=portfolio, trade_duration='1m', scaling_type=ScalingActionType.size,\n",
" scaling_risk=None, scaling_level=2, name='scaled_act')\n",
"trade_trigger_scaled = PeriodicTrigger(trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'),\n",
" actions=trade_action_scaled)\n",
" actions=trade_action_scaled)\n",
"\n",
"strategy = Strategy(None, trade_trigger_scaled)\n",
"\n",
Expand Down Expand Up @@ -250,7 +250,7 @@
"trade_action_scaled = AddScaledTradeAction(priceables=portfolio, trade_duration='1m', scaling_type=ScalingActionType.risk_measure,\n",
" scaling_risk=risk.EqVega, scaling_level=100, name='scaled_vega_act')\n",
"trade_trigger_scaled = PeriodicTrigger(trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'),\n",
" actions=trade_action_scaled)\n",
" actions=trade_action_scaled)\n",
"\n",
"strategy = Strategy(None, trade_trigger_scaled)\n",
"\n",
Expand Down Expand Up @@ -300,7 +300,8 @@
"outputs": [],
"source": [
"# View Performance\n",
"pd.DataFrame({'Generic backtester': backtest.result_summary['Cumulative Cash'] + backtest.result_summary[Price]}).plot(figsize=(10, 6), title='Performance')"
"pd.DataFrame({'Generic backtester': backtest.result_summary['Cumulative Cash'] + backtest.result_summary[Price]}).plot(figsize=(10, 6), \n",
" title='Performance')"
]
},
{
Expand All @@ -322,9 +323,10 @@
"start_date = date(2024, 1, 1)\n",
"end_date = datetime.today().date()\n",
"\n",
"trade_action_scaled = EnterPositionQuantityScaledAction(priceables=portfolio, trade_duration='1m', trade_quantity=100, trade_quantity_type=BacktestTradingQuantityType.NAV, name='nav_act')\n",
"trade_action_scaled = EnterPositionQuantityScaledAction(priceables=portfolio, trade_duration='1m', trade_quantity=100, \n",
" trade_quantity_type=BacktestTradingQuantityType.NAV, name='nav_act')\n",
"trade_trigger_scaled = PeriodicTrigger(trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'),\n",
" actions=trade_action_scaled)\n",
" actions=trade_action_scaled)\n",
"\n",
"strategy = Strategy(None, trade_trigger_scaled)\n",
"\n",
Expand Down Expand Up @@ -374,13 +376,87 @@
"outputs": [],
"source": [
"# View Performance. Maximum loss is 100 if there is no short selling\n",
"pd.DataFrame({'Generic backtester': backtest.result_summary['Cumulative Cash'] + backtest.result_summary[Price]}).plot(figsize=(10, 6), title='Performance')"
"pd.DataFrame({'Generic backtester': backtest.result_summary['Cumulative Cash'] + backtest.result_summary[Price]}).plot(figsize=(10, 6), \n",
" title='Performance')"
]
},
{
"cell_type": "markdown",
"id": "600d7d36-eb13-4099-ab3d-7525683836b4",
"metadata": {},
"source": [
"### Add Scaled Action with Transaction Cost"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "38f96947-0729-410d-95e8-169b0e6a0fe0",
"metadata": {},
"outputs": [],
"source": [
"# Scale the position quantity (no. of options) by 2 with a transaction cost of 1 per trade\n",
"trade_action_scaled_tc = AddScaledTradeAction(priceables=portfolio, trade_duration='1m', scaling_type=ScalingActionType.size,\n",
" scaling_risk=None, scaling_level=2, name='scaled_act', \n",
" transaction_cost=ConstantTransactionModel(1))\n",
"trade_trigger_scaled_tc = PeriodicTrigger(trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'),\n",
" actions=trade_action_scaled_tc)\n",
"\n",
"strategy = Strategy(None, trade_trigger_scaled_tc)\n",
"\n",
"# Run backtest daily\n",
"GE = GenericEngine()\n",
"backtest = GE.run_backtest(strategy, start=start_date, end=end_date, frequency='1b', show_progress=True)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f9fca96e-a17f-41b1-9a40-502036aa6753",
"metadata": {},
"outputs": [],
"source": [
"# View results summary. we see the transaction costs\n",
"backtest.result_summary"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0b954e01-bd97-43f1-9188-fad34a2c149a",
"metadata": {},
"outputs": [],
"source": [
"# View backtest trade ledger\n",
"backtest.trade_ledger()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bcbd056e-8760-48dc-885f-3e1a1413d44f",
"metadata": {},
"outputs": [],
"source": [
"# View Mark to Market\n",
"pd.DataFrame({'Generic backtester': backtest.result_summary[Price]}).plot(figsize=(10, 6), title='Mark to market')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c633620b-85e5-4862-9172-1dd39bf5e162",
"metadata": {},
"outputs": [],
"source": [
"pd.DataFrame([backtest.result_summary['Cumulative Cash'] + backtest.result_summary[Price],\n",
" backtest.result_summary['Total']], index=(['Without Transaction Cost', 'With Transaction Cost'])).T.plot()\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "19ba18f5-e905-4183-b47c-cd6cc7c74b0d",
"id": "c0c84855-26c1-4b29-9b18-af611b3e0f0c",
"metadata": {},
"outputs": [],
"source": []
Expand All @@ -407,4 +483,4 @@
},
"nbformat": 4,
"nbformat_minor": 5
}
}
3 changes: 2 additions & 1 deletion gs_quant/markets/factor.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import numpy as np
import pandas as pd
import math

from gs_quant.data.core import DataContext
from gs_quant.datetime import date
Expand Down Expand Up @@ -137,7 +138,7 @@ def volatility(self,
factors=[self.name],
limit_factors=False).get('results')

volatility_data_df = build_factor_volatility_dataframe(volatility_raw_data, True, None) * 252
volatility_data_df = build_factor_volatility_dataframe(volatility_raw_data, True, None) * math.sqrt(252)
if format == ReturnFormat.JSON:
return volatility_data_df.squeeze(axis=1).to_dict()

Expand Down
5 changes: 2 additions & 3 deletions gs_quant/markets/optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1098,9 +1098,8 @@ def run(self,
functions to pull results
:param optimizer_type: optimizer type
:param fail_on_unpriced_positions: whether or
not to fail the calculations if some of the portfolio positions do not have pricing data in Marquee. If set
to false, unpriced assets will be sifted out before the optimization is run
:param fail_on_unpriced_positions: whether to fail the calculations if some of the portfolio positions do not
have pricing data in Marquee. If set to false, unpriced assets will be sifted out before the optimization is run
"""
if optimizer_type is None:
raise MqValueError('You must pass an optimizer type.')
Expand Down
59 changes: 48 additions & 11 deletions gs_quant/test/backtest/test_backtest_eq_vol_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
from gs_quant.backtests.strategy import Strategy
from gs_quant.backtests.triggers import PeriodicTrigger, PeriodicTriggerRequirements, DateTriggerRequirements, \
AggregateTrigger, AggregateTriggerRequirements, PortfolioTriggerRequirements, TriggerDirection
from gs_quant.backtests.actions import EnterPositionQuantityScaledAction, HedgeAction, ExitPositionAction
from gs_quant.backtests.actions import EnterPositionQuantityScaledAction, HedgeAction, ExitPositionAction, \
AddTradeAction, AddScaledTradeAction, ScalingActionType
from gs_quant.backtests.equity_vol_engine import *
from gs_quant.common import Currency, AssetClass
from gs_quant.session import GsSession, Environment
Expand Down Expand Up @@ -692,7 +693,7 @@ def test_supports_strategy():
strategy = Strategy(initial_portfolio=None, triggers=[trigger, hedge_trigger])
assert not EquityVolEngine.supports_strategy(strategy)

# 6. Invalid - non-daily hedge trade
# 7. Invalid - non-daily hedge trade

action = EnterPositionQuantityScaledAction(priceables=option, trade_duration='1m')
trigger = PeriodicTrigger(
Expand All @@ -704,7 +705,7 @@ def test_supports_strategy():
strategy = Strategy(initial_portfolio=None, triggers=[trigger, hedge_trigger])
assert not EquityVolEngine.supports_strategy(strategy)

# 7. Invalid - expiration date modifiers must be the same
# 8. Invalid - expiration date modifiers must be the same

option_listed = EqOption('.STOXX50E', expirationDate='3m@listed', strikePrice='ATM', optionType=OptionType.Call,
optionStyle=OptionStyle.European)
Expand All @@ -716,10 +717,7 @@ def test_supports_strategy():
strategy = Strategy(initial_portfolio=None, triggers=[trigger])
assert not EquityVolEngine.supports_strategy(strategy)

option_listed = EqOption('.STOXX50E', expirationDate='3m@listed', strikePrice='ATM', optionType=OptionType.Call,
optionStyle=OptionStyle.European)

# 8. Invalid - expiration date modifier not in [otc, listed]
# 9. Invalid - expiration date modifier not in [otc, listed]

option_invalid = EqOption('.STOXX50E', expirationDate='3m@invalid', strikePrice='ATM', optionType=OptionType.Call)
action = EnterPositionQuantityScaledAction(priceables=[option_invalid], trade_duration='1m')
Expand All @@ -729,7 +727,7 @@ def test_supports_strategy():
strategy = Strategy(initial_portfolio=None, triggers=[trigger])
assert not EquityVolEngine.supports_strategy(strategy)

# 9. Invalid - hedging without synthetic forward (not a portfolio)
# 10. Invalid - hedging without synthetic forward (not a portfolio)
hedge_portfolio = Portfolio(name='SynFwd', priceables=[long_call])

hedge_trigger = PeriodicTrigger(
Expand All @@ -739,7 +737,7 @@ def test_supports_strategy():

assert not EquityVolEngine.supports_strategy(strategy)

# 10. Invalid - hedging without synthetic forward (two calls)
# 11. Invalid - hedging without synthetic forward (two calls)

long_call_2 = EqOption('.STOXX50E', expiration_date='3m', strike_price='ATM', option_type=OptionType.Call,
option_style=OptionStyle.European, buy_sell=BuySell.Buy)
Expand All @@ -753,7 +751,7 @@ def test_supports_strategy():

assert not EquityVolEngine.supports_strategy(strategy)

# 10. Invalid - hedging without synthetic forward (more than two options)
# 12. Invalid - hedging without synthetic forward (more than two options)

hedge_portfolio = Portfolio(name='SynFwd', priceables=[long_call, short_put, long_call_2])

Expand All @@ -764,7 +762,7 @@ def test_supports_strategy():

assert not EquityVolEngine.supports_strategy(strategy)

# 10. Invalid - hedging without synthetic forward (properties mismatch)
# 13. Invalid - hedging without synthetic forward (properties mismatch)

long_call_2m = EqOption('.STOXX50E', expiration_date='2m', strike_price='ATM', option_type=OptionType.Call,
option_style=OptionStyle.European, buy_sell=BuySell.Buy)
Expand All @@ -779,3 +777,42 @@ def test_supports_strategy():
strategy = Strategy(initial_portfolio=None, triggers=[trigger, hedge_trigger])

assert not EquityVolEngine.supports_strategy(strategy)

# 14. Valid - AddTradeAction

add_trade_action = AddTradeAction(priceables=option, trade_duration='1m')
trigger = PeriodicTrigger(
trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'),
actions=add_trade_action)
strategy = Strategy(initial_portfolio=None, triggers=[trigger])
assert EquityVolEngine.supports_strategy(strategy)

# 15. Valid - AddScaledTradeAction

add_scaled_trade_action = AddScaledTradeAction(priceables=option, trade_duration='1m',
scaling_type=ScalingActionType.size, scaling_level=2)
trigger = PeriodicTrigger(
trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'),
actions=add_scaled_trade_action)
strategy = Strategy(initial_portfolio=None, triggers=[trigger])
assert EquityVolEngine.supports_strategy(strategy)

# 16. Invalid - transaction_costs not supported

add_trade_action_tc = AddTradeAction(priceables=option, trade_duration='1m',
transaction_cost=ConstantTransactionModel(100))
trigger = PeriodicTrigger(
trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'),
actions=add_trade_action_tc)
strategy = Strategy(initial_portfolio=None, triggers=[trigger])
assert not EquityVolEngine.supports_strategy(strategy)

# 17. Valid - transaction_costs supported if 0

add_trade_action_tc0 = AddTradeAction(priceables=option, trade_duration='1m',
transaction_cost=ConstantTransactionModel(0))
trigger = PeriodicTrigger(
trigger_requirements=PeriodicTriggerRequirements(start_date=start_date, end_date=end_date, frequency='1m'),
actions=add_trade_action_tc0)
strategy = Strategy(initial_portfolio=None, triggers=[trigger])
assert EquityVolEngine.supports_strategy(strategy)
4 changes: 2 additions & 2 deletions gs_quant/test/backtest/test_generic_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ def test_scaled_transaction_cost(mocker):
assert len(summary) == 5
assert len(ledger) == 5
assert round(summary[Price].sum()) == 90
assert round(summary['Transaction Costs'].sum()) == 50000 * 0.0001 * 5 * -1
assert round(summary['Transaction Costs'][-1]) == 50000 * 0.0001 * 5 * -1


@patch.object(GenericEngine, 'new_pricing_context', mock_pricing_context)
Expand Down Expand Up @@ -401,7 +401,7 @@ def test_risk_scaled_transaction_cost(mocker):
assert len(summary) == 5
assert len(ledger) == 5
assert round(summary[Price].sum()) == -64
assert round(summary['Transaction Costs'].sum()) == 62
assert round(summary['Transaction Costs'][-1]) == 62


@patch.object(GenericEngine, 'new_pricing_context', mock_pricing_context)
Expand Down

0 comments on commit 3949947

Please sign in to comment.