Skip to content

Commit

Permalink
Chore: Make release 1.0.96
Browse files Browse the repository at this point in the history
  • Loading branch information
martinroberson authored and Pang, Stephen S C. [GBM Public] committed Jul 9, 2024
1 parent 574286e commit d0d75a6
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@
decode_risk_result_with_data
from gs_quant.api.gs.backtests_xasset.response_datatypes.backtest_datatypes import Transaction
from gs_quant.api.gs.backtests_xasset.response_datatypes.risk_result_datatypes import RiskResultWithData
from gs_quant.backtests import FlowVolBacktestMeasure
from gs_quant.common import Currency, CurrencyName, RiskMeasure
from gs_quant.json_convertors_common import encode_risk_measure, decode_risk_measure
from gs_quant.priceable import PriceableImpl
from gs_quant.target.backtests import FlowVolBacktestMeasure


def encode_response_obj(data: Any) -> Dict:
Expand Down
2 changes: 1 addition & 1 deletion gs_quant/api/gs/backtests_xasset/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from gs_quant.api.gs.backtests_xasset.json_encoders.request_encoders import legs_encoder, legs_decoder
from gs_quant.api.gs.backtests_xasset.response_datatypes.backtest_datatypes import DateConfig, Trade, \
CostPerTransaction, Configuration
from gs_quant.backtests.strategy import Strategy
from gs_quant.api.gs.backtests_xasset.response_datatypes.generic_backtest_datatypes import Strategy
from gs_quant.common import RiskMeasure
from gs_quant.json_convertors import decode_optional_date, decode_date_tuple, encode_date_tuple
from gs_quant.json_convertors_common import encode_risk_measure_tuple, decode_risk_measure_tuple
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""
Copyright 2019 Goldman Sachs.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
"""

from dataclasses import dataclass
from dataclasses_json import dataclass_json


@dataclass_json
@dataclass
class Strategy(object):
"""
A strategy object on which one may run a backtest
"""
pass
29 changes: 15 additions & 14 deletions gs_quant/api/gs/portfolios.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from gs_quant.common import PositionType
from gs_quant.common import RiskRequest, Currency
from gs_quant.instrument import Instrument
from gs_quant.session import GsSession
from gs_quant.target.portfolios import Portfolio, Position, PositionSet
from gs_quant.target.reports import Report
from gs_quant.target.risk_models import RiskModelTerm as Term
Expand Down Expand Up @@ -54,11 +55,11 @@ def get_portfolios(cls,
url += f'&{k}={i}'
else:
url += f'&{k}={v}'
return cls.get_session()._get(f'{url}&limit={limit}', cls=Portfolio)['results']
return GsSession.current._get(f'{url}&limit={limit}', cls=Portfolio)['results']

@classmethod
def get_portfolio(cls, portfolio_id: str) -> Portfolio:
return cls.get_session()._get('/portfolios/{id}'.format(id=portfolio_id), cls=Portfolio)
return GsSession.current._get('/portfolios/{id}'.format(id=portfolio_id), cls=Portfolio)

@classmethod
def get_portfolio_by_name(cls, name: str) -> Portfolio:
Expand All @@ -74,15 +75,15 @@ def get_portfolio_by_name(cls, name: str) -> Portfolio:

@classmethod
def create_portfolio(cls, portfolio: Portfolio) -> Portfolio:
return cls.get_session()._post('/portfolios', portfolio, cls=Portfolio)
return GsSession.current._post('/portfolios', portfolio, cls=Portfolio)

@classmethod
def update_portfolio(cls, portfolio: Portfolio):
return cls.get_session()._put('/portfolios/{id}'.format(id=portfolio.id), portfolio, cls=Portfolio)
return GsSession.current._put('/portfolios/{id}'.format(id=portfolio.id), portfolio, cls=Portfolio)

@classmethod
def delete_portfolio(cls, portfolio_id: str) -> dict:
return cls.get_session()._delete('/portfolios/{id}'.format(id=portfolio_id))
return GsSession.current._delete('/portfolios/{id}'.format(id=portfolio_id))

# manage portfolio positions

Expand All @@ -95,15 +96,15 @@ def get_positions(cls, portfolio_id: str, start_date: dt.date = None, end_date:
if end_date is not None:
url += '&endDate={sd}'.format(sd=end_date.isoformat())

res = cls.get_session()._get(url)
res = GsSession.current._get(url)
return tuple(PositionSet.from_dict(v) for v in res.get('positionSets', ()))

@classmethod
def get_positions_for_date(cls, portfolio_id: str, position_date: dt.date,
position_type: str = 'close') -> PositionSet:
url = '/portfolios/{id}/positions/{date}?type={ptype}'.format(
id=portfolio_id, date=position_date.isoformat(), ptype=position_type)
position_sets = cls.get_session()._get(url, cls=PositionSet)['results']
position_sets = GsSession.current._get(url, cls=PositionSet)['results']
return position_sets[0] if len(position_sets) > 0 else None

@classmethod
Expand Down Expand Up @@ -150,7 +151,7 @@ def get_instruments_by_position_type(cls, positions_type: str,
@classmethod
def get_latest_positions(cls, portfolio_id: str, position_type: str = 'close') -> Union[PositionSet, dict]:
url = '/portfolios/{id}/positions/last?type={ptype}'.format(id=portfolio_id, ptype=position_type)
results = cls.get_session()._get(url)['results']
results = GsSession.current._get(url)['results']

# Annoyingly, different types are returned depending on position_type

Expand Down Expand Up @@ -190,7 +191,7 @@ def update_positions(cls,
position_sets: List[PositionSet],
net_positions: bool = True) -> float:
url = f'/portfolios/{portfolio_id}/positions?netPositions={str(net_positions).lower()}'
return cls.get_session()._put(url, position_sets)
return GsSession.current._put(url, position_sets)

@classmethod
def get_positions_data(cls,
Expand All @@ -213,7 +214,7 @@ def get_positions_data(cls,
if include_all_business_days:
url += '&includeAllBusinessDays=true'

return cls.get_session()._get(url)['results']
return GsSession.current._get(url)['results']

@classmethod
def update_quote(cls, quote_id: str, request: RiskRequest):
Expand Down Expand Up @@ -322,7 +323,7 @@ def get_custom_aum(cls,
url += f"&startDate={start_date.strftime('%Y-%m-%d')}"
if end_date:
url += f"&endDate={end_date.strftime('%Y-%m-%d')}"
return cls.get_session()._get(url)['data']
return GsSession.current._get(url)['data']

@classmethod
@deprecation.deprecated(deprecated_in='1.0.10',
Expand All @@ -336,11 +337,11 @@ def upload_custom_aum(cls,
payload = {'data': aum_data}
if clear_existing_data:
url += '?clearExistingData=true'
return cls.get_session()._post(url, payload)
return GsSession.current._post(url, payload)

@classmethod
def update_portfolio_tree(cls, portfolio_id: str):
return cls.get_session()._post(f'/portfolios/{portfolio_id}/tree', {})
return GsSession.current._post(f'/portfolios/{portfolio_id}/tree', {})

@classmethod
def get_attribution(cls,
Expand All @@ -358,4 +359,4 @@ def get_attribution(cls,
url += f"&currency={currency.value}"
if performance_report_id:
url += f'&reportId={performance_report_id}'
return cls.get_session()._get(url)['results']
return GsSession.current._get(url)['results']
1 change: 0 additions & 1 deletion gs_quant/backtests/strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
from gs_quant.base import Priceable
from gs_quant.json_convertors import decode_named_instrument, encode_named_instrument, dc_decode


backtest_engines = [GenericEngine(), PredefinedAssetEngine(), EquityVolEngine()]


Expand Down
70 changes: 47 additions & 23 deletions gs_quant/backtests/strategy_systematic.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,15 @@

import gs_quant.target.backtests as backtests
from gs_quant.api.gs.backtests import GsBacktestApi
from gs_quant.base import get_enum_value
from gs_quant.api.gs.backtests_xasset.apis import GsBacktestXassetApi
from gs_quant.api.gs.backtests_xasset.request import BasicBacktestRequest
from gs_quant.api.gs.backtests_xasset.response_datatypes.backtest_datatypes import DateConfig, Trade, \
CostPerTransaction, TransactionCostModel
from gs_quant.backtests.core import Backtest, TradeInMethod
from gs_quant.errors import MqValueError
from gs_quant.target.backtests import *
from gs_quant.instrument import EqOption, EqVarianceSwap
from gs_quant.instrument import EqOption, EqVarianceSwap, Instrument
from gs_quant.target.instrument import FXOption, FXBinary

_logger = logging.getLogger(__name__)

Expand All @@ -31,24 +35,14 @@
ISO_FORMAT = r"^([0-9]{4})-([0-9]{2})-([0-9]{2})$"


class StrategySystematicBase(metaclass=ABCMeta):
@abstractmethod
def backtest(
self,
start: datetime.date = None,
end: datetime.date = datetime.date.today() - datetime.timedelta(days=1),
is_async: bool = False,
measures: Iterable[FlowVolBacktestMeasure] = (FlowVolBacktestMeasure.ALL_MEASURES,),
correlation_id: str = None
) -> Union[Backtest, BacktestResult]:
...


class StrategySystematic(StrategySystematicBase):
class StrategySystematic:
"""Equity back testing systematic strategy"""
_supported_eq_instruments = (EqOption, EqVarianceSwap)
_supported_fx_instruments = (FXOption, FXBinary)
_supported_instruments = _supported_eq_instruments + _supported_fx_instruments

def __init__(self,
underliers: Union[EqOption, Iterable[EqOption], EqVarianceSwap, Iterable[EqVarianceSwap]],
underliers: Union[Instrument, Iterable[Instrument]],
quantity: float = 1,
quantity_type: Union[BacktestTradingQuantityType, str] = BacktestTradingQuantityType.notional,
trade_in_method: Union[TradeInMethod, str] = TradeInMethod.FixedRoll,
Expand Down Expand Up @@ -85,11 +79,12 @@ def __init__(self,
)

self.__underliers = []

if isinstance(underliers, (EqOption, EqVarianceSwap)):
trade_instruments = []
if isinstance(underliers, self._supported_instruments):
instrument = underliers
notional_percentage = 100
instrument = self.check_underlier_fields(instrument)
trade_instruments.append(instrument)
self.__underliers.append(BacktestStrategyUnderlier(
instrument=instrument,
notional_percentage=notional_percentage,
Expand All @@ -105,16 +100,22 @@ def __init__(self,
instrument = underlier
notional_percentage = 100

if not isinstance(instrument, (EqOption, EqVarianceSwap)):
if not isinstance(instrument, self._supported_instruments):
raise MqValueError('The format of the backtest asset is incorrect.')
elif isinstance(instrument, self._supported_fx_instruments):
instrument.notional_amount *= notional_percentage / 100

instrument = self.check_underlier_fields(instrument)
trade_instruments.append(instrument)
self.__underliers.append(BacktestStrategyUnderlier(
instrument=instrument,
notional_percentage=notional_percentage,
hedge=BacktestStrategyUnderlierHedge(risk_details=delta_hedge),
market_model=market_model,
expiry_date_mode=expiry_date_mode))
# xasset backtesting service fields
self.__trades = (Trade(tuple(trade_instruments), roll_frequency, roll_frequency, quantity, quantity_type),)
self.__delta_hedge_frequency = '1b' if delta_hedge else None

backtest_parameters_class: Base = getattr(backtests, self.__backtest_type + 'BacktestParameters')
backtest_parameter_args = {
Expand All @@ -125,17 +126,39 @@ def __init__(self,
'index_initial_value': index_initial_value
}
self.__backtest_parameters = backtest_parameters_class.from_dict(backtest_parameter_args)
all_eq = all(isinstance(i, self._supported_eq_instruments) for i in trade_instruments)
all_fx = all(isinstance(i, self._supported_fx_instruments) for i in trade_instruments)
if not (all_eq or all_fx):
raise MqValueError('Cannot run backtests for different asset classes.')
self.__use_xasset_backtesting_service = all_fx

@staticmethod
def check_underlier_fields(
underlier: Union[EqOption, EqVarianceSwap]
) -> Union[EqOption, EqVarianceSwap]:
underlier: Instrument
) -> Instrument:

if isinstance(underlier, EqOption):
underlier.number_of_options = None

return underlier

def __run_service_based_backtest(self, start: datetime.date, end: datetime.date,
measures: Iterable[FlowVolBacktestMeasure]) -> BacktestResult:
date_cfg = DateConfig(start, end)
measures = tuple(m for m in measures if m != FlowVolBacktestMeasure.portfolio)
if not measures:
measures = (FlowVolBacktestMeasure.PNL,)
basic_bt_request = BasicBacktestRequest(date_cfg, self.__trades, measures, self.__delta_hedge_frequency,
CostPerTransaction(TransactionCostModel.Fixed, 0), None)
basic_bt_response = GsBacktestXassetApi.calculate_basic_backtest(basic_bt_request)
risks = tuple(
BacktestRisk(name=k.value,
timeseries=tuple(FieldValueMap(date=d, value=r.result) for d, r in v.items()))
for k, v in basic_bt_response.measures.items()
)
portfolio = None
return BacktestResult(risks=risks, portfolio=portfolio)

def backtest(
self,
start: datetime.date = None,
Expand All @@ -144,7 +167,8 @@ def backtest(
measures: Iterable[FlowVolBacktestMeasure] = (FlowVolBacktestMeasure.ALL_MEASURES,),
correlation_id: str = None
) -> Union[Backtest, BacktestResult]:

if self.__use_xasset_backtesting_service:
return self.__run_service_based_backtest(start, end, measures)
params_dict = self.__backtest_parameters.as_dict()
params_dict['measures'] = [m.value for m in measures]
backtest_parameters_class: Base = getattr(backtests, self.__backtest_type + 'BacktestParameters')
Expand Down
53 changes: 0 additions & 53 deletions gs_quant/backtests/strategy_systematic_factory.py

This file was deleted.

0 comments on commit d0d75a6

Please sign in to comment.