|
| 1 | +import time |
| 2 | +import os |
| 3 | +import uuid |
| 4 | +import pandas as pd |
| 5 | + |
| 6 | +from fds.analyticsapi.engines import ApiException |
| 7 | +from fds.analyticsapi.engines.api.pa_calculations_api import PACalculationsApi |
| 8 | +from fds.analyticsapi.engines.api_client import ApiClient |
| 9 | +from fds.analyticsapi.engines.configuration import Configuration |
| 10 | +from fds.analyticsapi.engines.model.pa_calculation_parameters_root import PACalculationParametersRoot |
| 11 | +from fds.analyticsapi.engines.model.pa_calculation_parameters import PACalculationParameters |
| 12 | +from fds.analyticsapi.engines.model.pa_date_parameters import PADateParameters |
| 13 | +from fds.analyticsapi.engines.model.pa_identifier import PAIdentifier |
| 14 | +from fds.protobuf.stach.extensions.StachVersion import StachVersion |
| 15 | +from fds.protobuf.stach.extensions.StachExtensionFactory import StachExtensionFactory |
| 16 | +from fds.analyticsapi.engines.api.templated_pa_components_api import TemplatedPAComponentsApi |
| 17 | +from fds.analyticsapi.engines.api.linked_pa_templates_api import LinkedPATemplatesApi |
| 18 | +from fds.analyticsapi.engines.model.templated_pa_component_parameters import TemplatedPAComponentParameters |
| 19 | +from fds.analyticsapi.engines.model.templated_pa_component_parameters_root import TemplatedPAComponentParametersRoot |
| 20 | +from fds.analyticsapi.engines.model.pa_component_data import PAComponentData |
| 21 | +from fds.analyticsapi.engines.model.pa_date_parameters import PADateParameters |
| 22 | +from fds.analyticsapi.engines.model.pa_identifier import PAIdentifier |
| 23 | +from fds.analyticsapi.engines.model.pa_calculation_group import PACalculationGroup |
| 24 | +from fds.analyticsapi.engines.model.pa_calculation_column import PACalculationColumn |
| 25 | +from fds.analyticsapi.engines.model.linked_pa_template_parameters import LinkedPATemplateParameters |
| 26 | +from fds.analyticsapi.engines.model.template_content_types import TemplateContentTypes |
| 27 | +from fds.analyticsapi.engines.model.linked_pa_template_parameters_root import LinkedPATemplateParametersRoot |
| 28 | +from fds.analyticsapi.engines.api.components_api import ComponentsApi |
| 29 | +from fds.analyticsapi.engines.model.component_summary import ComponentSummary |
| 30 | +from fds.analyticsapi.engines.api.columns_api import ColumnsApi |
| 31 | +from fds.analyticsapi.engines.api.column_statistics_api import ColumnStatisticsApi |
| 32 | +from fds.analyticsapi.engines.model.column_statistic import ColumnStatistic |
| 33 | +from fds.analyticsapi.engines.api.groups_api import GroupsApi |
| 34 | +from fds.analyticsapi.engines.model.group import Group |
| 35 | + |
| 36 | +from urllib3 import Retry |
| 37 | + |
| 38 | +host = os.environ['FACTSET_HOST'] |
| 39 | +fds_username = os.environ['FACTSET_USERNAME'] |
| 40 | +fds_api_key = os.environ['FACTSET_API_KEY'] |
| 41 | +proxy_url = os.environ['PROXY_URL'] |
| 42 | + |
| 43 | +def main(): |
| 44 | + config = Configuration() |
| 45 | + config.host = host |
| 46 | + config.username = fds_username |
| 47 | + config.password = fds_api_key |
| 48 | + config.discard_unknown_keys = True |
| 49 | + # add proxy and/or disable ssl verification according to your development environment |
| 50 | + # config.proxy = proxy_url |
| 51 | + config.verify_ssl = False |
| 52 | + |
| 53 | + # Setting configuration to retry api calls on http status codes of 429 and 503. |
| 54 | + config.retries = Retry(total=3, status=3, status_forcelist=frozenset([429, 503]), backoff_factor=2, |
| 55 | + raise_on_status=False) |
| 56 | + |
| 57 | + api_client = ApiClient(configuration=config) |
| 58 | + |
| 59 | + try: |
| 60 | + column_name = "Port. Average Weight" |
| 61 | + column_category = "Portfolio/Position Data" |
| 62 | + column_statistic_name = "Active Weights" |
| 63 | + |
| 64 | + group_category = "JP Morgan CEMBI " |
| 65 | + group_name = "Country - JP Morgan CEMBI " |
| 66 | + |
| 67 | + component_document = "PA_DOCUMENTS:DEFAULT" |
| 68 | + component_name = "Weights" |
| 69 | + component_category = "Weights / Exposures" |
| 70 | + |
| 71 | + linked_pa_template_directory = "Personal:LinkedPATemplates/" |
| 72 | + templated_pa_component_directory = "Personal:TemplatedPAComponents/" |
| 73 | + |
| 74 | + portfolio = "BENCH:SP50" |
| 75 | + benchmark = "BENCH:R.1000" |
| 76 | + startdate = "20180101" |
| 77 | + enddate = "20181231" |
| 78 | + frequency = "Monthly" |
| 79 | + currencyisocode = "USD" |
| 80 | + componentdetail = "GROUPS" |
| 81 | + directory = "Factset" |
| 82 | + |
| 83 | + # uncomment the below code line to setup cache control; max-stale=0 will be a fresh adhoc run and the max-stale value is in seconds. |
| 84 | + # Results are by default cached for 12 hours; Setting max-stale=300 will fetch a cached result which is 5 minutes older. |
| 85 | + # cache_control = "max-stale=0" |
| 86 | + |
| 87 | + # get column id |
| 88 | + columns_api = ColumnsApi(api_client=api_client) |
| 89 | + column = columns_api.get_pa_columns( |
| 90 | + name = column_name, |
| 91 | + category = column_category, |
| 92 | + directory = directory |
| 93 | + ) |
| 94 | + column_id = list(column[0].data.keys())[0] |
| 95 | + |
| 96 | + # get column statistics id |
| 97 | + column_statistics_api = ColumnStatisticsApi(api_client=api_client) |
| 98 | + get_all_column_statistics = column_statistics_api.get_pa_column_statistics() |
| 99 | + column_statistic_id = [id for id in list( |
| 100 | + get_all_column_statistics[0].data.keys()) if get_all_column_statistics[0].data[id].name == column_statistic_name][0] |
| 101 | + |
| 102 | + # create columns parameter |
| 103 | + columns = [PACalculationColumn(id=column_id, statistics=[column_statistic_id])] |
| 104 | + |
| 105 | + # get group id |
| 106 | + groups_api = GroupsApi(api_client=api_client) |
| 107 | + groups = groups_api.get_pa_groups() |
| 108 | + group_id = [id for id in list( |
| 109 | + groups[0].data.keys()) if groups[0].data[id].category == group_category and |
| 110 | + groups[0].data[id].directory == directory and |
| 111 | + groups[0].data[id].name == group_name][0] |
| 112 | + |
| 113 | + # create groups parameter |
| 114 | + groups = [PACalculationGroup(id=group_id)] |
| 115 | + |
| 116 | + # get parent component id |
| 117 | + components_api = ComponentsApi(api_client=api_client) |
| 118 | + components = components_api.get_pa_components(document=component_document) |
| 119 | + parent_component_id = [id for id in list( |
| 120 | + components[0].data.keys()) if components[0].data[id].name == component_name and |
| 121 | + components[0].data[id].category == component_category][0] |
| 122 | + |
| 123 | + # create a linked PA template |
| 124 | + linked_pa_template_parameters = LinkedPATemplateParameters( |
| 125 | + directory=linked_pa_template_directory, |
| 126 | + parent_component_id=parent_component_id, |
| 127 | + description="This is a linked PA template that only returns security level data", |
| 128 | + content = TemplateContentTypes( |
| 129 | + mandatory = ["accounts", "benchmarks"], |
| 130 | + optional = ["groups", "columns","currencyisocode", "componentdetail"], |
| 131 | + locked = ["dates"] |
| 132 | + ) |
| 133 | + ) |
| 134 | + |
| 135 | + linked_pa_template_parameters_root = LinkedPATemplateParametersRoot( |
| 136 | + data = linked_pa_template_parameters |
| 137 | + ) |
| 138 | + |
| 139 | + linked_pa_template_api = LinkedPATemplatesApi(api_client=api_client) |
| 140 | + |
| 141 | + response = linked_pa_template_api.create_linked_pa_templates( |
| 142 | + linked_pa_template_parameters_root = linked_pa_template_parameters_root) |
| 143 | + |
| 144 | + # create a templated component |
| 145 | + parent_template_id = response[0].data.get("id") |
| 146 | + |
| 147 | + templated_pa_component_parameters = TemplatedPAComponentParameters( |
| 148 | + directory=templated_pa_component_directory, |
| 149 | + parent_template_id=parent_template_id, |
| 150 | + description="This is a templated PA component", |
| 151 | + component_data = PAComponentData( |
| 152 | + accounts = [PAIdentifier(id=portfolio)], |
| 153 | + benchmarks = [PAIdentifier(id=benchmark)], |
| 154 | + columns = columns, |
| 155 | + groups = groups, |
| 156 | + currencyisocode = currencyisocode, |
| 157 | + componentdetail = componentdetail |
| 158 | + ) |
| 159 | + ) |
| 160 | + |
| 161 | + templated_pa_component_parameters_root = TemplatedPAComponentParametersRoot( |
| 162 | + data = templated_pa_component_parameters |
| 163 | + ) |
| 164 | + |
| 165 | + templated_pa_components_api = TemplatedPAComponentsApi(api_client=api_client) |
| 166 | + |
| 167 | + response = templated_pa_components_api.create_templated_pa_components( |
| 168 | + templated_pa_component_parameters_root = templated_pa_component_parameters_root) |
| 169 | + |
| 170 | + component_id = response[0].data.get("id") |
| 171 | + |
| 172 | + print("PA Component Id: " + component_id) |
| 173 | + |
| 174 | + # do PA calculation with given templated component id |
| 175 | + pa_accounts = [PAIdentifier(id=portfolio)] |
| 176 | + pa_benchmarks = [PAIdentifier(id=benchmark)] |
| 177 | + pa_dates = PADateParameters( |
| 178 | + startdate=startdate, enddate=enddate, frequency=frequency) |
| 179 | + |
| 180 | + pa_calculation_parameters = {"1": PACalculationParameters(componentid=component_id, accounts=pa_accounts, |
| 181 | + benchmarks=pa_benchmarks, dates=pa_dates), |
| 182 | + "2": PACalculationParameters(componentid=component_id, accounts=pa_accounts, |
| 183 | + benchmarks=pa_benchmarks, dates=pa_dates)} |
| 184 | + |
| 185 | + pa_calculation_parameter_root = PACalculationParametersRoot( |
| 186 | + data=pa_calculation_parameters) |
| 187 | + |
| 188 | + pa_calculations_api = PACalculationsApi(api_client=api_client) |
| 189 | + |
| 190 | + post_and_calculate_response = pa_calculations_api.post_and_calculate( |
| 191 | + pa_calculation_parameters_root=pa_calculation_parameter_root) |
| 192 | + |
| 193 | + if post_and_calculate_response[1] == 202 or post_and_calculate_response[1] == 200: |
| 194 | + calculation_id = post_and_calculate_response[0].data.calculationid |
| 195 | + print("Calculation Id: " + calculation_id) |
| 196 | + |
| 197 | + status_response = pa_calculations_api.get_calculation_status_by_id(id=calculation_id) |
| 198 | + |
| 199 | + while status_response[1] == 202: |
| 200 | + max_age = '5' |
| 201 | + age_value = status_response[2].get("cache-control") |
| 202 | + if age_value is not None: |
| 203 | + max_age = age_value.replace("max-age=", "") |
| 204 | + print('Sleeping: ' + max_age) |
| 205 | + time.sleep(int(max_age)) |
| 206 | + status_response = pa_calculations_api.get_calculation_status_by_id(calculation_id) |
| 207 | + |
| 208 | + for (calculation_unit_id, calculation_unit) in status_response[0].data.units.items(): |
| 209 | + if calculation_unit.status == "Success": |
| 210 | + print("Calculation Unit Id: " + |
| 211 | + calculation_unit_id + " Succeeded!!!") |
| 212 | + result_response = pa_calculations_api.get_calculation_unit_result_by_id(id=calculation_id, |
| 213 | + unit_id=calculation_unit_id) |
| 214 | + output_calculation_result(result=result_response[0]['data']) |
| 215 | + else: |
| 216 | + print("Calculation Unit Id:" + |
| 217 | + calculation_unit_id + " Failed!!!") |
| 218 | + print("Error message : " + str(calculation_unit.errors)) |
| 219 | + else: |
| 220 | + print("Calculation creation failed") |
| 221 | + print("Error status : " + str(post_and_calculate_response[1])) |
| 222 | + print("Error message : " + str(post_and_calculate_response[0])) |
| 223 | + |
| 224 | + except ApiException as e: |
| 225 | + print("Api exception Encountered") |
| 226 | + print(e) |
| 227 | + exit() |
| 228 | + |
| 229 | + |
| 230 | +def output_calculation_result(result): |
| 231 | + print("Calculation Result") |
| 232 | + stachBuilder = StachExtensionFactory.get_row_organized_builder( |
| 233 | + StachVersion.V2) |
| 234 | + stachExtension = stachBuilder.set_package(pkg=result).build() |
| 235 | + dataFramesList = stachExtension.convert_to_dataframe() |
| 236 | + print(dataFramesList) |
| 237 | + # generate_excel(dataFramesList) # Uncomment this line to get the result in table format exported to excel file. |
| 238 | + |
| 239 | + |
| 240 | +def generate_excel(data_frames_list): |
| 241 | + for dataFrame in data_frames_list: |
| 242 | + writer = pd.ExcelWriter( # pylint: disable=abstract-class-instantiated |
| 243 | + str(uuid.uuid1()) + ".xlsx") |
| 244 | + dataFrame.to_excel(excel_writer=writer) |
| 245 | + writer.save() |
| 246 | + writer.close() |
| 247 | + |
| 248 | + |
| 249 | +if __name__ == '__main__': |
| 250 | + main() |
0 commit comments