Skip to content

Commit 538920d

Browse files
martinrobersonpangst-888
authored andcommitted
Chore: Make release 1.4.45
1 parent 284d90b commit 538920d

File tree

14 files changed

+1234
-101
lines changed

14 files changed

+1234
-101
lines changed

gs_quant/api/gs/risk.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,14 @@
2424
import time
2525
from socket import gaierror
2626
from typing import Iterable, Optional, Union
27+
import re
28+
2729

2830
import msgpack
2931
from websockets import ConnectionClosed
3032

3133
from gs_quant.api.risk import RiskApi
34+
from gs_quant.errors import MqValueError
3235
from gs_quant.risk import RiskRequest
3336
from gs_quant.target.risk import OptimizationRequest
3437
from gs_quant.tracing import Tracer, TracingSpan
@@ -333,3 +336,82 @@ def get_pretrade_execution_optimization(cls, optimization_id: str, max_attempts:
333336
else:
334337
_logger.info('Optimization is fetched in {:.3f}s.'.format(time.perf_counter() - start))
335338
return results
339+
340+
@classmethod
341+
def get_liquidity_and_factor_analysis(cls,
342+
positions: list,
343+
risk_model: str,
344+
date: dt.date,
345+
currency: str = 'USD',
346+
participation_rate: float = 0.1,
347+
measures: Optional[list] = None,
348+
notional: Optional[float] = None,
349+
time_series_benchmark_ids: Optional[list] = None):
350+
"""
351+
Get liquidity and factor analysis for a portfolio using the /risk/liquidity endpoint.
352+
353+
:param positions: List of positions with assetId and quantity/weight
354+
:param risk_model: Risk model identifier (e.g., 'BARRA_EFM_USALTL', 'AXIOMA_AXUS4S')
355+
:param date: Analysis date
356+
:param currency: Currency for analysis (default: USD)
357+
:param participation_rate: Market participation rate (default: 0.1 = 10%)
358+
:param measures: List of measures to include (default: all available measures)
359+
:param notional: Optional reference notional
360+
:param time_series_benchmark_ids: Optional benchmark IDs for time series comparison
361+
:return: Dictionary with liquidity and factor analysis results
362+
"""
363+
if measures is None:
364+
measures = [
365+
"Time Series Data",
366+
"Risk Buckets",
367+
"Factor Risk Buckets",
368+
"Factor Exposure Buckets",
369+
"Exposure Buckets"
370+
]
371+
372+
payload = {
373+
"currency": currency,
374+
"date": date.isoformat() if isinstance(date, dt.date) else date,
375+
"positions": positions,
376+
"participationRate": participation_rate,
377+
"riskModel": risk_model,
378+
"timeSeriesBenchmarkIds": time_series_benchmark_ids or [],
379+
"measures": measures
380+
}
381+
382+
if notional is not None:
383+
payload["notional"] = notional
384+
385+
try:
386+
response = cls.get_session()._post('/risk/liquidity', payload)
387+
388+
if isinstance(response, dict) and 'errorMessage' in response:
389+
error_msg = response['errorMessage']
390+
391+
asset_ids_pattern = (r'Assets with the following ids are missing in marquee:'
392+
r'\s*\[\s*([^\]]+)\s*\]')
393+
asset_ids_match = re.search(asset_ids_pattern, error_msg, re.IGNORECASE)
394+
if asset_ids_match:
395+
clean_error_pattern = (r'(Assets with the following ids are missing in '
396+
r'marquee:\s*\[[^\]]+\])')
397+
clean_error_line = re.search(clean_error_pattern, error_msg, re.IGNORECASE)
398+
if clean_error_line:
399+
clean_message = (f"ERROR: liquidity analysis failed\n"
400+
f"{clean_error_line.group(1)}")
401+
_logger.error(clean_message)
402+
raise MqValueError(clean_message)
403+
else:
404+
missing_assets = asset_ids_match.group(1).strip()
405+
clean_message = (f"ERROR: liquidity analysis failed\n"
406+
f"Assets with the following ids are missing in marquee: "
407+
f"[ {missing_assets} ]")
408+
_logger.error(clean_message)
409+
raise MqValueError(clean_message)
410+
else:
411+
_logger.error(f'Liquidity analysis failed: {error_msg}')
412+
raise MqValueError("ERROR: liquidity analysis failed")
413+
414+
_logger.info('Liquidity analysis completed successfully')
415+
return response
416+
except Exception:
417+
raise

gs_quant/backtests/strategy_systematic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def __init__(self,
7070
cash_accrual: bool = True,
7171
combine_roll_signal_entries: bool = False,
7272
transaction_cost_config: TransactionCostConfig = None,
73-
use_xasset_backtesting_service: bool = False):
73+
use_xasset_backtesting_service: bool = True):
7474
self.__cost_netting = cost_netting
7575
self.__currency = get_enum_value(Currency, currency)
7676
self.__name = name

gs_quant/datetime/relative_date.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,14 +132,14 @@ def __handle_rule(self,
132132
exchanges: List[Union[ExchangeCode, str]] = None,
133133
holiday_calendar: List[dt.date] = None,
134134
**kwargs) -> dt.date:
135-
roll = None
135+
sign = "+"
136136
if rule.startswith('-'):
137137
index = 1
138138
while index != len(rule) and rule[index].isdigit():
139139
index += 1
140140
number = int(rule[1:index]) * -1 if index < len(rule) else 0
141141
rule_str = rule[index]
142-
roll = "preceding"
142+
sign = "-"
143143
else:
144144
index = 0
145145
if not rule[0].isdigit():
@@ -158,6 +158,8 @@ def __handle_rule(self,
158158
if not rule_str:
159159
raise MqValueError(f'Invalid rule "{rule}"')
160160

161+
roll = kwargs.get('roll_convention')
162+
161163
try:
162164
rule_class = getattr(rules, f'{rule_str}Rule')
163165
return rule_class(result,
@@ -168,7 +170,8 @@ def __handle_rule(self,
168170
exchanges=exchanges,
169171
holiday_calendar=holiday_calendar,
170172
usd_calendar=kwargs.get('usd_calendar'),
171-
roll=roll).handle()
173+
roll=roll,
174+
sign=sign).handle()
172175
except AttributeError:
173176
raise NotImplementedError(f'Rule {rule} not implemented')
174177

gs_quant/datetime/rules.py

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import calendar
1919
import logging
2020
from abc import ABC, abstractmethod
21-
from typing import List, Union
21+
from typing import List, Union, Optional
2222

2323
from dateutil.relativedelta import relativedelta, FR, SA, SU, TH, TU, WE, MO
2424
import numpy as np
@@ -38,6 +38,7 @@ class RDateRule(ABC):
3838
currencies: List[Union[Currency, str]] = None
3939
exchanges: List[Union[ExchangeCode, str]] = None
4040
holiday_calendar: List[dt.date] = None
41+
sign: Optional[str] = None
4142

4243
def __init__(self, result: dt.date, **params):
4344
self.result = result
@@ -47,6 +48,8 @@ def __init__(self, result: dt.date, **params):
4748
self.exchanges = params.get('exchanges')
4849
self.holiday_calendar = params.get('holiday_calendar')
4950
self.usd_calendar = params.get('usd_calendar')
51+
self.roll = params.get('roll')
52+
self.sign = params.get('sign')
5053
super().__init__()
5154

5255
@abstractmethod
@@ -98,6 +101,9 @@ def add_years(self, holidays: List[dt.date]):
98101
def is_weekend(d: dt.date):
99102
return False if d.weekday() < 5 else True # 5 Sat, 6 Sun
100103

104+
def roll_convention(self, default=None):
105+
return self.roll or default
106+
101107

102108
class ARule(RDateRule):
103109
def handle(self) -> dt.date:
@@ -108,7 +114,7 @@ def handle(self) -> dt.date:
108114
class bRule(RDateRule):
109115
def handle(self) -> dt.date:
110116
holidays = self._get_holidays()
111-
roll = 'forward' if self.number <= 0 else 'preceding'
117+
roll = self.roll_convention('forward' if self.number <= 0 else 'preceding')
112118
return self._apply_business_days_logic(holidays, offset=self.number, roll=roll)
113119

114120

@@ -132,7 +138,7 @@ class gRule(RDateRule):
132138
def handle(self) -> dt.date:
133139
self.result = self.result + relativedelta(weeks=self.number)
134140
holidays = self._get_holidays()
135-
return self._apply_business_days_logic(holidays, offset=0)
141+
return self._apply_business_days_logic(holidays, offset=0, roll=self.roll_convention('backward'))
136142

137143

138144
class NRule(RDateRule):
@@ -161,13 +167,13 @@ def handle(self) -> dt.date:
161167
while self.week_mask[self.result.isoweekday() - 1] == '0':
162168
self.result += relativedelta(days=1)
163169
holidays = self._get_holidays()
164-
return self._apply_business_days_logic(holidays, offset=0)
170+
return self._apply_business_days_logic(holidays, offset=0, roll=self.roll_convention('backward'))
165171

166172

167173
class mRule(RDateRule):
168174
def handle(self) -> dt.date:
169175
self.result = self.result + relativedelta(months=self.number)
170-
return self._apply_business_days_logic(self._get_holidays(), offset=0, roll='forward')
176+
return self._apply_business_days_logic(self._get_holidays(), offset=0, roll=self.roll_convention('forward'))
171177

172178

173179
class MRule(RDateRule):
@@ -201,16 +207,9 @@ def handle(self) -> dt.date:
201207

202208

203209
class uRule(RDateRule):
204-
def __init__(self, result: dt.date, **params):
205-
super().__init__(result, **params)
206-
self.roll = params.get('roll')
207-
208210
def handle(self) -> dt.date:
209211
holidays = self._get_holidays()
210-
if self.number == 0 and self.roll:
211-
roll = self.roll
212-
else:
213-
roll = 'forward' if self.number <= 0 else 'preceding'
212+
roll = 'preceding' if self.sign == "-" and self.number == 0 else 'forward' if self.number <= 0 else 'preceding'
214213
return self._apply_business_days_logic(holidays, offset=self.number, roll=roll)
215214

216215

@@ -225,7 +224,7 @@ def handle(self) -> dt.date:
225224
month_range = calendar.monthrange(self.result.year, self.result.month)
226225
self.result = self.result.replace(day=month_range[1])
227226
holidays = self._get_holidays()
228-
return self._apply_business_days_logic(holidays, offset=0, roll='backward')
227+
return self._apply_business_days_logic(holidays, offset=0, roll=self.roll_convention('backward'))
229228

230229

231230
class VRule(RDateRule):
@@ -242,15 +241,16 @@ class wRule(RDateRule):
242241
def handle(self) -> dt.date:
243242
self.result = self.result + relativedelta(weeks=self.number)
244243
holidays = self._get_holidays()
245-
return self._apply_business_days_logic(holidays, offset=0)
244+
roll = 'forward' if self.number >= 0 else 'backward'
245+
return self._apply_business_days_logic(holidays, offset=0, roll=self.roll_convention(roll))
246246

247247

248248
class xRule(RDateRule):
249249
def handle(self) -> dt.date:
250250
month_range = calendar.monthrange(self.result.year, self.result.month)
251251
self.result = self.result.replace(day=month_range[1])
252252
holidays = self._get_holidays()
253-
return self._apply_business_days_logic(holidays, offset=0, roll='backward')
253+
return self._apply_business_days_logic(holidays, offset=0, roll=self.roll_convention('backward'))
254254

255255

256256
class XRule(RDateRule):
@@ -264,7 +264,7 @@ def handle(self) -> dt.date:
264264
while self.week_mask[self.result.isoweekday() - 1] == '0':
265265
self.result += relativedelta(days=1)
266266
holidays = self._get_holidays()
267-
return self._apply_business_days_logic(holidays, offset=0)
267+
return self._apply_business_days_logic(holidays, offset=0, roll=self.roll_convention('backward'))
268268

269269

270270
class ZRule(RDateRule):

0 commit comments

Comments
 (0)