Skip to content

Commit f50bbb3

Browse files
release-0.9.17, batching logic, bug fixes (#230)
1 parent 7196745 commit f50bbb3

File tree

4 files changed

+114
-11
lines changed

4 files changed

+114
-11
lines changed

gs_quant/api/risk.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,13 @@ def execute_requests(outstanding_requests: queue.Queue,
159159
cls.shutdown_queue_listener(responses, loop=loop)
160160

161161
async def run_async():
162+
def num_risk_jobs(request: RiskRequest):
163+
# size of calculation job
164+
return len(request.pricing_and_market_data_as_of) * len(request.positions)
165+
162166
def num_risk_keys(request: RiskRequest):
163-
return len(request.pricing_and_market_data_as_of) * len(request.positions) * len(request.measures)
167+
# total number of risk calculations
168+
return num_risk_jobs(request) * len(request.measures)
164169

165170
is_async = not requests[0].wait_for_results
166171
loop = asyncio.get_event_loop()
@@ -179,7 +184,7 @@ def num_risk_keys(request: RiskRequest):
179184
# If async we need a task to handle result subscription
180185
results_handler = loop.create_task(cls.get_results(responses, raw_results, timeout=timeout))
181186

182-
expected = sum(num_risk_keys(r) for r in requests)
187+
expected = sum(num_risk_jobs(r) for r in requests)
183188
received = 0
184189
chunk_size = min(max_concurrent, expected)
185190
result_thread = None
@@ -198,7 +203,7 @@ def num_risk_keys(request: RiskRequest):
198203
while requests and dispatch_risk_keys < chunk_size:
199204
dispatch_request = requests.pop()
200205
dispatch_requests.append(dispatch_request)
201-
dispatch_risk_keys += num_risk_keys(dispatch_request)
206+
dispatch_risk_keys += num_risk_jobs(dispatch_request)
202207

203208
cls.enqueue(outstanding_requests, dispatch_requests, loop=loop)
204209

@@ -209,14 +214,15 @@ def num_risk_keys(request: RiskRequest):
209214
break
210215

211216
# Enable as many new requests as we've received results, to keep the outstanding number constant
212-
chunk_received = sum(num_risk_keys(request) for request, _ in completed)
213-
chunk_size = min(chunk_received, expected - received)
217+
risk_jobs_received = sum(num_risk_jobs(request) for request, _ in completed)
218+
chunk_size = min(risk_jobs_received, expected - received)
214219

215220
if progress_bar:
216-
progress_bar.update(chunk_received)
221+
risk_calcs_received = sum(num_risk_keys(request) for request, _ in completed)
222+
progress_bar.update(risk_calcs_received)
217223
progress_bar.refresh()
218224

219-
received += chunk_received
225+
received += risk_jobs_received
220226

221227
# Handle the results
222228
if unprocessed_results is not None:

gs_quant/datetime/time.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def __exit__(self, *args):
5959

6060

6161
class Tracer:
62+
__version = 0
6263
__stack_depth = 0
6364
__stack = []
6465

@@ -70,10 +71,13 @@ def __init__(self, label: str = 'Execution', print_on_exit: bool = False, thresh
7071
def __enter__(self):
7172
self.__start = dt.datetime.now()
7273
self.__index = len(Tracer.__stack)
74+
self.__version = Tracer.__version
7375
Tracer.__stack.append([dt.datetime.now(), 0.0, self.__label, Tracer.__stack_depth])
7476
Tracer.__stack_depth += 1
7577

7678
def __exit__(self, *args):
79+
if self.__version != Tracer.__version:
80+
return
7781
self.__elapsed = dt.datetime.now() - self.__start
7882
elapsed_sec = self.__elapsed.seconds + self.__elapsed.microseconds / 1000000
7983
Tracer.__stack[self.__index][1] = elapsed_sec
@@ -84,6 +88,10 @@ def __exit__(self, *args):
8488

8589
@staticmethod
8690
def reset():
91+
if Tracer.__stack_depth != 0:
92+
_logger.warning('Attempted to reset unfinished Trace!')
93+
Tracer.__version += 1
94+
8795
Tracer.__stack_depth = 0
8896
Tracer.__stack = []
8997

gs_quant/markets/securities.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ class SecurityIdentifier(EntityIdentifier):
231231
PRIMEID = "primeId"
232232
BBG = "bbg"
233233
ASSET_ID = "assetId"
234+
ANY = "identifiers"
234235

235236

236237
class ReturnType(Enum):
@@ -820,8 +821,9 @@ class Bond(Asset):
820821
def __init__(self,
821822
id_: str,
822823
name: str,
824+
asset_class: AssetClass = AssetClass.Credit,
823825
entity: Optional[Dict] = None):
824-
Asset.__init__(self, id_, AssetClass.Credit, name, entity=entity)
826+
Asset.__init__(self, id_, asset_class, name, entity=entity)
825827

826828
def get_type(self) -> AssetType:
827829
return AssetType.BOND
@@ -1141,7 +1143,7 @@ def __gs_asset_to_asset(cls, gs_asset: GsAsset) -> Asset:
11411143
return CommodityPowerAggregatedNodes(gs_asset.id, gs_asset.name, entity=asset_entity)
11421144

11431145
if asset_type in (GsAssetType.Bond.value,):
1144-
return Bond(gs_asset.id, gs_asset.name, entity=asset_entity)
1146+
return Bond(gs_asset.id, gs_asset.name, gs_asset.assetClass or AssetClass.Credit, entity=asset_entity)
11451147

11461148
if asset_type in (GsAssetType.Commodity.value,):
11471149
return Commodity(gs_asset.id, gs_asset.name, entity=asset_entity)
@@ -1504,7 +1506,7 @@ def get_asset_id_type(type_: SecurityIdentifier):
15041506

15051507
assert cls._source == SecurityMasterSource.SECURITY_MASTER
15061508
params = {
1507-
'identifiers': list(ids),
1509+
input_type.value: list(ids),
15081510
'toIdentifiers': [identifier.value for identifier in output_types],
15091511
'compact': True
15101512
}
@@ -1519,7 +1521,6 @@ def get_asset_id_type(type_: SecurityIdentifier):
15191521
if end_date is not None:
15201522
params['endDate'] = end_date
15211523
r = _get_with_retries('/markets/securities/map', params)
1522-
# TODO: pass input_type along (after updating endpoint implementation)
15231524

15241525
results = r['results']
15251526
if isinstance(results, dict):

gs_quant/test/markets/test_securities.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -885,6 +885,94 @@ def test_map_identifiers_empty(mocker):
885885
assert actual == {}
886886

887887

888+
def test_secmaster_map_identifiers_with_passed_input_types(mocker):
889+
start = str(dt.date(2021, 10, 11))
890+
end = str(dt.date(2021, 10, 12))
891+
892+
def mock_mapping_service_response_by_input_type(*args, **kwargs):
893+
'''
894+
Mocks Secmaster api's response json based on payload's input_type, output_type, and ids provided
895+
'''
896+
input_type = None
897+
for enum in SecurityIdentifier:
898+
if enum.value in kwargs['payload']:
899+
input_type = enum.value
900+
break
901+
output_types = kwargs['payload']['toIdentifiers']
902+
903+
mock_output = {'results': []}
904+
for id in kwargs['payload'][input_type]:
905+
for output_type in output_types:
906+
row = {
907+
"outputType": output_type,
908+
"outputValue": "mock output for " + id,
909+
"startDate": start,
910+
"endDate": end,
911+
"input": id
912+
}
913+
if output_type in (SecurityIdentifier.BBID, SecurityIdentifier.BBG, SecurityIdentifier.BCID):
914+
row['exchange'] = 'mock-exchange'
915+
row['compositeExchange'] = 'mock-comp'
916+
mock_output['results'].append(row)
917+
return mock_output
918+
919+
mocker.patch.object(GsSession.current, '_get',
920+
side_effect=mock_mapping_service_response_by_input_type)
921+
922+
with SecMasterContext():
923+
mock_any_ids = ["mock-any-1", "mock-any-2"]
924+
any_to_cusip_results = SecurityMaster.map_identifiers(input_type=SecurityIdentifier.ANY, ids=mock_any_ids,
925+
output_types=[SecurityIdentifier.CUSIP])
926+
assert start in any_to_cusip_results.keys()
927+
for input_id in mock_any_ids:
928+
assert input_id in any_to_cusip_results[start].keys()
929+
assert SecurityIdentifier.CUSIP.value in any_to_cusip_results[start][input_id].keys()
930+
assert any_to_cusip_results == {
931+
"2021-10-11": {
932+
"mock-any-1": {
933+
"cusip": "mock output for mock-any-1"
934+
},
935+
"mock-any-2": {
936+
"cusip": "mock output for mock-any-2"
937+
}
938+
},
939+
"2021-10-12": {
940+
"mock-any-1": {
941+
"cusip": "mock output for mock-any-1"
942+
},
943+
"mock-any-2": {
944+
"cusip": "mock output for mock-any-2"
945+
}
946+
}
947+
}
948+
949+
mock_cusip_ids = ["mock-cusip-input1", "mock-cusip-input2"]
950+
cusip_to_isin_result = SecurityMaster.map_identifiers(input_type=SecurityIdentifier.CUSIP, ids=mock_cusip_ids,
951+
output_types=[SecurityIdentifier.ISIN])
952+
assert start in cusip_to_isin_result.keys()
953+
for cusip_input_id in mock_cusip_ids:
954+
assert cusip_input_id in cusip_to_isin_result[start].keys()
955+
assert SecurityIdentifier.ISIN.value in cusip_to_isin_result[start][cusip_input_id].keys()
956+
assert cusip_to_isin_result == {
957+
"2021-10-11": {
958+
"mock-cusip-input1": {
959+
"isin": "mock output for mock-cusip-input1"
960+
},
961+
"mock-cusip-input2": {
962+
"isin": "mock output for mock-cusip-input2"
963+
}
964+
},
965+
"2021-10-12": {
966+
"mock-cusip-input1": {
967+
"isin": "mock output for mock-cusip-input1"
968+
},
969+
"mock-cusip-input2": {
970+
"isin": "mock output for mock-cusip-input2"
971+
}
972+
}
973+
}
974+
975+
888976
def test_map_identifiers_asset_service(mocker):
889977
response = {'AAPL UN': ['AAPL.N'], 'GS UN': ['GS.N']}
890978
mocker.patch.object(GsAssetApi, 'map_identifiers', side_effect=lambda *arg, **kwargs: response)

0 commit comments

Comments
 (0)