The code defines a function `AgeCrif` that is designed to...

August 29, 2025 at 06:27 PM

def AgeCrif(crifDf, timeNodesList=defaultTimeNodeList, OutPutPathName="", saveAgedCrif=False,timeNodesDf=pd.DataFrame()): """ Takes a Crif dataframe input, with some additional columns timeNodesList should be in years, for months and days use fractions of an year "30days/365" timeNodesDf will overide timeNodesList and must contain TimeNodeDate """ time_0 = time.time() print('Started ageCrif()') crifDf_local = crifDf # set the indexes to optimize join if timeNodesDf.empty: timeNodesDf = pd.DataFrame(timeNodesList, columns=["TimeNode"]) timeNodesDf["ShiftDays"] = timeNodesDf.TimeNode * 365 timeNodesDf["TimeNodeDate"] = crifDf_local.ValuationDate.iloc[0] + pd.TimedeltaIndex(timeNodesDf.ShiftDays, unit="D") timeNodesDf["TimeNodeDate"] = timeNodesDf["TimeNodeDate"].dt.date else: timeNodesDf["ShiftDays"] = (timeNodesDf["TimeNodeDate"] - crifDf_local.ValuationDate.iloc[0]).dt.days timeNodesDf["TimeNode"] = timeNodesDf.ShiftDays / 365 crifDf_local = pd.merge(crifDf_local, tenorDaysDf[tenorDaysDf.Type == 'single'], how="left", right_on='Tenor', left_on="Label1") crifDf_local = crifDf_local.drop( labels = ['Tenor', 'TenorDaysStart', 'TenorDaysEnd', 'Type', 'TenorDaysLow'], axis = 1) # perform the cross join with timeNodes timeNodesDf["key"] = 0 crifDf_local["key"] = 0 crifDf_local["DaysToMaturity"] = (crifDf_local.EndDate - crifDf_local.ValuationDate).dt.days """ the macro excludes based on the rule below Not ( IsMissing(endDate) Or IsMissing(valDate) Or (shiftDays > UBound(endDateArray, 1) ) Or (shiftDays > UBound(valDateArray, 1) ) ) And (endDateArray(i, 1) - valDateArray(i, 1) ) <= shiftDays """ crifDf_local = pd.merge(crifDf_local, timeNodesDf, how="inner", right_on='key', left_on='key').drop( labels="key", axis=1) # filter out un-necessary records # filter all trades that would expire before the new "valuation date" crifDf_local = crifDf_local[(crifDf_local.DaysToMaturity > crifDf_local.ShiftDays)] # Take only records that after ageing would end after the new "valuation date". The others would yield zero anyway crifDf_local = crifDf_local[( ((crifDf_local.Label1 != '') & ( crifDf_local.TenorDaysHigh > crifDf_local.ShiftDays)) # has tenor use TenorDays | ((crifDf_local.Label1 == '')) # no tenor use enddate ) ] # Calculate remaining maturity after ageing crifDf_local["RemainingMaturity"] = (crifDf_local.DaysToMaturity - crifDf_local.ShiftDays) crifDf_local["RemainingMaturity"] = crifDf_local["RemainingMaturity"].mask(crifDf_local["RemainingMaturity"].lt(0),0) # calculate remaining tenor days ageing crifDf_local["RemainingTenorDays"] = (crifDf_local.TenorDaysHigh - crifDf_local.ShiftDays) crifDf_local["RemainingTenorDays"] = crifDf_local["RemainingTenorDays"].mask( crifDf_local["RemainingTenorDays"].lt(0), 0) # Join with tenorDays again to split the records into Low and High allocation (rebucketing based on Label1/Tenor ) # get the new Tenor range, at the new time node lowTenorDaysDf = tenorDaysDf[~(tenorDaysDf.Type == 'high')] tenorRange = lowTenorDaysDf[['TenorDaysStart', 'TenorDaysEnd']].stack()[1::2].tolist() tenorLabels = lowTenorDaysDf.TenorDaysStart.astype('str') + ',' + lowTenorDaysDf.TenorDaysEnd.astype('str') crifDf_local['lowTenorStartFinish'] = pd.cut(crifDf_local.RemainingTenorDays, bins=[0] + tenorRange, labels=tenorLabels) # use the new Tenor range to join with the appropriate 'low' and 'high' tenors tenorDaysDf["tenorStartFinish"] = tenorDaysDf.TenorDaysStart.astype('str') + ',' + tenorDaysDf.TenorDaysEnd.astype( 'str') crifDf_local = pd.merge(crifDf_local, tenorDaysDf, how='left', left_on='lowTenorStartFinish', right_on='tenorStartFinish', suffixes=('_x', "_y")) crifDf_local.rename(columns={'TenorDaysHigh_x': 'TenorDaysHigh', 'tenorStartFinish_x': 'tenorStartFinish'}, inplace=True) crifDf_local["TermRatio"] = np.where((crifDf_local.Label1 == '') & (crifDf_local.RiskType == 'Risk_Inflation'), crifDf_local.RemainingMaturity / crifDf_local.DaysToMaturity , crifDf_local.RemainingTenorDays / crifDf_local.TenorDaysHigh ) crifDf_local.TermRatio = crifDf_local.TermRatio.replace(np.nan, 1.0, regex=True) crifDf_local["AllocationLow"] = np.where((crifDf_local.Type == "single") | (crifDf_local.Label1 == '') , 1 , (crifDf_local.TenorDaysHigh_y - crifDf_local.RemainingTenorDays) / ( crifDf_local.TenorDaysHigh_y - crifDf_local.TenorDaysLow) ) crifDf_local["AllocationHigh"] = 1 - crifDf_local.AllocationLow crifDf_local.Tenor = crifDf_local.Tenor.fillna("") crifDf_local.Label1 = crifDf_local.Tenor crifDf_local["FinalRatio"] = (crifDf_local.TermRatio * np.select([crifDf_local.Type == 'low', crifDf_local.Type == 'high'], [crifDf_local.AllocationLow, crifDf_local.AllocationHigh] , default=1) ) crifDf_local.to_csv("crifDf_local.csv") crifDf_local.Amount = crifDf_local.Amount * crifDf_local.FinalRatio crifDf_local.AmountUSD = crifDf_local.AmountUSD * crifDf_local.FinalRatio # global minHeaderList minHeaderList_local = minHeaderList + ['TimeNode', 'TimeNodeDate', 'ShiftDays','Type','FinalRatio'] # cleanup columns # print(crifDf_local.dtypes) crifDf_local = crifDf_local[crifDf_local.Amount != 0] crifDf_local = crifDf_local.drop(labels= set(crifDf_local.columns.to_list()) - set(minHeaderList_local), axis=1) if saveAgedCrif: crifDf_local.to_csv(OutPutPathName + 'GID_' +str(crifDf_local.GID.iloc[0]) + '_' + crifDf_local.PostOrCall.iloc[0] + '_' + str( crifDf_local.ValuationDate.iloc[0].strftime('%Y-%m-%d')) + '_AgedCRIF.csv.gz', compression='gzip') print("AgeCrif() time: " + str(time.time() - time_0)) return crifDf_local

The code defines a function AgeCrif that is designed to "age" a CRIF (Common Risk Interchange Format) dataframe. A CRIF is typically a financial dataset used in calculating Initial Margins for portfolios under regulatory frameworks, such as the Standard Initial Margin Model (SIMM). Here's an explanation of what the function does:

1. Inputs:

  • crifDf: A dataframe containing CRIF data, including financial instruments and their relevant attributes (e.g., valuation date, end date, label, tenor).
  • timeNodesList: A list of time nodes (in years) used to shift the valuation date forward in time for aging the CRIF.
  • OutPutPathName: A string representing the output path for saving an aged CRIF file (optional).
  • saveAgedCrif: A boolean flag indicating whether to save the aged CRIF data to a file.
  • timeNodesDf: An optional dataframe specifying time nodes (overrides timeNodesList).

2. Steps Performed by the Code:

a. Preparation of Time Nodes:

  • If timeNodesDf is empty, the function computes it based on the timeNodesList (converting years to days and calculating future dates using the valuation date).
  • If a timeNodesDf is provided, it calculates "ShiftDays" (days to shift valuation date) and the TimeNode (years equivalent of shift).

b. Data Merge and Filtering:

  • Joins CRIF data (crifDf) with tenorDaysDf (assumes a predefined dataframe describing tenor ranges) to add tenor-related information to the CRIF.
  • Filters out irrelevant records based on business rules (e.g., skips trades that expire before the shifted valuation date or don't meet certain maturity requirements).

c. Aging Logic:

  • The CRIF data is aged based on the new TimeNodeDate (shifted valuation date). For instance:
    • Computes remaining maturity by subtracting ShiftDays.
    • Adjusts tenor days (remaining tenor after shifting).
  • Assigns trades to new tenor buckets based on the remaining maturity or remaining tenor days.
  • Re-buckets records by joining CRIF data with the tenor ranges again after the adjustments.

d. Term Ratios and Allocations:

  • Calculates a TermRatio that reflects the proportion of time remaining to the total original maturity (or tenor).
  • Allocates amounts between "low" and "high" tenor buckets:
    • AllocationLow: Proportion allocated to the low tenor.
    • AllocationHigh: Proportion allocated to the high tenor.
    • Adjusts amounts (Amount and AmountUSD) based on these ratios.

e. Cleanup:

  • Drops unnecessary columns after processing.
  • Filters out records with zero amounts (since they're no longer relevant).
  • Ensures only a minimal set of columns (minHeaderList_local) is retained.

f. Optional Save:

  • If saveAgedCrif is True, saves the processed (aged) CRIF to a .csv.gz file with a filename indicating the GID, post-or-call flag, and valuation date.

3. Output:

  • The function returns the aged CRIF dataframe, which contains adjusted tenors, maturities, and amounts reflective of a new valuation date.

4. Key Features:

  • Time Bucketing: Uses time nodes to shift and age the CRIF data forward in time.
  • Tenor Adjustments: Adjusts tenor-related fields and reassigns records to appropriate tenor buckets.
  • Allocation of Amounts: Adjusts financial amounts (e.g., cashflows) to reflect partial allocations due to aging.
  • Filtering Rules: Carefully filters out records that do not meet aging criteria (e.g., expired trades).

5. Applications:

  • The function is useful in financial risk management, particularly in scenarios where aging of financial trades or positions is required for regulatory or operational analysis (e.g., calculating margin or risk exposure at future time nodes).
Generate your own explanations
Download our vscode extension
Read other generated explanations

Built by @thebuilderjr
Sponsored by beam analytics
Read our terms and privacy policy
Forked from openai-quickstart-node