Skip to content

Commit

Permalink
Chore: Make release 1.0.49
Browse files Browse the repository at this point in the history
  • Loading branch information
martinroberson authored and Vanden Bon, David V [GBM Public] committed Dec 6, 2023
1 parent d5bfb5a commit 4e412e3
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 29 deletions.
17 changes: 16 additions & 1 deletion gs_quant/target/common.py

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion gs_quant/target/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ class DataSetType(EnumBase, Enum):
"""Type of the dataset"""

PlotTool_Pro = 'PlotTool Pro'
Alloy = 'Alloy'
Alloy = 'Alloy'
Snowflake = 'Snowflake'


class DelayExclusionType(EnumBase, Enum):
Expand Down Expand Up @@ -521,6 +522,7 @@ class DataSetParameters(Base):
support_distribution_list: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata)
apply_market_data_entitlements: Optional[bool] = field(default=None, metadata=field_metadata)
upload_data_policy: Optional[str] = field(default=None, metadata=field_metadata)
database_id: Optional[str] = field(default=None, metadata=field_metadata)
logical_db: Optional[str] = field(default=None, metadata=field_metadata)
symbol_strategy: Optional[str] = field(default=None, metadata=field_metadata)
underlying_data_set_id: Optional[str] = field(default=None, metadata=field_metadata)
Expand Down
14 changes: 0 additions & 14 deletions gs_quant/target/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,6 @@ class UserCoverage(Base):
guid: Optional[str] = field(default=None, metadata=field_metadata)


@handle_camel_case_args
@dataclass_json(letter_case=LetterCase.CAMEL)
@dataclass(unsafe_hash=True, repr=False)
class UserTag(Base):
name: str = field(default=None, metadata=field_metadata)
added_on: Optional[datetime.datetime] = field(default=None, metadata=field_metadata)
added_by_id: Optional[str] = field(default=None, metadata=field_metadata)
removed: Optional[bool] = field(default=None, metadata=field_metadata)
removed_on: Optional[datetime.datetime] = field(default=None, metadata=field_metadata)
removed_by_id: Optional[str] = field(default=None, metadata=field_metadata)
removal_reason: Optional[str] = field(default=None, metadata=field_metadata)
category: Optional[str] = field(default=None, metadata=field_metadata)


@handle_camel_case_args
@dataclass_json(letter_case=LetterCase.CAMEL)
@dataclass(unsafe_hash=True, repr=False)
Expand Down
1 change: 1 addition & 0 deletions gs_quant/target/indices.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,7 @@ class CustomBasketsRebalanceInputs(Base):
allow_in_position_rebalance: Optional[bool] = field(default=False, metadata=field_metadata)
action_date: Optional[datetime.date] = field(default=None, metadata=field_metadata)
preferred_risk_model: Optional[str] = field(default=None, metadata=field_metadata)
on_behalf_of: Optional[str] = field(default=None, metadata=field_metadata)
name: Optional[str] = field(default=None, metadata=name_metadata)


Expand Down
2 changes: 2 additions & 0 deletions gs_quant/target/positions_v2_pricing.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class PositionsPricingParameters(Base):
weighting_strategy: str = field(default=None, metadata=field_metadata)
carryover_positions_for_missing_dates: Optional[bool] = field(default=False, metadata=field_metadata)
should_reweight: Optional[bool] = field(default=False, metadata=field_metadata)
allow_fractional_shares: Optional[bool] = field(default=True, metadata=field_metadata)
name: Optional[str] = field(default=None, metadata=name_metadata)


Expand All @@ -40,6 +41,7 @@ class PositionsRequest(Base):
asset_id: str = field(default=None, metadata=field_metadata)
weight: Optional[float] = field(default=None, metadata=field_metadata)
quantity: Optional[float] = field(default=None, metadata=field_metadata)
notional: Optional[float] = field(default=None, metadata=field_metadata)
tags: Optional[Tuple[PositionTag, ...]] = field(default=None, metadata=field_metadata)
name: Optional[str] = field(default=None, metadata=name_metadata)

Expand Down
1 change: 1 addition & 0 deletions gs_quant/target/secmaster.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ class SecMasterGetRequestPathSchema(Base):
fields: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata)
as_of_time: Optional[Tuple[datetime.datetime, ...]] = field(default=None, metadata=field_metadata)
is_primary: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata)
all_listings: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata)
effective_date: Optional[Tuple[datetime.date, ...]] = field(default=None, metadata=field_metadata)
limit: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata)
offset: Optional[Tuple[str, ...]] = field(default=None, metadata=field_metadata)
Expand Down
49 changes: 41 additions & 8 deletions gs_quant/test/timeseries/test_measures_reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
{
'date': '2020-11-23',
'reportId': 'report_id',
'factor': 'factor_id',
'factor': 'Factor Name',
'factorCategory': 'CNT',
'pnl': 11.23,
'exposure': -11.23,
Expand All @@ -73,7 +73,7 @@
{
'date': '2020-11-24',
'reportId': 'report_id',
'factor': 'factor_id',
'factor': 'Factor Name',
'factorCategory': 'CNT',
'pnl': 11.24,
'exposure': -11.24,
Expand All @@ -82,11 +82,38 @@
{
'date': '2020-11-25',
'reportId': 'report_id',
'factor': 'factor_id',
'factor': 'Factor Name',
'factorCategory': 'CNT',
'pnl': 11.25,
'exposure': -11.25,
'proportionOfRisk': 3
},
{
'date': '2020-11-23',
'reportId': 'report_id',
'factor': 'Total',
'factorCategory': 'CNT',
'pnl': 19.23,
'exposure': -11.23,
'proportionOfRisk': 1
},
{
'date': '2020-11-24',
'reportId': 'report_id',
'factor': 'Total',
'factorCategory': 'CNT',
'pnl': 14.24,
'exposure': -11.24,
'proportionOfRisk': 2
},
{
'date': '2020-11-25',
'reportId': 'report_id',
'factor': 'Total',
'factorCategory': 'CNT',
'pnl': 21.25,
'exposure': -11.25,
'proportionOfRisk': 3
}
]

Expand Down Expand Up @@ -499,7 +526,7 @@ def test_factor_pnl():
factor_data_copy.insert(0, {
'date': '2020-11-22',
'reportId': 'report_id',
'factor': 'factor_id',
'factor': 'Factor Name',
'factorCategory': 'CNT',
'pnl': 0,
'exposure': -11.23,
Expand All @@ -519,6 +546,8 @@ def test_factor_pnl():
def test_factor_pnl_percent():
replace = Replacer()

aum = {'2020-11-22': 200, '2020-11-23': 400, '2020-11-24': 400, '2020-11-25': 400}

# mock getting risk model entity()
mock = replace('gs_quant.api.gs.risk_models.GsRiskModelApi.get_risk_model', Mock())
mock.return_value = risk_model
Expand Down Expand Up @@ -553,10 +582,11 @@ def test_factor_pnl_percent():
'factorCategory': 'Factor Name'
}]

pnl = {entry['date']: entry['pnl'] for entry in factor_data}
aum = {'2020-11-22': 200, '2020-11-23': 400, '2020-11-24': 400, '2020-11-25': 400}
mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock())
mock.return_value = aum

# Each team uses pnl today/aum yesterday
expected_values = compute_geometric_aggregation_calculations(aum, pnl, list(aum.keys()))
expected_values = [0, 0, 3.022984004, 6.0231948]

with DataContext(datetime.date(2020, 11, 23), datetime.date(2020, 11, 25)):
# mock getting aum source
Expand Down Expand Up @@ -1074,7 +1104,7 @@ def test_pnl_percent():

# mock PerformanceReport.get_pnl()
mock = replace('gs_quant.markets.report.PerformanceReport.get_pnl', Mock())
mock.return_value = pandas.DataFrame.from_dict({'date': pnl.keys(), 'pnl': pnl.values()})
mock.return_value = pandas.DataFrame.from_dict({'date': list(pnl.keys()), 'pnl': list(pnl.values())})

mock = replace('gs_quant.markets.report.PerformanceReport.get_aum_source', Mock())
mock.return_value = RiskAumSource.Long
Expand All @@ -1083,6 +1113,9 @@ def test_pnl_percent():
mock = replace('gs_quant.markets.portfolio_manager.PortfolioManager.get_performance_report', Mock())
mock.return_value = PerformanceReport(id='ID')

mock = replace('gs_quant.markets.report.PerformanceReport.get_aum', Mock())
mock.return_value = aum

# Each team uses pnl today/aum yesterday
expected_values = compute_geometric_aggregation_calculations(aum, pnl, list(aum.keys()))

Expand Down
45 changes: 40 additions & 5 deletions gs_quant/timeseries/measures_reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from typing import Optional

import pandas as pd
import numpy as np
from pandas.tseries.offsets import BDay
from pydash import decapitalize

Expand Down Expand Up @@ -352,14 +353,16 @@ def _get_factor_data(report_id: str, factor_name: str, query_type: QueryType, un
col_name = query_type.value.replace(' ', '')
col_name = decapitalize(col_name)
data_type = decapitalize(col_name[6:]) if col_name.startswith('factor') else col_name

include_total_factor = factor_name != 'Total' and unit == Unit.PERCENT and query_type == QueryType.FACTOR_PNL
factors_to_query = [factor_name, 'Total'] if include_total_factor else [factor_name]
factor_data = report.get_results(
factors=[factor_name],
factors=factors_to_query,
start_date=DataContext.current.start_date,
end_date=DataContext.current.end_date,
return_format=ReturnFormat.JSON
)
factor_data = [d for d in factor_data if d.get(data_type) is not None]
total_data = [d for d in factor_data if d.get(data_type) is not None and d.get('factor') == 'Total']
factor_data = [d for d in factor_data if d.get(data_type) is not None and d.get('factor') == factor_name]
if unit == Unit.PERCENT:
if report.position_source_type != PositionSourceType.Portfolio:
raise MqValueError('Unit can only be set to percent for portfolio reports')
Expand All @@ -372,9 +375,11 @@ def _get_factor_data(report_id: str, factor_name: str, query_type: QueryType, un
aum_as_dict = performance_report.get_aum(start_date=prev_business_date(start_date), end_date=end_date)
aum_df = pd.DataFrame(aum_as_dict.items(), columns=['date', 'aum'])
pnl_df = pd.DataFrame(factor_data)[['date', 'pnl']]
total_returns_df = pd.DataFrame(total_data)[['date', 'pnl']]
total_returns_df = total_returns_df.rename(columns={'pnl': 'totalPnl'})
pnl_df = pd.merge(pnl_df, total_returns_df, how='inner', on=['date'])
is_first_data_point_on_start_date = pnl_df['date'].iloc[[0]].values[0] == start_date.strftime('%Y-%m-%d')
return_series = _generate_daily_returns(aum_df, pnl_df, 'aum', 'pnl', is_first_data_point_on_start_date)
return geometrically_aggregate(return_series).multiply(100)
return _generate_daily_returns(aum_df, pnl_df, 'aum', 'pnl', is_first_data_point_on_start_date)
else:
aum = performance_report.get_aum(start_date=start_date, end_date=end_date)
for data in factor_data:
Expand Down Expand Up @@ -421,5 +426,35 @@ def _generate_daily_returns(aum_df: pd.DataFrame, pnl_df: pd.DataFrame, aum_col_
df.sort_index(inplace=True)
df.loc[:, [aum_col_key]] = df.loc[:, [aum_col_key]].fillna(method='ffill')
df['return'] = df[pnl_col_key].div(df[aum_col_key].shift(1))
if 'totalPnl' in list(df.columns.values):
df['totalPnl'] = df['totalPnl'].div(df[aum_col_key].shift(1))
df = df.fillna(0)
df['return'] = __smooth_percent_returns(df['return'].to_numpy(), df['totalPnl'].to_numpy()).tolist()
return_series = pd.Series(df['return'], name="return").dropna()
return return_series


def __smooth_percent_returns(daily_factor_returns: np.array, daily_total_returns: np.array) -> np.array:
"""
When attribution (in weights) are decomposed among multiple factors (like a group of risk model factors or
categories), simple geometric aggregation will not preserve additivity. In other words, the geometric sum of factor
PnL from Factor and Specific will NOT add up to the factor PnL from Total. The Carino log linking formula
calculates the coefficients for each node, and after that multiplication is done, the results can be aggregated
to calculate cumulative PnL:
https://rdrr.io/github/R-Finance/PortfolioAttribution/man/Carino.html
βt = A * αt
where βt is the linking coefficient on date t
where A is the log scaling factor (which is constant throughout the timeseries)
where αt is the perturbation factor on date t
A = (Rp - Rb) / (ln(1 + Rp) - ln(1 + Rb)) where Rp is the total portfolio return ; Rb is the total benchmark return
αt = (ln(1 + Rpt) - ln(1 + Rbt)) / (Rpt - Rbt) where Rpt is portfolio return on t and Rbp is benchmark return on t
For this use case benchmark returns are set to 0 for every day.
"""
total_return = np.prod(daily_total_returns + 1) - 1
log_scaling_factor = (total_return) / (np.log(1 + total_return)) if total_return != 0 else 1
perturbation_factors = np.log(1 + daily_total_returns) / daily_total_returns
perturbation_factors = np.nan_to_num(perturbation_factors, nan=1)
return np.cumsum(daily_factor_returns * log_scaling_factor * perturbation_factors * 100)

0 comments on commit 4e412e3

Please sign in to comment.