classdef (Sealed, InferiorClasses = {?matlab.graphics.axis.Axes,...
        ?matlab.ui.control.UIAxes,?matlab.ui.Figure}) saccr
    % SACCR Standardised Approach for Counterparty Credit Risk Class.
    %
    % Syntax:
    %
    %   mySACCR = saccr(SACCRCRIF)
    %   mySACCR = saccr(SACCRCRIF,Name=Value,...)
    %
    % Description:
    %
    %   Create a SA-CCR object and calculate Exposure-at-Default (EAD),
    %   Replacement Cost (RC), Potential Future Exposure (PFE) and Add-Ons.
    %
    % Input Arguments:
    %
    %  Required:
    %   SACCRCRIF - String specifying the file name for ISDA SA-CCR Common
    %               Risk Interchange Format (CRIF) data.
    %
    %  Optional:
    %   Alpha     - Scalar numeric or NumPortfolios-by-1 vector specifying
    %               the Alpha value for each portfolio EAD. Typically, the
    %               Alpha value is either 1.4 or 1.
    %               Default: 1.4 for each portfolio EAD.
    %
    %   DomesticCurrency - Scalar string in the form of a 3-letter ISO
    %               currency code indicating the domestic currency.
    %               Default: "USD"
    %
    %   FXSpotRates - Table containing spot foreign exchange rates, with
    %               the following columns:
    %                 * Base: String vector for Base currencies.
    %                   (e.g. ["AUD";"EUR"; ...])
    %                 * Quote: String vector for Quote currencies.
    %                   (e.g. ["USD";"USD"; ...])
    %                 * SpotRate: Numeric vector for spot exchange rates.
    %                   (e.g. [0.8;1.2; ...])
    %
    %               Default: table.empty
    %
    %   InputVariants - 5-by-1 vector of strings indicating the preferred
    %               input variants "1a" or "1b" for trade adjusted
    %               notionals for each asset class in the following order:
    %                 * IR (Interest Rate)
    %                 * CR (Credit)
    %                 * EQ (Equity)
    %                 * CO (Commodity)
    %                 * FX (Foreign Exchange)
    %
    %               This argument sets the TradeDecompositions property,
    %               which is a table indicating the preferred input
    %               variants for trade adjusted notionals for each asset
    %               class.
    %
    %               Default: ["1a";"1a";"1a";"1a";"1b"]
    %
    %               Default specifies the following input variants for
    %               trade adjusted notionals for each asset class in the
    %               TradeDecompositions property:
    %                 * IR (Interest Rate): "1a"
    %                 * CR (Credit): "1a"
    %                 * EQ (Equity): "1a"
    %                 * CO (Commodity): "1a"
    %                 * FX (Foreign Exchange): "1b"
    %
    %               The trade decomposition input variants can only be
    %               either "1a" or "1b":
    %                 * "1a": Single data input. For example, interest rate
    %                         cap trade's adjusted notional is represented
    %                         in a single set of input data as a single
    %                         instrument. In the SACCR-CRIF file, this
    %                         input variant has a trade ID that does not
    %                         end with an underscore followed by a number
    %                         (e.g. "Tr071").
    %                 * "1b": Decomposed data inputs. For example, interest
    %                         rate cap trade's adjusted notional is
    %                         represented in multiple sets of input data
    %                         that are decomposed into individual caplets.
    %                         In the SACCR-CRIF file, this input variant
    %                         has trade IDs that end with an underscore
    %                         followed by a number (e.g. "Tr071_01",
    %                         "Tr071_02", etc.).
    %
    %               If the SACCR-CRIF file contains data for both input
    %               variants for a trade, the specified input variant will
    %               be chosen. If the specified input variant is not
    %               present in the SACCR-CRIF file, whichever input variant
    %               that is in the SACCR-CRIF file applies to the trade.
    %
    %   MaturityBusinessDaysFloor - Positive scalar integer defining the
    %               minimum number of business days remaining in maturity.
    %               Default: 10 business days.
    %
    %   NumBusinessDaysYear - Positive scalar integer defining the number
    %               of business days in a year in relation to maturity
    %               times and MPOR (Margin Period of Risk).
    %               Default: 250 business days.
    %
    % Output Argument:
    %
    %   saccrObj - saccr object
    %
    % Properties:
    %
    %   CRIF      - Table containing the SACCR-CRIF data.
    %
    %   NumPortfolios - Scalar numeric indicating the number of portfolios.
    %
    %   PortfolioIDs - NumPortfolios-by-1 string vector of portfolio IDs.
    %
    %   CounterpartyIDs - NumPortfolios-by-1 string vector of counterparty
    %               IDs.
    %
    %   Portfolios - NumPortfolios-by-1 vector of saccr.Portfolio objects.
    %
    %   Regulation - Scalar string indicating the regulation "Basel_CRE52".
    %
    %   DomesticCurrency - String scalar in the form of a 3-letter ISO
    %               currency code indicating the domestic currency.
    %
    %   Alpha     - NumPortfolios-by-1 numeric vector of Alpha values.
    %
    %   FXSpotRates - Table containing specified foreign exchange rates.
    %
    %   TradeDecompositions - Table indicating the preferred trade adjusted
    %               notional input variants for each asset class in the
    %               following order:
    %                 * IR (Interest Rate)
    %                 * CR (Credit)
    %                 * EQ (Equity)
    %                 * CO (Commodity)
    %                 * FX (Foreign Exchange)
    %
    %   CollateralHaircuts - Table containing the supervisory collateral
    %               haircuts for each collateral position instrument type.
    %
    %   SupervisoryParameters - Table containing the supervisory parameter
    %               data, including:
    %                 * Supervisory Factors
    %                 * Supervisory Correlations
    %                 * Supervisory Option Volatilities
    %
    %   MaturityBusinessDaysFloor - Positive scalar integer defining the
    %               minimum number of business days remaining in maturity.
    %
    %   NumBusinessDaysYear - Positive scalar integer defining the number
    %               of business days in a year.
    %
    % Methods:
    %
    %   saccr             - Constructor. Returns the saccr object.
    %
    %   ead               - Returns a saccr.EADResults object containing
    %                       the Exposure-at-Default (EAD) results for each
    %                       portfolio in the SACCR-CRIF data.
    %
    %   rc                - Returns a saccr.RCResults object containing the
    %                       Replacement Cost (RC) results for each
    %                       portfolio in the SACCR-CRIF data.
    %
    %   pfe               - Returns a saccr.PFEResults object containing the
    %                       Potential Future Exposure (PFE) results for
    %                       each portfolio in the SACCR-CRIF data.
    %
    %   addOn             - Returns a saccr.AddOnResults object containing
    %                       the Add-On results aggregated from all asset
    %                       classes for each portfolio in the SACCR-CRIF
    %                       data.
    %
    % Example:
    %
    % mySACCR = saccr("SACCR_CRIF.xlsx");
    % EADResults = ead(mySACCR);
    % RCResults = rc(mySACCR);
    % PFEResults = pfe(mySACCR);
    % AddOnResults = addOn(mySACCR);
    %
    % References:
    %
    % "CRE52- Standardised approach to counterparty credit risk", Bank for
    % International Settlements, June, 2020.
    % https://www.bis.org/basel_framework/chapter/CRE/52.htm
    %
    % "CRE22- Standardised approach: credit risk mitigation", Bank for
    % International Settlements, November, 2020.
    % https://www.bis.org/basel_framework/chapter/CRE/22.htm
    %
    % "Basel Committee on Banking Supervision: The standardised approach
    % for measuring counterparty credit risk exposures", Bank for
    % International Settlements, April, 2014.
    % https://www.bis.org/publ/bcbs279.pdf
    %
    % See also SACCR.PORTFOLIO, SACCR.TRADE, SACCR.NETTINGSET,
    %          SACCR.COLLATERALSET, SACCR.COLLATERALPOSITION,
    %          SACC.READRESULTS, SACCR.RCRESULTS, SACCR.PFERESULTS,
    %          SACCR.ADDONRESULTS, SACCR.ADDONASSETCLASSRESULTS.

    % Copyright 2023 The MathWorks, Inc.

    properties(SetAccess=protected, Hidden=false)
        CRIF table
        NumPortfolios(1,1) double {mustBeFinite, mustBeNonNan, mustBeNonnegative}
        PortfolioIDs(:,1) string
        CounterpartyIDs(:,1) string
        Portfolios(:,1) saccr.Portfolio
        Regulation(1,1) string = "Basel_CRE52"
        DomesticCurrency(1,1) string
        Alpha(:,1) double {mustBeNonnegative, mustBeFinite, mustBeNonNan}
        FXSpotRates table
        TradeDecompositions table
        CollateralHaircuts table
        SupervisoryParameters table
        MaturityBusinessDaysFloor(1,1) double {mustBeNonnegative, mustBeFinite, mustBeNonNan}
        NumBusinessDaysYear(1,1) double {mustBeNonnegative, mustBeFinite, mustBeNonNan}
    end

    properties(SetAccess=protected, Hidden=true)
        InputVariants string
        PFEMultiplierFloor(1,1) double {mustBeNonnegative, mustBeFinite, mustBeNonNan} = 0.05
    end

    methods
        function obj = saccr(filename, NVargs)
            arguments
                filename(1,1) string
                NVargs.Alpha(:,1) double {mustBeNonnegative, mustBeFinite, mustBeNonNan} = 1.4
                NVargs.DomesticCurrency(1,1) string = "USD"
                NVargs.FXSpotRates table = table.empty
                %  For              AssetClasses = ["IR";"CR";"EQ";"CO";"FX"]
                NVargs.InputVariants(5,1) string = ["1a";"1a";"1a";"1a";"1b"]
                NVargs.MaturityBusinessDaysFloor(1,1) double {mustBeNonnegative, mustBeFinite, mustBeNonNan} = 10
                NVargs.NumBusinessDaysYear(1,1) double {mustBeNonnegative, mustBeFinite, mustBeNonNan} = 250
            end

            matObj = matfile('SACCR_Parameters.mat',Writable=false);
            obj.CollateralHaircuts = matObj.CollateralHaircuts;
            obj.SupervisoryParameters = matObj.SupervisoryParameters_Basel_EU;
            obj.CRIF = loadSACCRCRIFData(obj,filename);
            MissingIdx = ismissing(obj.CRIF.RiskType) | ...
                (obj.CRIF.RiskType=="0");
            obj.CRIF = obj.CRIF(~MissingIdx,:);
            obj.PortfolioIDs = unique([obj.CRIF.PortfolioID]);
            obj.NumPortfolios = length(unique([obj.CRIF.PortfolioID]));
            obj.CounterpartyIDs = repmat("",obj.NumPortfolios,1);

            if isequal(NVargs.Alpha, 1.4)
                obj.Alpha = 1.4.*ones(obj.NumPortfolios,1);
            elseif isscalar(NVargs.Alpha) && ~isnan(NVargs.Alpha)
                obj.Alpha = NVargs.Alpha.*ones(obj.NumPortfolios,1);
            elseif isvector(NVargs.Alpha)
                obj.Alpha = NVargs.Alpha;
            end

            obj.DomesticCurrency = NVargs.DomesticCurrency;
            obj.FXSpotRates = NVargs.FXSpotRates;
            obj.InputVariants = NVargs.InputVariants;
            AssetClasses = ["IR";"CR";"EQ";"CO";"FX"];
            Decompositions = obj.InputVariants;
            obj.TradeDecompositions = table(AssetClasses, Decompositions);
            obj.MaturityBusinessDaysFloor = NVargs.MaturityBusinessDaysFloor;
            obj.NumBusinessDaysYear = NVargs.NumBusinessDaysYear;
            obj.Portfolios(obj.NumPortfolios,1) = saccr.Portfolio;

            for k = 1:obj.NumPortfolios
                % Extract Portfolios
                obj.Portfolios(k).ID = obj.PortfolioIDs(k);
                PortIdx = (obj.CRIF.PortfolioID == obj.Portfolios(k).ID);
                PortCRIF = obj.CRIF(PortIdx,:);
                CounterpartyID = unique(PortCRIF.CounterpartyID);
                CounterpartyID = CounterpartyID(~ismissing(CounterpartyID));
                if ~isempty(CounterpartyID)
                    if isscalar(CounterpartyID)
                        obj.Portfolios(k).CounterpartyID = CounterpartyID;
                        obj.CounterpartyIDs(k) = CounterpartyID;
                    else
                        error(message('fininst:saccr:multipleCounterPartyIDs'));
                    end
                end

                % Extract Netting Sets
                NettingSetIDs = unique(PortCRIF.NettingSetNumber);
                NettingSetIDs = NettingSetIDs(~ismissing(NettingSetIDs));
                IdxNettingSetIDsWithCol = contains(NettingSetIDs,"_");
                NettingSetIDs(IdxNettingSetIDsWithCol) = ...
                    extractBefore(NettingSetIDs(IdxNettingSetIDsWithCol),"_");
                NettingSetIDs = unique(NettingSetIDs);
                NumNettingSets = length(NettingSetIDs);

                if NumNettingSets>=1
                    obj.Portfolios(k).NettingSets(NumNettingSets,1) = ...
                        saccr.NettingSet;
                    for ns = 1:NumNettingSets
                        obj.Portfolios(k).NettingSets(ns).ID = NettingSetIDs(ns);
                    end
                end

                % Extract Collateral Sets
                CollIdx = strncmpi(PortCRIF.RiskType,"COL",3);
                PortCollateralCRIF = PortCRIF(CollIdx,:);
                if ~isempty(PortCollateralCRIF)
                    ColPosIdx = strncmpi(PortCollateralCRIF.TradeID,"ColPos",6);
                    PortCACRIF = PortCollateralCRIF(~ColPosIdx,:);
                    PortColPosCRIF = PortCollateralCRIF(ColPosIdx,:);
                    CollateralSetIDs = unique(PortCACRIF.TradeID);
                    NumCollateralSets = length(CollateralSetIDs);
                    ColPosIDs = unique(PortColPosCRIF.TradeID);
                    NumColPos = length(ColPosIDs);

                    % Extract Collateral Positions
                    if NumColPos>=1
                        CollateralPositions(NumColPos,1) = ...
                            saccr.CollateralPosition; %#ok<AGROW>
                        for cp = 1:NumColPos
                            CollateralPositions(cp) = saccr.CollateralPosition;
                            CollateralPositions(cp).ID = ColPosIDs(cp);
                            if contains(PortColPosCRIF(cp,:).NettingSetNumber,"_")
                                CollateralPositions(cp).NettingSetID = ...
                                    extractBefore(...
                                    PortColPosCRIF(cp,:).NettingSetNumber,"_");
                                CollateralPositions(cp).CollateralSetID = ...
                                    extractAfter(...
                                    PortColPosCRIF(cp,:).NettingSetNumber,"_");
                            else
                                CollateralPositions(cp).NettingSetID = ...
                                    PortColPosCRIF(cp,:).NettingSetNumber;
                                CollateralPositions(cp).CollateralSetID = ...
                                    PortColPosCRIF(cp,:).NettingSetNumber;
                            end
                            CollateralPositions(cp).MarginType = ...
                                PortColPosCRIF(cp,:).Category;
                            CollateralPositions(cp).AssetType = ...
                                PortColPosCRIF(cp,:).Label2;

                            CollateralPositions(cp).SubType = ...
                                PortColPosCRIF(cp,:).Qualifier;

                            CollateralPositions(cp).Currency = ...
                                PortColPosCRIF(cp,:).AmountCurrency;
                            CollateralPositions(cp).Notional = ...
                                PortColPosCRIF(cp,:).Amount;
                            CollateralPositions(cp).NotionalUSD = ...
                                PortColPosCRIF(cp,:).AmountUSD;
                            CollateralPositions(cp).Rating = ...
                                PortColPosCRIF(cp,:).Bucket;
                            CollateralPositions(cp).MaturityTime = ...
                                PortColPosCRIF(cp,:).EndDate;

                            if strcmpi("SEGREGATED",PortColPosCRIF(cp,:).Label1)
                                CollateralPositions(cp).Segregated = true;
                            end
                        end
                    end

                    for cs = 1:NumCollateralSets
                        CollateralSetIdx = strcmpi(...
                            PortCACRIF.TradeID, CollateralSetIDs(cs));
                        pat = "_" + CollateralSetIDs(cs);
                        CollateralSetCRIF = PortCACRIF(CollateralSetIdx,:);
                        ColSetNettingSetId = ...
                            unique(erase([CollateralSetCRIF.NettingSetNumber],pat));

                        if ~isscalar(ColSetNettingSetId)
                            error(message('fininst:saccr:multipleNettingSetCollateral'));
                        end

                        CollateralSet = saccr.CollateralSet;
                        CollateralSet.ID = CollateralSetIDs(cs);
                        CollateralSet.NettingSetID = ColSetNettingSetId;
                        CollateralSet.Direction = CollateralSetCRIF(strcmpi( ...
                            CollateralSetCRIF.Category, "DIRECTION"),:).Label1;

                        TAIdx = strcmpi("TA",CollateralSetCRIF.Category);
                        if all(~TAIdx)
                            error(message('fininst:saccr:missingTACollateral'));
                        else
                            CollateralSet.Threshold = CollateralSetCRIF(TAIdx,:).Amount;
                            CollateralSet.ThresholdCurrency = ...
                                CollateralSetCRIF(TAIdx,:).AmountCurrency;
                        end

                        MTAIdx = strcmpi("MTA",CollateralSetCRIF.Category);
                        if all(~MTAIdx)
                            error(message('fininst:saccr:missingMTACollateral'));
                        else
                            CollateralSet.MTA = CollateralSetCRIF(MTAIdx,:).Amount;
                            CollateralSet.MTACurrency = ...
                                CollateralSetCRIF(MTAIdx,:).AmountCurrency;
                        end

                        MPORIdx = strcmpi("MPOR",CollateralSetCRIF.Category);
                        if all(~MPORIdx)
                            error(message('fininst:saccr:missingMPORCollateral'));
                        else
                            CollateralSet.MPOR = CollateralSetCRIF(MPORIdx,:).Label1;
                        end

                        SettleTypeCollateralSetCRIF = ...
                            CollateralSetCRIF(strcmpi( ...
                            CollateralSetCRIF.Category, "SETTLEMENTTYPE"),:);

                        if ~isempty(SettleTypeCollateralSetCRIF)
                            if strcmpi (SettleTypeCollateralSetCRIF.Label1,"STM")
                                % STM Settlement Type
                                CollateralSet.STM = true;
                            end
                        end

                        if NumColPos>=1
                            CollateralSet.CollateralPositions = ...
                                CollateralPositions(strcmpi( ...
                                CollateralSet.ID, ...
                                [CollateralPositions.CollateralSetID]') & ...
                                strcmpi( ...
                                CollateralSet.NettingSetID, ...
                                [CollateralPositions.NettingSetID]'));

                            % Determine Haircuts (CRE 22)
                            NumCollateralSetColPos = length(CollateralSet.CollateralPositions);
                            TerminationCurrency = CollateralSet.ThresholdCurrency;

                            for CollateralSetColPosIdx = 1:NumCollateralSetColPos
                                ColPosCurrency = ...
                                    CollateralSet.CollateralPositions(CollateralSetColPosIdx).Currency;
                                % FXHaircut
                                if ~strcmpi(TerminationCurrency, ColPosCurrency)
                                    CashCollateralHaircutIdx = ...
                                        strcmpi("CASH",obj.CollateralHaircuts.InstrumentType);
                                    CollateralSet.CollateralPositions(CollateralSetColPosIdx).FXHaircut = ...
                                        obj.CollateralHaircuts.SupervisoryHaircut(...
                                        find(CashCollateralHaircutIdx,1));
                                end
                                % BondHaircut
                                if strcmpi("BOND", ...
                                        CollateralSet.CollateralPositions(CollateralSetColPosIdx).AssetType)
                                    BondCollateralHaircutIdx = ...
                                        strcmpi("BOND",obj.CollateralHaircuts.InstrumentType) & ...
                                        strcmpi(CollateralSet.CollateralPositions(...
                                        CollateralSetColPosIdx).SubType, ...
                                        obj.CollateralHaircuts.Type) & ...
                                        strcmpi(CollateralSet.CollateralPositions(...
                                        CollateralSetColPosIdx).ResidualMaturity, ...
                                        obj.CollateralHaircuts.ResidualMaturity) & ...
                                        strcmpi(CollateralSet.CollateralPositions(...
                                        CollateralSetColPosIdx).Rating, ...
                                        obj.CollateralHaircuts.Rating);
                                    CollateralSet.CollateralPositions(CollateralSetColPosIdx).BondHaircut = ...
                                        obj.CollateralHaircuts.SupervisoryHaircut(...
                                        find(BondCollateralHaircutIdx,1));
                                end
                                % EquityHaircut
                                if strcmpi("EQUITY", ...
                                        CollateralSet.CollateralPositions(CollateralSetColPosIdx).AssetType)
                                    EquityCollateralHaircutIdx = ...
                                        strcmpi("EQUITY",obj.CollateralHaircuts.InstrumentType) & ...
                                        strcmpi(CollateralSet.CollateralPositions(CollateralSetColPosIdx).SubType, ...
                                        obj.CollateralHaircuts.Type);
                                    CollateralSet.CollateralPositions(CollateralSetColPosIdx).EquityHaircut = ...
                                        obj.CollateralHaircuts.SupervisoryHaircut(...
                                        find(EquityCollateralHaircutIdx,1));
                                end
                                % GoldHaircut
                                if strcmpi("GOLD", ...
                                        CollateralSet.CollateralPositions(CollateralSetColPosIdx).AssetType)
                                    GoldCollateralHaircutIdx = ...
                                        strcmpi("GOLD",obj.CollateralHaircuts.InstrumentType);
                                    CollateralSet.CollateralPositions(CollateralSetColPosIdx).GoldHaircut = ...
                                        obj.CollateralHaircuts.SupervisoryHaircut(...
                                        find(GoldCollateralHaircutIdx,1));
                                end
                            end
                        end

                        if NumNettingSets>=1
                            for ns = 1:NumNettingSets
                                if strcmpi(obj.Portfolios(k).NettingSets(ns).ID, ...
                                        ColSetNettingSetId)
                                    obj.Portfolios(k).NettingSets(ns).CollateralSets = ...
                                        [obj.Portfolios(k).NettingSets(ns).CollateralSets;CollateralSet];
                                end
                            end
                        end
                    end
                end

                % Extract Trades
                TradeIdx = ~CollIdx;
                PortTradeCRIF = PortCRIF(TradeIdx,:);

                AltDecomp1bPat = "_" + digitsPattern;
                AltDecomp1bIdx = contains(PortTradeCRIF.TradeID,AltDecomp1bPat);

                PortTradeIDs = unique(PortTradeCRIF(~AltDecomp1bIdx,:).TradeID);

                NumTrades = length(PortTradeIDs);
                obj.Portfolios(k).Trades(NumTrades,1) = saccr.Trade;

                for tr = 1:NumTrades
                    obj.Portfolios(k).Trades(tr).ID = PortTradeIDs(tr);
                    IndividualTradeIdx = strncmpi( ...
                        PortTradeCRIF.TradeID,PortTradeIDs(tr),strlength(PortTradeIDs(tr)));
                    IndividualTradeCRIF = PortTradeCRIF(IndividualTradeIdx,:);

                    PVRowIdx = strcmpi(IndividualTradeCRIF.RiskType,"PV");
                    IndividualTradePVRow = IndividualTradeCRIF(PVRowIdx,:);
                    if size(IndividualTradePVRow,1) ~=1
                        error(message('fininst:saccr:multiplePVTrade'));
                    end
                    obj.Portfolios(k).Trades(tr).PV = IndividualTradePVRow.Amount;
                    obj.Portfolios(k).Trades(tr).PVCurrency = ...
                        IndividualTradePVRow.AmountCurrency;
                    obj.Portfolios(k).Trades(tr).PVUSD = IndividualTradePVRow.AmountUSD;
                    if endsWith(IndividualTradePVRow.TradeID,"_SOpt",IgnoreCase=true)
                        obj.Portfolios(k).Trades(tr).SoldOption = true;
                    else
                        obj.Portfolios(k).Trades(tr).SoldOption = false;
                    end

                    IndividualTradeAssetRows = IndividualTradeCRIF(~PVRowIdx,:);

                    if isempty(IndividualTradeAssetRows)
                        error(message('fininst:saccr:missingAssetTrade'));
                    end

                    AssetClass = unique(IndividualTradeAssetRows.RiskType);
                    if ~isscalar(AssetClass)
                        error(message('fininst:saccr:multipleAssetClassTrade'));
                    else
                        obj.Portfolios(k).Trades(tr).AssetClass = AssetClass;
                        TradeDecompositionIdx = strncmpi( ...
                            obj.TradeDecompositions.AssetClasses, AssetClass,2);
                        obj.Portfolios(k).Trades(tr).InputVariant = ...
                            obj.TradeDecompositions(TradeDecompositionIdx,:).Decompositions;
                    end

                    IndividualTradeAltDecomp1bIdx = ...
                        contains(IndividualTradeAssetRows.TradeID,AltDecomp1bPat);

                    if strcmpi(obj.Portfolios(k).Trades(tr).InputVariant,"1b")
                        SelectedIndividualTradeAssetRows = ...
                            IndividualTradeAssetRows(IndividualTradeAltDecomp1bIdx,:);
                        if isempty(SelectedIndividualTradeAssetRows)
                            SelectedIndividualTradeAssetRows = ...
                                IndividualTradeAssetRows(~IndividualTradeAltDecomp1bIdx,:);
                        end
                    else
                        SelectedIndividualTradeAssetRows = ...
                            IndividualTradeAssetRows(~IndividualTradeAltDecomp1bIdx,:);
                        if isempty(SelectedIndividualTradeAssetRows)
                            SelectedIndividualTradeAssetRows = ...
                                IndividualTradeAssetRows(IndividualTradeAltDecomp1bIdx,:);
                        end
                    end

                    if contains(SelectedIndividualTradeAssetRows.NettingSetNumber,"_")
                        obj.Portfolios(k).Trades(tr).NettingSetID = ...
                            unique(extractBefore(...
                            SelectedIndividualTradeAssetRows.NettingSetNumber,"_"));
                        obj.Portfolios(k).Trades(tr).CollateralSetID = ...
                            unique(extractAfter(...
                            SelectedIndividualTradeAssetRows.NettingSetNumber,"_"));
                    else
                        NettingSetID = ...
                            unique(SelectedIndividualTradeAssetRows.NettingSetNumber);
                        obj.Portfolios(k).Trades(tr).NettingSetID = NettingSetID(1);
                    end

                    if ismissing(obj.Portfolios(k).Trades(tr).NettingSetID)
                        obj.Portfolios(k).Trades(tr).NettingSetID = ...
                            "Missing_NettingSet_"+obj.Portfolios(k).ID+...
                            "_"+obj.Portfolios(k).Trades(tr).ID;
                    end

                    obj.Portfolios(k).Trades(tr).AssetClass = ...
                        SelectedIndividualTradeAssetRows(1,:).RiskType;
                    obj.Portfolios(k).Trades(tr).HedgingSet = ...
                        SelectedIndividualTradeAssetRows.Category;
                    obj.Portfolios(k).Trades(tr).Qualifier = ...
                        SelectedIndividualTradeAssetRows.Qualifier;
                    obj.Portfolios(k).Trades(tr).AdjustedNotional = ...
                        SelectedIndividualTradeAssetRows.Amount;
                    obj.Portfolios(k).Trades(tr).AdjustedNotionalCurrency = ...
                        SelectedIndividualTradeAssetRows.AmountCurrency;
                    obj.Portfolios(k).Trades(tr).AdjustedNotionalUSD = ...
                        SelectedIndividualTradeAssetRows.AmountUSD;
                    obj.Portfolios(k).Trades(tr).StartTime = ...
                        SelectedIndividualTradeAssetRows.Label1;
                    obj.Portfolios(k).Trades(tr).EndTime = ...
                        SelectedIndividualTradeAssetRows.Label2;
                    obj.Portfolios(k).Trades(tr).MaturityTime = ...
                        SelectedIndividualTradeAssetRows.EndDate;
                    obj.Portfolios(k).Trades(tr).SubClass = ...
                        SelectedIndividualTradeAssetRows(1,:).Bucket;
                    obj.Portfolios(k).Trades(tr).SupervisoryDelta = ...
                        SelectedIndividualTradeAssetRows.Label3;

                    % Determine Maturity Factors
                    obj.Portfolios(k).Trades(tr).MaturityFactorUncollateralized = ...
                        sqrt(min(1, max(obj.Portfolios(k).Trades(tr).MaturityTime, ...
                        obj.MaturityBusinessDaysFloor./obj.NumBusinessDaysYear)));

                    obj.Portfolios(k).Trades(tr).MaturityFactorCollateralized = ...
                        obj.Portfolios(k).Trades(tr).MaturityFactorUncollateralized;

                    if ~isempty(obj.Portfolios(k).NettingSets)
                        trNSIdx = strcmpi(obj.Portfolios(k).Trades(tr).NettingSetID, ...
                            obj.Portfolios(k).NettingSets.ID);
                        if any(trNSIdx) && ...
                                ~isempty(obj.Portfolios(k).NettingSets(trNSIdx).CollateralSets)
                            trCSIdx = strcmpi(obj.Portfolios(k).Trades(tr).CollateralSetID, ...
                                [obj.Portfolios(k).NettingSets(trNSIdx).CollateralSets.ID]');

                            if any(trCSIdx)
                                MPOR = obj.Portfolios(k).NettingSets(trNSIdx).CollateralSets(trCSIdx).MPOR;
                                obj.Portfolios(k).Trades(tr).MaturityFactorCollateralized = ...
                                    1.5.*sqrt(MPOR./obj.NumBusinessDaysYear);
                            end
                        end
                    end
                end
            end
        end

        function outEADResults = ead(obj)
            %EAD Compute Exposure-at-Default (EAD) Values
            %
            % Syntax:
            %
            %  outEADResults = ead(saccrObject)
            %
            % Description:
            %
            %  Compute Exposure-at-Default (EAD) value for each portfolio
            %  using the saccr object.
            %
            % Input Argument:
            %
            %  Required:
            %   saccrObject  - saccr object
            %
            % Output Argument:
            %
            %   outEADResults - Output saccr.EADResults object
            %
            % Example:
            %
            % mySACCR = saccr("SACCR_CRIF.xlsx");
            % EADResults = ead(mySACCR);
            %
            % See also SACCR.EADRESULTS, SACCR.RCRESULTS, SACCR.PFERESULTS,
            %          SACCR.ADDONRESULTS, SACCR.ADDONASSETCLASSRESULTS.

            outEADResults = saccr.EADResults;
            outEADResults.NumPortfolios = obj.NumPortfolios;
            outEADResults.PortfolioIDs = obj.PortfolioIDs;
            outEADResults.CounterpartyIDs = obj.CounterpartyIDs;
            outEADResults.Regulation = obj.Regulation;
            outEADResults.DomesticCurrency = obj.DomesticCurrency;
            outEADResults.Alpha = obj.Alpha;

            % Compute RCs and PFEs
            outEADResults.RCResults = rc(obj);
            outEADResults.PFEResults = pfe(obj);

            % Compute EADs
            outEADResults.EAD = nan(obj.NumPortfolios,1);
            outEADResults.RC = outEADResults.EAD;
            outEADResults.PFE = outEADResults.EAD;
            outEADResults.Multiplier = outEADResults.EAD;
            outEADResults.AddOnAggregate = outEADResults.EAD;
            AddOnIR = outEADResults.EAD;
            AddOnFX = outEADResults.EAD;
            AddOnCR = outEADResults.EAD;
            AddOnEQ = outEADResults.EAD;
            AddOnCO = outEADResults.EAD;
            Collateralized = false(obj.NumPortfolios,1);
            UsedCollateral = Collateralized;

            for k=1:obj.NumPortfolios
                EADUncoll = (outEADResults.RCResults.RCUncollateralized(k) + ...
                    outEADResults.PFEResults.MultiplierUncollateralized(k).*...
                    outEADResults.PFEResults.AddOnResults.AddOnAggregateUncollateralized(k)).* ...
                    outEADResults.Alpha(k);
                if isnan(outEADResults.RCResults.RCCollateralized(k))
                    outEADResults.EAD(k) = EADUncoll;
                else
                    Collateralized(k) = true;
                    EADColl = (outEADResults.RCResults.RCCollateralized(k) + ...
                        outEADResults.PFEResults.MultiplierCollateralized(k).*...
                        outEADResults.PFEResults.AddOnResults.AddOnAggregateCollateralized(k)).* ...
                        outEADResults.Alpha(k);
                    outEADResults.EAD(k) = min(EADColl, EADUncoll);
                    if outEADResults.EAD(k) < EADUncoll(1)
                        UsedCollateral(k) = true;
                    end
                end

                if UsedCollateral(k)
                    outEADResults.RC(k) = outEADResults.RCResults.RCCollateralized(k);
                    outEADResults.PFE(k) = outEADResults.PFEResults.PFECollateralized(k);
                    outEADResults.Multiplier(k) = outEADResults.PFEResults.MultiplierCollateralized(k);
                    outEADResults.AddOnAggregate(k) = outEADResults.PFEResults.AddOnResults.AddOnAggregateCollateralized(k);
                    AddOnIR(k) = outEADResults.PFEResults.AddOnResults.AddOnAssetClassesCollateralized.AddOnInterestRate(k);
                    AddOnFX(k) = outEADResults.PFEResults.AddOnResults.AddOnAssetClassesCollateralized.AddOnForeignExchange(k);
                    AddOnCR(k) = outEADResults.PFEResults.AddOnResults.AddOnAssetClassesCollateralized.AddOnCredit(k);
                    AddOnEQ(k) = outEADResults.PFEResults.AddOnResults.AddOnAssetClassesCollateralized.AddOnEquity(k);
                    AddOnCO(k) = outEADResults.PFEResults.AddOnResults.AddOnAssetClassesCollateralized.AddOnCommodity(k);
                else
                    outEADResults.RC(k) = outEADResults.RCResults.RCUncollateralized(k);
                    outEADResults.PFE(k) = outEADResults.PFEResults.PFEUncollateralized(k);
                    outEADResults.Multiplier(k) = outEADResults.PFEResults.MultiplierUncollateralized(k);
                    outEADResults.AddOnAggregate(k) = outEADResults.PFEResults.AddOnResults.AddOnAggregateUncollateralized(k);
                    AddOnIR(k) = outEADResults.PFEResults.AddOnResults.AddOnAssetClassesUncollateralized.AddOnInterestRate(k);
                    AddOnFX(k) = outEADResults.PFEResults.AddOnResults.AddOnAssetClassesUncollateralized.AddOnForeignExchange(k);
                    AddOnCR(k) = outEADResults.PFEResults.AddOnResults.AddOnAssetClassesUncollateralized.AddOnCredit(k);
                    AddOnEQ(k) = outEADResults.PFEResults.AddOnResults.AddOnAssetClassesUncollateralized.AddOnEquity(k);
                    AddOnCO(k) = outEADResults.PFEResults.AddOnResults.AddOnAssetClassesUncollateralized.AddOnCommodity(k);
                end
            end

            RegulationStr = repmat(obj.Regulation, obj.NumPortfolios, 1);
            DomCurrencyStr = repmat(obj.DomesticCurrency, obj.NumPortfolios, 1);

            outEADResults.ResultsTable = ...
                table(obj.PortfolioIDs, obj.CounterpartyIDs, RegulationStr, DomCurrencyStr, outEADResults.EAD,...
                outEADResults.Alpha, outEADResults.RC, outEADResults.PFE, outEADResults.Multiplier, outEADResults.AddOnAggregate, ...
                AddOnIR, AddOnFX, AddOnCR, AddOnEQ, AddOnCO, Collateralized, UsedCollateral, ...
                VariableNames=["PortfolioIDs","CounterpartyIDs","Regulation","DomesticCurrency","EAD",...
                "Alpha","RC","PFE","Multiplier","AddOnAggregate","AddOnIR","AddOnFX","AddOnCR",...
                "AddOnEQ","AddOnCO","Collateralized","UsedCollateral"]);
        end

        function outRCResults = rc(obj)
            %RC Compute Replacement Costs (RC)
            %
            % Syntax:
            %
            %  outRCResults = rc(saccrObject)
            %
            % Description:
            %
            %  Compute Replacement Cost (RC) for each portfolio
            %  using the saccr object.
            %
            % Input Argument:
            %
            %  Required:
            %   saccrObject  - saccr object
            %
            % Output Argument:
            %
            %   outRCResults - Output saccr.RCResults object
            %
            % Example:
            %
            % mySACCR = saccr("SACCR_CRIF.xlsx");
            % RCResults = rc(mySACCR);
            %
            % See also SACCR.RCRESULTS.

            outRCResults = saccr.RCResults;
            outRCResults.NumPortfolios = obj.NumPortfolios;
            outRCResults.PortfolioIDs = obj.PortfolioIDs;
            outRCResults.CounterpartyIDs = obj.CounterpartyIDs;
            outRCResults.Regulation = obj.Regulation;
            outRCResults.DomesticCurrency = obj.DomesticCurrency;
            outRCResults.RCUncollateralized = nan(obj.NumPortfolios,1);
            outRCResults.RCCollateralized = outRCResults.RCUncollateralized;

            for k=1:obj.NumPortfolios
                [~, ~, RCUncollateralized, RCCollateralized] = netValueRC(obj, k);

                outRCResults.RCUncollateralized(k) = sum(RCUncollateralized);

                if isempty(obj.Portfolios(k).NettingSets)
                    outRCResults.RCCollateralized(k) = NaN;
                elseif isempty(obj.Portfolios(k).NettingSets.CollateralSets)
                    outRCResults.RCCollateralized(k) = NaN;
                elseif all([obj.Portfolios(k).NettingSets.CollateralSets.STM]')
                    outRCResults.RCCollateralized(k) = NaN;
                else
                    outRCResults.RCCollateralized(k) = sum(RCCollateralized);
                end
            end
        end

        function outPFEResults = pfe(obj)
            %PFE Compute Potential Future Exposures (PFE)
            %
            % Syntax:
            %
            %  outPFEResults = pfe(saccrObject)
            %
            % Description:
            %
            %  Compute Potential Future Exposure (PFE) for each portfolio
            %  using the saccr object.
            %
            % Input Argument:
            %
            %  Required:
            %   saccrObject  - saccr object
            %
            % Output Argument:
            %
            %   outPFEResults - Output saccr.PFEResults object
            %
            % Example:
            %
            % mySACCR = saccr("SACCR_CRIF.xlsx");
            % PFEResults = pfe(mySACCR);
            %
            % See also SACCR.PFERESULTS, SACCR.ADDONRESULTS,
            %          SACCR.ADDONASSETCLASSRESULTS.

            outPFEResults = saccr.PFEResults;
            outPFEResults.NumPortfolios = obj.NumPortfolios;
            outPFEResults.PortfolioIDs = obj.PortfolioIDs;
            outPFEResults.CounterpartyIDs = obj.CounterpartyIDs;
            outPFEResults.Regulation = obj.Regulation;
            outPFEResults.DomesticCurrency = obj.DomesticCurrency;

            % Compute AddOns
            outPFEResults.AddOnResults = addOn(obj);

            outPFEResults.MultiplierUncollateralized = nan(outPFEResults.NumPortfolios,1);
            outPFEResults.MultiplierCollateralized = outPFEResults.MultiplierUncollateralized;
            outPFEResults.PFEUncollateralized = nan(outPFEResults.NumPortfolios,1);
            outPFEResults.PFECollateralized = outPFEResults.PFEUncollateralized;


            for k=1:obj.NumPortfolios
                [VminusCUncoll, VminusCColl] = netValueRC(obj, k);

                VminusCUncoll = sum(VminusCUncoll);
                VminusCColl = sum(VminusCColl);

                % Compute Multipliers
                if abs(outPFEResults.AddOnResults.AddOnAggregateUncollateralized(k)) <= (1e6*eps)
                    outPFEResults.MultiplierUncollateralized(k) = 1;
                else
                    outPFEResults.MultiplierUncollateralized(k) = ...
                        min(1, (obj.PFEMultiplierFloor + (1 - obj.PFEMultiplierFloor) .* ...
                        exp(VminusCUncoll./(2.*(1-obj.PFEMultiplierFloor).* ...
                        outPFEResults.AddOnResults.AddOnAggregateUncollateralized(k)))));
                end

                if abs(outPFEResults.AddOnResults.AddOnAggregateCollateralized(k)) <= (1e6*eps)
                    outPFEResults.MultiplierCollateralized(k) = 1;
                else
                    outPFEResults.MultiplierCollateralized(k) = ...
                        min(1, (obj.PFEMultiplierFloor + (1 - obj.PFEMultiplierFloor) .* ...
                        exp(VminusCColl./(2.*(1-obj.PFEMultiplierFloor).* ...
                        outPFEResults.AddOnResults.AddOnAggregateCollateralized(k)))));
                end

                if isempty(obj.Portfolios(k).NettingSets)
                    outPFEResults.MultiplierCollateralized(k) = NaN;
                elseif isempty(obj.Portfolios(k).NettingSets.CollateralSets)
                    outPFEResults.MultiplierCollateralized(k) = NaN;
                elseif all([obj.Portfolios(k).NettingSets.CollateralSets.STM]')
                    outPFEResults.MultiplierCollateralized(k) = NaN;
                end

                % Compute PFEs
                outPFEResults.PFEUncollateralized(k) = ...
                    outPFEResults.MultiplierUncollateralized(k).* ...
                    outPFEResults.AddOnResults.AddOnAggregateUncollateralized(k);

                outPFEResults.PFECollateralized(k) = ...
                    outPFEResults.MultiplierCollateralized(k).* ...
                    outPFEResults.AddOnResults.AddOnAggregateCollateralized(k);
            end
        end

        function outAddOnResults = addOn(obj)
            %ADDON Compute Aggregate Add-Ons
            %
            % Syntax:
            %
            %  outAddOnResults = addOn(saccrObject)
            %
            % Description:
            %
            %  Compute Add-On aggregated from all asset classes for each
            %  portfolio using the saccr object.
            %
            % Input Argument:
            %
            %  Required:
            %   saccrObject  - saccr object
            %
            % Output Argument:
            %
            %   outAddOnResults - Output saccr.AddOnResults object
            %
            % Example:
            %
            % mySACCR = saccr("SACCR_CRIF.xlsx");
            % AddOnResults = addOn(mySACCR);
            %
            % See also SACCR.ADDONRESULTS, SACCR.ADDONASSETCLASSRESULTS.

            outAddOnResults = saccr.AddOnResults;
            outAddOnResults.NumPortfolios = obj.NumPortfolios;
            outAddOnResults.PortfolioIDs = obj.PortfolioIDs;
            outAddOnResults.CounterpartyIDs = obj.CounterpartyIDs;
            outAddOnResults.Regulation = obj.Regulation;
            outAddOnResults.DomesticCurrency = obj.DomesticCurrency;

            outAddOnResults.AddOnAssetClassesUncollateralized = saccr.AddOnAssetClassResults;
            outAddOnResults.AddOnAssetClassesUncollateralized.NumPortfolios = obj.NumPortfolios;
            outAddOnResults.AddOnAssetClassesUncollateralized.PortfolioIDs = obj.PortfolioIDs;
            outAddOnResults.AddOnAssetClassesUncollateralized.Regulation = obj.Regulation;
            outAddOnResults.AddOnAssetClassesUncollateralized.DomesticCurrency = obj.DomesticCurrency;

            outAddOnResults.AddOnAssetClassesCollateralized = saccr.AddOnAssetClassResults;
            outAddOnResults.AddOnAssetClassesCollateralized.NumPortfolios = obj.NumPortfolios;
            outAddOnResults.AddOnAssetClassesCollateralized.PortfolioIDs = obj.PortfolioIDs;
            outAddOnResults.AddOnAssetClassesCollateralized.Regulation = obj.Regulation;
            outAddOnResults.AddOnAssetClassesCollateralized.DomesticCurrency = obj.DomesticCurrency;

            % Compute IR (Interest Rate) AddOns
            outAddOnResults = addOnIR(obj, outAddOnResults);

            % Compute FX (Foreign Exchange) AddOns
            outAddOnResults = addOnFX(obj, outAddOnResults);

            % Compute CR (Credit) AddOns
            outAddOnResults = addOnCR(obj, outAddOnResults);

            % Compute EQ (Equity) AddOns
            outAddOnResults = addOnEQ(obj, outAddOnResults);

            % Compute CO (Commodity) AddOns
            outAddOnResults = addOnCO(obj, outAddOnResults);

            % Aggregate Add-Ons over asset classes
            outAddOnResults.AddOnAggregateUncollateralized = ...
                aggregate(outAddOnResults.AddOnAssetClassesUncollateralized);
            outAddOnResults.AddOnAggregateCollateralized = ...
                aggregate(outAddOnResults.AddOnAssetClassesCollateralized);
        end
    end

    methods(Access=private)
        function inpData = loadSACCRCRIFData(~, filename)
            % Load SACCR-CRIF data from file

            % Create import options
            opts = detectImportOptions(filename, TextType='string');
            opts = setvartype(opts, ["Label1", "Label2"], ["string", "string"]);

            % Import the data
            inpData = readtable(filename, opts);
        end

        function outAddOnIRResults = addOnIR(obj, inAddOnIRResults)
            outAddOnIRResults = inAddOnIRResults;
            outAddOnIRResults.AddOnAssetClassesUncollateralized.AddOnInterestRate = ...
                nan(outAddOnIRResults.NumPortfolios,1);
            outAddOnIRResults.AddOnAssetClassesCollateralized.AddOnInterestRate = ...
                nan(outAddOnIRResults.NumPortfolios,1);

            for k=1:obj.NumPortfolios
                IRTradeIdx = strncmpi("IR",[obj.Portfolios(k).Trades.AssetClass]',2);
                IRTrades = obj.Portfolios(k).Trades(IRTradeIdx);
                NumIRTrades = length(IRTrades);
                MPOR = nan(NumIRTrades,1);

                NumTradeTableRows = 0;
                for tr=1:NumIRTrades
                    NumTradeTableRows = ...
                        NumTradeTableRows + length(IRTrades(tr).AdjustedNotional);
                end

                IRTradeTable = table(Size=[NumTradeTableRows,7],...
                    VariableTypes=["string" "string" "double" "double" "string" "double" "double"]);
                IRTradeTable.Properties.VariableNames = ...
                    ["TradeID","HedgingSet","MPOR","MaturityBucket","EquivalenceClass",...
                    "AddOnCollateralized", "AddOnUncollateralized"];

                TableRow = 1;
                for tr=1:NumIRTrades
                    NumComponents = length(IRTrades(tr).AdjustedNotional);
                    MaturityBucket = nan(NumComponents,1);
                    BasisVolMultiplier = MaturityBucket;
                    EndTime = IRTrades(tr).EndTime;

                    for c=1:NumComponents
                        if EndTime(c) < 1
                            % MaturityBucket 1: "< 1Y" ("Less than 1Y")
                            MaturityBucket(c) = 1;
                        elseif EndTime(c) <= 5
                            % MaturityBucket 2: ">= 1Y, <= 5Y" ("Between 1Y and 5Y")
                            MaturityBucket(c) = 2;
                        else
                            % MaturityBucket 3: "> 5Y" ("More than 5Y")
                            MaturityBucket(c) = 3;
                        end
                    end

                    if ~isempty(obj.Portfolios(k).NettingSets)
                        NSIdx = strcmpi(IRTrades(tr).NettingSetID, obj.Portfolios(k).NettingSets.ID);
                        NettingSet = obj.Portfolios(k).NettingSets(NSIdx);
                        if ~isempty(NettingSet)
                            if ~isempty(NettingSet.CollateralSets)
                                CSIdx = strcmpi(IRTrades(tr).CollateralSetID, [NettingSet.CollateralSets.ID]');
                                CollateralSet = NettingSet.CollateralSets(CSIdx);
                                MPOR(tr) = CollateralSet.MPOR;
                            end
                        end
                    end

                    EquivalenceClass = IRTrades(tr).HedgingSet;
                    if ~isnan(MPOR(tr))
                        EquivalenceClass = EquivalenceClass + ...
                            repmat("_",NumComponents,1) + repmat(num2str(MPOR(tr)),NumComponents,1);
                    end

                    if strcmpi(obj.DomesticCurrency,"USD")
                        AdjustedNotional = IRTrades(tr).AdjustedNotionalUSD;
                    else
                        Currency = IRTrades(tr).AdjustedNotionalCurrency;
                        AdjustedNotional = IRTrades(tr).AdjustedNotional;
                        if ~strcmpi(obj.DomesticCurrency,Currency)
                            if isempty(obj.FXSpotRates)
                                error(message('fininst:saccr:missingFXSpotRates'));
                            end
                            FXSpotRate = exchangeRate(obj, Currency, obj.DomesticCurrency);
                            AdjustedNotional = AdjustedNotional.*FXSpotRate;
                        end
                    end

                    SupervisoryDelta = IRTrades(tr).SupervisoryDelta;

                    % Check if trade is a Sold Option outside netting and margin agreements (CRE 52.1 FAQ1)
                    if IRTrades(tr).SoldOption && strncmpi("Missing_NettingSet", IRTrades(tr).NettingSetID,18)
                        AdjustedNotionalSupervisoryDelta = zeros(NumComponents,1);
                    else
                        AdjustedNotionalSupervisoryDelta = AdjustedNotional.*SupervisoryDelta;
                    end

                    MaturityFactorUncollateralized = IRTrades(tr).MaturityFactorUncollateralized;
                    MaturityFactorCollateralized = IRTrades(tr).MaturityFactorCollateralized;

                    HedgingSet = IRTrades(tr).HedgingSet;
                    for c=1:NumComponents
                        if contains(HedgingSet(c),"basis",IgnoreCase=true)
                            % BCBS279_162: Hedging Sets for Basis Transactions
                            BasisVolMultiplier(c) = 0.5;
                        elseif contains(IRTrades(tr).HedgingSet,"vol",IgnoreCase=true)
                            % BCBS279_163: Hedging Sets for Volatility Transactions
                            BasisVolMultiplier(c) = 5;
                        else
                            BasisVolMultiplier(c) = 1;
                        end
                    end

                    Key = IRTrades(tr).AssetClass + "_";
                    SupervisoryParametersIdx = strcmpi(Key, obj.SupervisoryParameters.Key);
                    if all(~SupervisoryParametersIdx)
                        error(message('fininst:saccr:missingSupervisoryParameters', Key));
                    end
                    TradeSupervisoryParameters = obj.SupervisoryParameters(SupervisoryParametersIdx,:);
                    SupervisoryFactor = TradeSupervisoryParameters.SupervisoryFactor;

                    AddOnUncollateralized = ...
                        AdjustedNotionalSupervisoryDelta.*MaturityFactorUncollateralized.* ...
                        BasisVolMultiplier.*SupervisoryFactor;

                    AddOnCollateralized = ...
                        AdjustedNotionalSupervisoryDelta.*MaturityFactorCollateralized.* ...
                        BasisVolMultiplier.*SupervisoryFactor;

                    if strncmpi("Missing_NettingSet", IRTrades(tr).NettingSetID,18)
                        AddOnUncollateralized = ...
                            abs(AddOnUncollateralized);
                    end

                    IRTradeTable(TableRow:TableRow+NumComponents-1,:) = ...
                        table(repmat(IRTrades(tr).ID,NumComponents,1), ...
                        HedgingSet, repmat(MPOR(tr),NumComponents,1), ...
                        MaturityBucket, EquivalenceClass, ...
                        AddOnCollateralized, AddOnUncollateralized,...
                        VariableNames=["TradeID","HedgingSet","MPOR","MaturityBucket","EquivalenceClass",...
                        "AddOnCollateralized", "AddOnUncollateralized"]);

                    TableRow = TableRow + NumComponents;
                end

                EquivalenceClasses = unique(IRTradeTable.EquivalenceClass);
                NumEquivalenceClasses = height(EquivalenceClasses);

                % Aggregate Maturity Bucket Add-Ons
                % MaturityBucket 1 (DB1): "< 1Y" ("Less than 1Y")
                % MaturityBucket 2 (DB2): ">= 1Y, <= 5Y" ("Between 1Y and 5Y")
                % MaturityBucket 3 (DB3): "> 5Y" ("More than 5Y")
                DB1_Uncollateralized = nan(NumEquivalenceClasses,1);
                DB2_Uncollateralized = DB1_Uncollateralized;
                DB3_Uncollateralized = DB1_Uncollateralized;

                DB1_Collateralized = nan(NumEquivalenceClasses,1);
                DB2_Collateralized = DB1_Collateralized;
                DB3_Collateralized = DB1_Collateralized;

                for ec=1:NumEquivalenceClasses
                    DB1Idx = IRTradeTable.MaturityBucket==1 & ...
                        strcmpi(EquivalenceClasses(ec),IRTradeTable.EquivalenceClass);
                    DB1_Uncollateralized(ec) = sum(IRTradeTable.AddOnUncollateralized(DB1Idx));
                    DB1_Collateralized(ec) = sum(IRTradeTable.AddOnCollateralized(DB1Idx));

                    DB2Idx = IRTradeTable.MaturityBucket==2 & ...
                        strcmpi(EquivalenceClasses(ec),IRTradeTable.EquivalenceClass);
                    DB2_Uncollateralized(ec) = sum(IRTradeTable.AddOnUncollateralized(DB2Idx));
                    DB2_Collateralized(ec) = sum(IRTradeTable.AddOnCollateralized(DB2Idx));

                    DB3Idx = IRTradeTable.MaturityBucket==3 & ...
                        strcmpi(EquivalenceClasses(ec),IRTradeTable.EquivalenceClass);
                    DB3_Uncollateralized(ec) = sum(IRTradeTable.AddOnUncollateralized(DB3Idx));
                    DB3_Collateralized(ec) = sum(IRTradeTable.AddOnCollateralized(DB3Idx));
                end

                if isempty(obj.Portfolios(k).NettingSets)
                    % No Netting Set: No Offset
                    IRAddonUncollateralizedEq = abs(DB1_Uncollateralized) + ...
                        abs(DB2_Uncollateralized) + abs(DB3_Uncollateralized);

                    IRAddonCollateralizedEq = abs(DB1_Collateralized) + ...
                        abs(DB2_Collateralized) + abs(DB3_Collateralized);
                else
                    % Has Netting Set: With Offset
                    IRAddonUncollateralizedEq = (DB1_Uncollateralized.^2 + ...
                        DB2_Uncollateralized.^2 + DB3_Uncollateralized.^2 + ...
                        1.4.*DB1_Uncollateralized.*DB2_Uncollateralized + ...
                        1.4.*DB2_Uncollateralized.*DB3_Uncollateralized + ...
                        0.6.*DB1_Uncollateralized.*DB3_Uncollateralized).^0.5;

                    IRAddonCollateralizedEq = (DB1_Collateralized.^2 + ...
                        DB2_Collateralized.^2 + DB3_Collateralized.^2 + ...
                        1.4.*DB1_Collateralized.*DB2_Collateralized + ...
                        1.4.*DB2_Collateralized.*DB3_Collateralized + ...
                        0.6.*DB1_Collateralized.*DB3_Collateralized).^0.5;
                end
                outAddOnIRResults.AddOnAssetClassesUncollateralized.AddOnInterestRate(k) = ...
                    sum(IRAddonUncollateralizedEq);

                if ~isempty(obj.Portfolios(k).NettingSets) && ...
                        ~isempty(obj.Portfolios(k).NettingSets.CollateralSets)
                    if ~all([obj.Portfolios(k).NettingSets.CollateralSets.STM]')
                        outAddOnIRResults.AddOnAssetClassesCollateralized.AddOnInterestRate(k) = ...
                            sum(IRAddonCollateralizedEq);
                    end
                end
            end
        end

        function outAddOnFXResults = addOnFX(obj, inAddOnFXResults)
            outAddOnFXResults = inAddOnFXResults;
            outAddOnFXResults.AddOnAssetClassesUncollateralized.AddOnForeignExchange = ...
                nan(outAddOnFXResults.NumPortfolios,1);
            outAddOnFXResults.AddOnAssetClassesCollateralized.AddOnForeignExchange = ...
                nan(outAddOnFXResults.NumPortfolios,1);

            for k=1:obj.NumPortfolios
                FXTradeIdx = strncmpi("FX",[obj.Portfolios(k).Trades.AssetClass]',2);
                FXTrades = obj.Portfolios(k).Trades(FXTradeIdx);
                NumFXTrades = length(FXTrades);
                MPOR = nan(NumFXTrades,1);

                NumTradeTableRows = 0;
                for tr=1:NumFXTrades
                    NumTradeTableRows = ...
                        NumTradeTableRows + length(FXTrades(tr).AdjustedNotional);
                end

                FXTradeTable = table(Size=[NumTradeTableRows,6],...
                    VariableTypes=["string" "string" "double" "string" "double" "double"]);
                FXTradeTable.Properties.VariableNames = ...
                    ["TradeID","HedgingSet","MPOR","EquivalenceClass",...
                    "AddOnCollateralized", "AddOnUncollateralized"];

                TableRow = 1;
                for tr=1:NumFXTrades
                    NumComponents = length(FXTrades(tr).AdjustedNotional);
                    if ~isempty(obj.Portfolios(k).NettingSets)
                        NSIdx = strcmpi(FXTrades(tr).NettingSetID, obj.Portfolios(k).NettingSets.ID);
                        NettingSet = obj.Portfolios(k).NettingSets(NSIdx);
                        if ~isempty(NettingSet)
                            if ~isempty(NettingSet.CollateralSets)
                                CSIdx = strcmpi(FXTrades(tr).CollateralSetID, [NettingSet.CollateralSets.ID]');
                                CollateralSet = NettingSet.CollateralSets(CSIdx);
                                MPOR(tr) = CollateralSet.MPOR;
                            end
                        end
                    end

                    EquivalenceClass = FXTrades(tr).HedgingSet;
                    if ~isnan(MPOR(tr))
                        EquivalenceClass = EquivalenceClass + ...
                            repmat("_",NumComponents,1) + repmat(num2str(MPOR(tr)),NumComponents,1);
                    end

                    if strcmpi(obj.DomesticCurrency,"USD")
                        AdjustedNotional = FXTrades(tr).AdjustedNotionalUSD;
                    else
                        Currency = FXTrades(tr).AdjustedNotionalCurrency;
                        AdjustedNotional = FXTrades(tr).AdjustedNotional;
                        if ~strcmpi(obj.DomesticCurrency,Currency)
                            if isempty(obj.FXSpotRates)
                                error(message('fininst:saccr:missingFXSpotRates'));
                            end
                            FXSpotRate = exchangeRate(obj, Currency, obj.DomesticCurrency);
                            AdjustedNotional = AdjustedNotional.*FXSpotRate;
                        end
                    end

                    SupervisoryDelta = FXTrades(tr).SupervisoryDelta;

                    % Check if trade is a Sold Option outside netting and margin agreements (CRE 52.1 FAQ1)
                    if FXTrades(tr).SoldOption && strncmpi("Missing_NettingSet", FXTrades(tr).NettingSetID,18)
                        AdjustedNotionalSupervisoryDelta = zeros(NumComponents,1);
                    else
                        AdjustedNotionalSupervisoryDelta = AdjustedNotional.*SupervisoryDelta;
                    end

                    MaturityFactorUncollateralized = FXTrades(tr).MaturityFactorUncollateralized;
                    MaturityFactorCollateralized = FXTrades(tr).MaturityFactorCollateralized;

                    HedgingSet = FXTrades(tr).HedgingSet;
                    BasisVolMultiplier = nan(NumComponents,1);
                    for c=1:NumComponents
                        if contains(HedgingSet(c),"basis",IgnoreCase=true)
                            % BCBS279_162: Hedging Sets for Basis Transactions
                            BasisVolMultiplier(c) = 0.5;
                        elseif contains(HedgingSet(c),"vol",IgnoreCase=true)
                            % BCBS279_163: Hedging Sets for Volatility Transactions
                            BasisVolMultiplier(c) = 5;
                        else
                            BasisVolMultiplier(c) = 1;
                        end
                    end

                    Key = FXTrades(tr).AssetClass + "_";
                    SupervisoryParametersIdx = strcmpi(Key, obj.SupervisoryParameters.Key);
                    if all(~SupervisoryParametersIdx)
                        error(message('fininst:saccr:missingSupervisoryParameters', Key));
                    end
                    TradeSupervisoryParameters = obj.SupervisoryParameters(SupervisoryParametersIdx,:);
                    SupervisoryFactor = TradeSupervisoryParameters.SupervisoryFactor;

                    AddOnUncollateralized = ...
                        AdjustedNotionalSupervisoryDelta.*MaturityFactorUncollateralized.* ...
                        BasisVolMultiplier.*SupervisoryFactor;

                    AddOnCollateralized = ...
                        AdjustedNotionalSupervisoryDelta.*MaturityFactorCollateralized.* ...
                        BasisVolMultiplier.*SupervisoryFactor;

                    if strncmpi("Missing_NettingSet", FXTrades(tr).NettingSetID,18)
                        AddOnUncollateralized = abs(AddOnUncollateralized);
                    end
                    FXTradeTable(TableRow:TableRow+NumComponents-1,:) = ...
                        table(repmat(FXTrades(tr).ID,NumComponents,1), ...
                        HedgingSet, repmat(MPOR(tr),NumComponents,1), EquivalenceClass, ...
                        AddOnCollateralized, AddOnUncollateralized,...
                        VariableNames=["TradeID","HedgingSet","MPOR","EquivalenceClass",...
                        "AddOnCollateralized", "AddOnUncollateralized"]);

                    TableRow = TableRow + NumComponents;
                end

                EquivalenceClasses = unique(FXTradeTable.EquivalenceClass);
                NumEquivalenceClasses = height(EquivalenceClasses);

                % Aggregate FX Add-Ons
                FXAddonUncollateralizedEq = nan(NumEquivalenceClasses,1);
                FXAddonCollateralizedEq = FXAddonUncollateralizedEq;

                for ec=1:NumEquivalenceClasses
                    ecIdx = strcmpi(EquivalenceClasses(ec),FXTradeTable.EquivalenceClass);

                    if isempty(obj.Portfolios(k).NettingSets)
                        % No Netting Set: No Offset
                        FXAddonUncollateralizedEq(ec) = ...
                            sum(abs(FXTradeTable.AddOnUncollateralized(ecIdx)));

                        FXAddonCollateralizedEq(ec) = ...
                            sum(abs(FXTradeTable.AddOnCollateralized(ecIdx)));
                    else
                        % Has Netting Set: With Offset
                        FXAddonUncollateralizedEq(ec) = ...
                            sum(FXTradeTable.AddOnUncollateralized(ecIdx));

                        FXAddonCollateralizedEq(ec) = ...
                            sum(FXTradeTable.AddOnCollateralized(ecIdx));
                    end
                end

                outAddOnFXResults.AddOnAssetClassesUncollateralized.AddOnForeignExchange(k) = ...
                    sum(abs(FXAddonUncollateralizedEq));

                if ~isempty(obj.Portfolios(k).NettingSets) && ...
                        ~isempty(obj.Portfolios(k).NettingSets.CollateralSets)
                    if ~all([obj.Portfolios(k).NettingSets.CollateralSets.STM]')
                        outAddOnFXResults.AddOnAssetClassesCollateralized.AddOnForeignExchange(k) = ...
                            sum(abs(FXAddonCollateralizedEq));
                    end
                end
            end
        end

        function outAddOnCRResults = addOnCR(obj, inAddOnCRResults)
            outAddOnCRResults = inAddOnCRResults;
            outAddOnCRResults.AddOnAssetClassesUncollateralized.AddOnCredit = ...
                nan(outAddOnCRResults.NumPortfolios,1);
            outAddOnCRResults.AddOnAssetClassesCollateralized.AddOnCredit = ...
                nan(outAddOnCRResults.NumPortfolios,1);

            for k=1:obj.NumPortfolios
                CRTradeIdx = strncmpi("CR",[obj.Portfolios(k).Trades.AssetClass]',2);
                CRTrades = obj.Portfolios(k).Trades(CRTradeIdx);
                NumCRTrades = length(CRTrades);
                MPOR = nan(NumCRTrades,1);

                NumTradeTableRows = 0;
                for tr=1:NumCRTrades
                    NumTradeTableRows = ...
                        NumTradeTableRows + length(CRTrades(tr).AdjustedNotional);
                end

                CRTradeTable = table(Size=[NumTradeTableRows,8],...
                    VariableTypes=...
                    ["string" "string" "double" "double" "string" "string" "double" "double"]);
                CRTradeTable.Properties.VariableNames = ...
                    ["TradeID","HedgingSet","MPOR","SupervisoryCorrelation",...
                    "EquivalenceClass","QualifierClass",...
                    "AddOnCollateralized", "AddOnUncollateralized"];

                TableRow = 1;
                for tr=1:NumCRTrades
                    NumComponents = length(CRTrades(tr).AdjustedNotional);
                    if ~isempty(obj.Portfolios(k).NettingSets)
                        NSIdx = strcmpi(CRTrades(tr).NettingSetID, obj.Portfolios(k).NettingSets.ID);
                        if any(NSIdx)
                            NettingSet = obj.Portfolios(k).NettingSets(NSIdx);
                            if ~isempty(NettingSet)
                                if ~isempty(NettingSet.CollateralSets)
                                    CSIdx = strcmpi(CRTrades(tr).CollateralSetID, [NettingSet.CollateralSets.ID]');
                                    if any(CSIdx)
                                        CollateralSet = NettingSet.CollateralSets(CSIdx);
                                        MPOR(tr) = CollateralSet.MPOR;
                                    end
                                end
                            end
                        end
                    end

                    QualifierClass = CRTrades(tr).Qualifier;
                    EquivalenceClass = CRTrades(tr).HedgingSet;
                    if ~isnan(MPOR(tr))
                        EquivalenceClass = EquivalenceClass + ...
                            repmat("_",NumComponents,1) + repmat(num2str(MPOR(tr)),NumComponents,1);
                        QualifierClass = QualifierClass + ...
                            repmat("_",NumComponents,1) + repmat(num2str(MPOR(tr)),NumComponents,1);
                    end

                    if strcmpi(obj.DomesticCurrency,"USD")
                        AdjustedNotional = CRTrades(tr).AdjustedNotionalUSD;
                    else
                        Currency = CRTrades(tr).AdjustedNotionalCurrency;
                        AdjustedNotional = CRTrades(tr).AdjustedNotional;
                        if ~strcmpi(obj.DomesticCurrency,Currency)
                            if isempty(obj.FXSpotRates)
                                error(message('fininst:saccr:missingFXSpotRates'));
                            end
                            FXSpotRate = exchangeRate(obj, Currency, obj.DomesticCurrency);
                            AdjustedNotional = AdjustedNotional.*FXSpotRate;
                        end
                    end

                    SupervisoryDelta = CRTrades(tr).SupervisoryDelta;

                    % Check if trade is a Sold Option outside netting and margin agreements (CRE 52.1 FAQ1)
                    if CRTrades(tr).SoldOption && strncmpi("Missing_NettingSet", CRTrades(tr).NettingSetID,18)
                        AdjustedNotionalSupervisoryDelta = zeros(NumComponents,1);
                    else
                        AdjustedNotionalSupervisoryDelta = AdjustedNotional.*SupervisoryDelta;
                    end

                    MaturityFactorUncollateralized = CRTrades(tr).MaturityFactorUncollateralized;
                    MaturityFactorCollateralized = CRTrades(tr).MaturityFactorCollateralized;

                    HedgingSet = CRTrades(tr).HedgingSet;
                    BasisVolMultiplier = nan(NumComponents,1);
                    for c=1:NumComponents
                        if contains(HedgingSet(c),"basis",IgnoreCase=true)
                            % BCBS279_162: Hedging Sets for Basis Transactions
                            BasisVolMultiplier(c) = 0.5;
                        elseif contains(HedgingSet(c),"vol",IgnoreCase=true)
                            % BCBS279_163: Hedging Sets for Volatility Transactions
                            BasisVolMultiplier(c) = 5;
                        else
                            BasisVolMultiplier(c) = 1;
                        end
                    end

                    Key = CRTrades(tr).AssetClass + "_" + CRTrades(tr).SubClass;
                    SupervisoryParametersIdx = strcmpi(Key, obj.SupervisoryParameters.Key);
                    if all(~SupervisoryParametersIdx)
                        error(message('fininst:saccr:missingSupervisoryParameters', Key));
                    end
                    TradeSupervisoryParameters = obj.SupervisoryParameters(SupervisoryParametersIdx,:);
                    SupervisoryFactor = TradeSupervisoryParameters.SupervisoryFactor;
                    SupervisoryCorrelation = TradeSupervisoryParameters.SupervisoryCorrelation;

                    AddOnUncollateralized = ...
                        AdjustedNotionalSupervisoryDelta.*MaturityFactorUncollateralized.* ...
                        BasisVolMultiplier.*SupervisoryFactor;

                    AddOnCollateralized = ...
                        AdjustedNotionalSupervisoryDelta.*MaturityFactorCollateralized.* ...
                        BasisVolMultiplier.*SupervisoryFactor;

                    if strncmpi("Missing_NettingSet", CRTrades(tr).NettingSetID,18)
                        AddOnUncollateralized = ...
                            abs(AddOnUncollateralized);
                    end

                    CRTradeTable(TableRow:TableRow+NumComponents-1,:) = ...
                        table(repmat(CRTrades(tr).ID,NumComponents,1), ...
                        HedgingSet, repmat(MPOR(tr),NumComponents,1), ...
                        repmat(SupervisoryCorrelation,NumComponents,1), EquivalenceClass, ...
                        QualifierClass, AddOnCollateralized, AddOnUncollateralized,...
                        VariableNames=["TradeID","HedgingSet","MPOR",...
                        "SupervisoryCorrelation","EquivalenceClass",...
                        "QualifierClass","AddOnCollateralized", "AddOnUncollateralized"]);

                    TableRow = TableRow + NumComponents;
                end

                QualifierClasses = unique(CRTradeTable.QualifierClass);
                NumQualifierClasses = height(QualifierClasses);
                EquivalenceClasses = unique(CRTradeTable.EquivalenceClass);
                NumEquivalenceClasses = height(EquivalenceClasses);

                % Aggregate CR Add-Ons by Qualifier
                CRQualifierTable = table(Size=[NumQualifierClasses,8],...
                    VariableTypes=["string" "string" "double" "double" "double" "double" "double" "double"]);
                CRQualifierTable.Properties.VariableNames = ...
                    ["Qualifier","EquivalenceClass", ...
                    "CRAddonUncollateralized","SystematicUncollateralized",...
                    "IdiosyncraticUncollateralized","CRAddonCollateralized",...
                    "SystematicCollateralized", "IdiosyncraticCollateralized"];

                for ql=1:NumQualifierClasses
                    qlIdx = strcmpi(QualifierClasses(ql),CRTradeTable.QualifierClass);

                    EquivalenceClass = CRTradeTable.EquivalenceClass(qlIdx);
                    EquivalenceClass = EquivalenceClass(1);

                    CRAddonUncollateralizedQL = sum(CRTradeTable.AddOnUncollateralized(qlIdx));
                    CRAddonCollateralizedQL = sum(CRTradeTable.AddOnCollateralized(qlIdx));

                    if isempty(obj.Portfolios(k).NettingSets)
                        % No Netting Set
                        SystematicUncollateralizedQL = CRAddonUncollateralizedQL;
                        IdiosyncraticUncollateralizedQL = 0;

                        SystematicCollateralizedQL = CRAddonCollateralizedQL;
                        IdiosyncraticCollateralizedQL = 0;

                    else
                        SupervisoryCorrelation = CRTradeTable.SupervisoryCorrelation(qlIdx);
                        SupervisoryCorrelation = SupervisoryCorrelation(1);

                        SystematicUncollateralizedQL = ...
                            SupervisoryCorrelation.*CRAddonUncollateralizedQL;
                        IdiosyncraticUncollateralizedQL = ...
                            (1-SupervisoryCorrelation.^2).*CRAddonUncollateralizedQL.^2;

                        SystematicCollateralizedQL = ...
                            SupervisoryCorrelation.*CRAddonCollateralizedQL;
                        IdiosyncraticCollateralizedQL = ...
                            (1-SupervisoryCorrelation.^2).*CRAddonCollateralizedQL.^2;
                    end

                    CRQualifierTable(ql,:) = ...
                        table(QualifierClasses(ql), EquivalenceClass, ...
                        CRAddonUncollateralizedQL, SystematicUncollateralizedQL, IdiosyncraticUncollateralizedQL, ...
                        CRAddonCollateralizedQL, SystematicCollateralizedQL, IdiosyncraticCollateralizedQL, ...
                        VariableNames=["Qualifier","EquivalenceClass", ...
                        "CRAddonUncollateralized","SystematicUncollateralized",...
                        "IdiosyncraticUncollateralized","CRAddonCollateralized",...
                        "SystematicCollateralized", "IdiosyncraticCollateralized"]);
                end

                % Aggregate CR Add-Ons
                CRAddonUncollateralizedEq = nan(NumEquivalenceClasses,1);
                SystematicUncollateralizedEq = CRAddonUncollateralizedEq;
                IdiosyncraticUncollateralizedEq = CRAddonUncollateralizedEq;

                CRAddonCollateralizedEq = nan(NumEquivalenceClasses,1);
                SystematicCollateralizedEq = CRAddonCollateralizedEq;
                IdiosyncraticCollateralizedEq = CRAddonCollateralizedEq;

                for ec=1:NumEquivalenceClasses
                    ecIdx = strcmpi(EquivalenceClasses(ec),CRQualifierTable.EquivalenceClass);
                    SystematicUncollateralizedEq(ec) = sum(CRQualifierTable.SystematicUncollateralized(ecIdx));
                    IdiosyncraticUncollateralizedEq(ec) = sum(CRQualifierTable.IdiosyncraticUncollateralized(ecIdx));
                    CRAddonUncollateralizedEq(ec) = sqrt((SystematicUncollateralizedEq(ec).^2) + IdiosyncraticUncollateralizedEq(ec));

                    SystematicCollateralizedEq(ec) = sum(CRQualifierTable.SystematicCollateralized(ecIdx));
                    IdiosyncraticCollateralizedEq(ec) = sum(CRQualifierTable.IdiosyncraticCollateralized(ecIdx));
                    CRAddonCollateralizedEq(ec) = sqrt((SystematicCollateralizedEq(ec).^2) + IdiosyncraticCollateralizedEq(ec));
                end

                outAddOnCRResults.AddOnAssetClassesUncollateralized.AddOnCredit(k) = ...
                    sum(CRAddonUncollateralizedEq);

                if ~isempty(obj.Portfolios(k).NettingSets) && ...
                        ~isempty(obj.Portfolios(k).NettingSets.CollateralSets)
                    if ~all([obj.Portfolios(k).NettingSets.CollateralSets.STM]')
                        outAddOnCRResults.AddOnAssetClassesCollateralized.AddOnCredit(k) = ...
                            sum(CRAddonCollateralizedEq);
                    end
                end
            end
        end

        function outAddOnEQResults = addOnEQ(obj, inAddOnEQResults)
            outAddOnEQResults = inAddOnEQResults;
            outAddOnEQResults.AddOnAssetClassesUncollateralized.AddOnEquity = ...
                nan(outAddOnEQResults.NumPortfolios,1);
            outAddOnEQResults.AddOnAssetClassesCollateralized.AddOnEquity = ...
                nan(outAddOnEQResults.NumPortfolios,1);

            for k=1:obj.NumPortfolios
                EQTradeIdx = strncmpi("EQ",[obj.Portfolios(k).Trades.AssetClass]',2);
                EQTrades = obj.Portfolios(k).Trades(EQTradeIdx);
                NumEQTrades = length(EQTrades);
                MPOR = nan(NumEQTrades,1);

                NumTradeTableRows = 0;
                for tr=1:NumEQTrades
                    NumTradeTableRows = ...
                        NumTradeTableRows + length(EQTrades(tr).AdjustedNotional);
                end

                EQTradeTable = table(Size=[NumTradeTableRows,8],...
                    VariableTypes=...
                    ["string" "string" "double" "double" "string" "string" "double" "double"]);
                EQTradeTable.Properties.VariableNames = ...
                    ["TradeID","HedgingSet","MPOR","SupervisoryCorrelation",...
                    "EquivalenceClass","RiskDriverClass",...
                    "AddOnCollateralized", "AddOnUncollateralized"];

                TableRow = 1;
                for tr=1:NumEQTrades
                    NumComponents = length(EQTrades(tr).AdjustedNotional);
                    if ~isempty(obj.Portfolios(k).NettingSets)
                        NSIdx = strcmpi(EQTrades(tr).NettingSetID, obj.Portfolios(k).NettingSets.ID);
                        if any(NSIdx)
                            NettingSet = obj.Portfolios(k).NettingSets(NSIdx);
                            if ~isempty(NettingSet)
                                if ~isempty(NettingSet.CollateralSets)
                                    CSIdx = strcmpi(EQTrades(tr).CollateralSetID, [NettingSet.CollateralSets.ID]');
                                    if any(CSIdx)
                                        CollateralSet = NettingSet.CollateralSets(CSIdx);
                                        MPOR(tr) = CollateralSet.MPOR;
                                    end
                                end
                            end
                        end
                    end

                    RiskDriverClass = EQTrades(tr).Qualifier;
                    EquivalenceClass = EQTrades(tr).HedgingSet;
                    if ~isnan(MPOR(tr))
                        EquivalenceClass = EquivalenceClass + ...
                            repmat("_",NumComponents,1) + repmat(num2str(MPOR(tr)),NumComponents,1);
                    end

                    if strcmpi(obj.DomesticCurrency,"USD")
                        AdjustedNotional = EQTrades(tr).AdjustedNotionalUSD;
                    else
                        Currency = EQTrades(tr).AdjustedNotionalCurrency;
                        AdjustedNotional = EQTrades(tr).AdjustedNotional;
                        if ~strcmpi(obj.DomesticCurrency,Currency)
                            if isempty(obj.FXSpotRates)
                                error(message('fininst:saccr:missingFXSpotRates'));
                            end
                            FXSpotRate = exchangeRate(obj, Currency, obj.DomesticCurrency);
                            AdjustedNotional = AdjustedNotional.*FXSpotRate;
                        end
                    end

                    SupervisoryDelta = EQTrades(tr).SupervisoryDelta;

                    % Check if trade is a Sold Option outside netting and margin agreements (CRE 52.1 FAQ1)
                    if EQTrades(tr).SoldOption && strncmpi("Missing_NettingSet", EQTrades(tr).NettingSetID,18)
                        AdjustedNotionalSupervisoryDelta = zeros(NumComponents,1);
                    else
                        AdjustedNotionalSupervisoryDelta = AdjustedNotional.*SupervisoryDelta;
                    end

                    MaturityFactorUncollateralized = EQTrades(tr).MaturityFactorUncollateralized;
                    MaturityFactorCollateralized = EQTrades(tr).MaturityFactorCollateralized;

                    HedgingSet = EQTrades(tr).HedgingSet;
                    BasisVolMultiplier = nan(NumComponents,1);
                    for c=1:NumComponents
                        if contains(HedgingSet(c),"basis",IgnoreCase=true)
                            % BCBS279_162: Hedging Sets for Basis Transactions
                            BasisVolMultiplier(c) = 0.5;
                        elseif contains(HedgingSet(c),"vol",IgnoreCase=true)
                            % BCBS279_163: Hedging Sets for Volatility Transactions
                            BasisVolMultiplier(c) = 5;
                        else
                            BasisVolMultiplier(c) = 1;
                        end
                    end

                    Key = EQTrades(tr).AssetClass + "_";
                    SupervisoryParametersIdx = strcmpi(Key, obj.SupervisoryParameters.Key);
                    if all(~SupervisoryParametersIdx)
                        error(message('fininst:saccr:missingSupervisoryParameters', Key));
                    end
                    TradeSupervisoryParameters = obj.SupervisoryParameters(SupervisoryParametersIdx,:);
                    SupervisoryFactor = TradeSupervisoryParameters.SupervisoryFactor;
                    SupervisoryCorrelation = TradeSupervisoryParameters.SupervisoryCorrelation;

                    AddOnUncollateralized = ...
                        AdjustedNotionalSupervisoryDelta.*MaturityFactorUncollateralized.* ...
                        BasisVolMultiplier.*SupervisoryFactor;

                    AddOnCollateralized = ...
                        AdjustedNotionalSupervisoryDelta.*MaturityFactorCollateralized.* ...
                        BasisVolMultiplier.*SupervisoryFactor;

                    if strncmpi("Missing_NettingSet", EQTrades(tr).NettingSetID,18)
                        AddOnUncollateralized = ...
                            abs(AddOnUncollateralized);
                    end

                    EQTradeTable(TableRow:TableRow+NumComponents-1,:) = ...
                        table(repmat(EQTrades(tr).ID,NumComponents,1), ...
                        HedgingSet, repmat(MPOR(tr),NumComponents,1), ...
                        repmat(SupervisoryCorrelation,NumComponents,1), EquivalenceClass, ...
                        RiskDriverClass, AddOnCollateralized, AddOnUncollateralized,...
                        VariableNames=["TradeID","HedgingSet","MPOR",...
                        "SupervisoryCorrelation","EquivalenceClass",...
                        "RiskDriverClass","AddOnCollateralized", "AddOnUncollateralized"]);

                    TableRow = TableRow + NumComponents;
                end

                RiskDriverClasses = unique(EQTradeTable.RiskDriverClass);
                NumRiskDriverClasses = height(RiskDriverClasses);
                EquivalenceClasses = unique(EQTradeTable.EquivalenceClass);
                NumEquivalenceClasses = height(EquivalenceClasses);

                % Aggregate EQ Add-Ons by RiskDriver
                EQRiskDriverTable = table(Size=[NumRiskDriverClasses,8],...
                    VariableTypes=["string" "string" "double" "double" "double" "double" "double" "double"]);
                EQRiskDriverTable.Properties.VariableNames = ...
                    ["RiskDriver","EquivalenceClass", ...
                    "EQAddonUncollateralized","SystematicUncollateralized",...
                    "IdiosyncraticUncollateralized","EQAddonCollateralized",...
                    "SystematicCollateralized", "IdiosyncraticCollateralized"];

                for rd=1:NumRiskDriverClasses
                    rdIdx = strcmpi(RiskDriverClasses(rd),EQTradeTable.RiskDriverClass);

                    EquivalenceClass = EQTradeTable.EquivalenceClass(rdIdx);
                    EquivalenceClass = EquivalenceClass(1);

                    EQAddonUncollateralizedRD = sum(EQTradeTable.AddOnUncollateralized(rdIdx));
                    EQAddonCollateralizedRD = sum(EQTradeTable.AddOnCollateralized(rdIdx));

                    if isempty(obj.Portfolios(k).NettingSets)
                        % No Netting Set
                        SystematicUncollateralizedRD = EQAddonUncollateralizedRD;
                        IdiosyncraticUncollateralizedRD = 0;

                        SystematicCollateralizedRD = EQAddonCollateralizedRD;
                        IdiosyncraticCollateralizedRD = 0;

                    else
                        SupervisoryCorrelation = EQTradeTable.SupervisoryCorrelation(rdIdx);
                        SupervisoryCorrelation = SupervisoryCorrelation(1);

                        SystematicUncollateralizedRD = ...
                            SupervisoryCorrelation.*EQAddonUncollateralizedRD;
                        IdiosyncraticUncollateralizedRD = ...
                            (1-SupervisoryCorrelation.^2).*EQAddonUncollateralizedRD.^2;

                        SystematicCollateralizedRD = ...
                            SupervisoryCorrelation.*EQAddonCollateralizedRD;
                        IdiosyncraticCollateralizedRD = ...
                            (1-SupervisoryCorrelation.^2).*EQAddonCollateralizedRD.^2;
                    end

                    EQRiskDriverTable(rd,:) = ...
                        table(RiskDriverClasses(rd), EquivalenceClass, ...
                        EQAddonUncollateralizedRD, SystematicUncollateralizedRD, IdiosyncraticUncollateralizedRD, ...
                        EQAddonCollateralizedRD, SystematicCollateralizedRD, IdiosyncraticCollateralizedRD, ...
                        VariableNames=["RiskDriver","EquivalenceClass", ...
                        "EQAddonUncollateralized","SystematicUncollateralized",...
                        "IdiosyncraticUncollateralized","EQAddonCollateralized",...
                        "SystematicCollateralized", "IdiosyncraticCollateralized"]);
                end

                % Aggregate EQ Add-Ons
                EQAddonUncollateralizedEq = nan(NumEquivalenceClasses,1);
                SystematicUncollateralizedEq = EQAddonUncollateralizedEq;
                IdiosyncraticUncollateralizedEq = EQAddonUncollateralizedEq;

                EQAddonCollateralizedEq = nan(NumEquivalenceClasses,1);
                SystematicCollateralizedEq = EQAddonCollateralizedEq;
                IdiosyncraticCollateralizedEq = EQAddonCollateralizedEq;

                for ec=1:NumEquivalenceClasses
                    ecIdx = strcmpi(EquivalenceClasses(ec),EQRiskDriverTable.EquivalenceClass);
                    SystematicUncollateralizedEq(ec) = sum(EQRiskDriverTable.SystematicUncollateralized(ecIdx));
                    IdiosyncraticUncollateralizedEq(ec) = sum(EQRiskDriverTable.IdiosyncraticUncollateralized(ecIdx));
                    EQAddonUncollateralizedEq(ec) = sqrt((SystematicUncollateralizedEq(ec).^2) + IdiosyncraticUncollateralizedEq(ec));

                    SystematicCollateralizedEq(ec) = sum(EQRiskDriverTable.SystematicCollateralized(ecIdx));
                    IdiosyncraticCollateralizedEq(ec) = sum(EQRiskDriverTable.IdiosyncraticCollateralized(ecIdx));
                    EQAddonCollateralizedEq(ec) = sqrt((SystematicCollateralizedEq(ec).^2) + IdiosyncraticCollateralizedEq(ec));
                end

                outAddOnEQResults.AddOnAssetClassesUncollateralized.AddOnEquity(k) = ...
                    sum(EQAddonUncollateralizedEq);

                if ~isempty(obj.Portfolios(k).NettingSets) && ...
                        ~isempty(obj.Portfolios(k).NettingSets.CollateralSets)
                    if ~all([obj.Portfolios(k).NettingSets.CollateralSets.STM]')
                        outAddOnEQResults.AddOnAssetClassesCollateralized.AddOnEquity(k) = ...
                            sum(EQAddonCollateralizedEq);
                    end
                end
            end
        end

        function outAddOnCOResults = addOnCO(obj, inAddOnCOResults)
            outAddOnCOResults = inAddOnCOResults;
            outAddOnCOResults.AddOnAssetClassesUncollateralized.AddOnCommodity = ...
                nan(outAddOnCOResults.NumPortfolios,1);
            outAddOnCOResults.AddOnAssetClassesCollateralized.AddOnCommodity = ...
                nan(outAddOnCOResults.NumPortfolios,1);

            for k=1:obj.NumPortfolios
                COTradeIdx = strncmpi("CO",[obj.Portfolios(k).Trades.AssetClass]',2);
                COTrades = obj.Portfolios(k).Trades(COTradeIdx);
                NumCOTrades = length(COTrades);
                MPOR = nan(NumCOTrades,1);

                NumTradeTableRows = 0;
                for tr=1:NumCOTrades
                    NumTradeTableRows = ...
                        NumTradeTableRows + length(COTrades(tr).AdjustedNotional);
                end

                COTradeTable = table(Size=[NumTradeTableRows,8],...
                    VariableTypes=...
                    ["string" "string" "double" "double" "string" "string" "double" "double"]);
                COTradeTable.Properties.VariableNames = ...
                    ["TradeID","HedgingSet","MPOR","SupervisoryCorrelation",...
                    "EquivalenceClass","QualifierClass",...
                    "AddOnCollateralized", "AddOnUncollateralized"];

                TableRow = 1;
                for tr=1:NumCOTrades
                    NumComponents = length(COTrades(tr).AdjustedNotional);
                    if ~isempty(obj.Portfolios(k).NettingSets)
                        NSIdx = strcmpi(COTrades(tr).NettingSetID, obj.Portfolios(k).NettingSets.ID);
                        if any(NSIdx)
                            NettingSet = obj.Portfolios(k).NettingSets(NSIdx);
                            if ~isempty(NettingSet)
                                if ~isempty(NettingSet.CollateralSets)
                                    CSIdx = strcmpi(COTrades(tr).CollateralSetID, [NettingSet.CollateralSets.ID]');
                                    if any(CSIdx)
                                        CollateralSet = NettingSet.CollateralSets(CSIdx);
                                        MPOR(tr) = CollateralSet.MPOR;
                                    end
                                end
                            end
                        end
                    end

                    QualifierClass = COTrades(tr).Qualifier;
                    EquivalenceClass = COTrades(tr).HedgingSet;
                    if ~isnan(MPOR(tr))
                        EquivalenceClass = EquivalenceClass + ...
                            repmat("_",NumComponents,1) + repmat(num2str(MPOR(tr)),NumComponents,1);
                        QualifierClass = QualifierClass + ...
                            repmat("_",NumComponents,1) + repmat(num2str(MPOR(tr)),NumComponents,1);
                    end

                    if strcmpi(obj.DomesticCurrency,"USD")
                        AdjustedNotional = COTrades(tr).AdjustedNotionalUSD;
                    else
                        Currency = COTrades(tr).AdjustedNotionalCurrency;
                        AdjustedNotional = COTrades(tr).AdjustedNotional;
                        if ~strcmpi(obj.DomesticCurrency,Currency)
                            if isempty(obj.FXSpotRates)
                                error(message('fininst:saccr:missingFXSpotRates'));
                            end
                            FXSpotRate = exchangeRate(obj, Currency, obj.DomesticCurrency);
                            AdjustedNotional = AdjustedNotional.*FXSpotRate;
                        end
                    end

                    SupervisoryDelta = COTrades(tr).SupervisoryDelta;

                    % Check if trade is a Sold Option outside netting and margin agreements (CRE 52.1 FAQ1)
                    if COTrades(tr).SoldOption && strncmpi("Missing_NettingSet", COTrades(tr).NettingSetID,18)
                        AdjustedNotionalSupervisoryDelta = zeros(NumComponents,1);
                    else
                        AdjustedNotionalSupervisoryDelta = AdjustedNotional.*SupervisoryDelta;
                    end

                    MaturityFactorUncollateralized = COTrades(tr).MaturityFactorUncollateralized;
                    MaturityFactorCollateralized = COTrades(tr).MaturityFactorCollateralized;

                    HedgingSet = COTrades(tr).HedgingSet;
                    BasisVolMultiplier = nan(NumComponents,1);
                    for c=1:NumComponents
                        if contains(HedgingSet(c),"basis",IgnoreCase=true)
                            % BCBS279_162: Hedging Sets for Basis Transactions
                            BasisVolMultiplier(c) = 0.5;
                        elseif contains(HedgingSet(c),"vol",IgnoreCase=true)
                            % BCBS279_163: Hedging Sets for Volatility Transactions
                            BasisVolMultiplier(c) = 5;
                        else
                            BasisVolMultiplier(c) = 1;
                        end
                    end

                    Key = COTrades(tr).AssetClass + "_" + COTrades(tr).SubClass;
                    SupervisoryParametersIdx = strcmpi(Key, obj.SupervisoryParameters.Key);
                    if all(~SupervisoryParametersIdx)
                        error(message('fininst:saccr:missingSupervisoryParameters', Key));
                    end
                    TradeSupervisoryParameters = obj.SupervisoryParameters(SupervisoryParametersIdx,:);
                    SupervisoryFactor = TradeSupervisoryParameters.SupervisoryFactor;
                    SupervisoryCorrelation = TradeSupervisoryParameters.SupervisoryCorrelation;

                    AddOnUncollateralized = ...
                        AdjustedNotionalSupervisoryDelta.*MaturityFactorUncollateralized.* ...
                        BasisVolMultiplier.*SupervisoryFactor;

                    AddOnCollateralized = ...
                        AdjustedNotionalSupervisoryDelta.*MaturityFactorCollateralized.* ...
                        BasisVolMultiplier.*SupervisoryFactor;

                    if strncmpi("Missing_NettingSet", COTrades(tr).NettingSetID,18)
                        AddOnUncollateralized = ...
                            abs(AddOnUncollateralized);
                    end

                    COTradeTable(TableRow:TableRow+NumComponents-1,:) = ...
                        table(repmat(COTrades(tr).ID,NumComponents,1), ...
                        HedgingSet, repmat(MPOR(tr),NumComponents,1), ...
                        repmat(SupervisoryCorrelation,NumComponents,1), EquivalenceClass, ...
                        QualifierClass, AddOnCollateralized, AddOnUncollateralized,...
                        VariableNames=["TradeID","HedgingSet","MPOR",...
                        "SupervisoryCorrelation","EquivalenceClass",...
                        "QualifierClass","AddOnCollateralized", "AddOnUncollateralized"]);

                    TableRow = TableRow + NumComponents;
                end

                QualifierClasses = unique(COTradeTable.QualifierClass);
                NumQualifierClasses = height(QualifierClasses);
                EquivalenceClasses = unique(COTradeTable.EquivalenceClass);
                NumEquivalenceClasses = height(EquivalenceClasses);

                % Aggregate CO Add-Ons by Qualifier
                COQualifierTable = table(Size=[NumQualifierClasses,8],...
                    VariableTypes=["string" "string" "double" "double" "double" "double" "double" "double"]);
                COQualifierTable.Properties.VariableNames = ...
                    ["Qualifier","EquivalenceClass", ...
                    "COAddonUncollateralized","SystematicUncollateralized",...
                    "IdiosyncraticUncollateralized","COAddonCollateralized",...
                    "SystematicCollateralized", "IdiosyncraticCollateralized"];

                for ql=1:NumQualifierClasses
                    qlIdx = strcmpi(QualifierClasses(ql),COTradeTable.QualifierClass);

                    EquivalenceClass = COTradeTable.EquivalenceClass(qlIdx);
                    EquivalenceClass = EquivalenceClass(1);

                    COAddonUncollateralizedQL = sum(COTradeTable.AddOnUncollateralized(qlIdx));
                    COAddonCollateralizedQL = sum(COTradeTable.AddOnCollateralized(qlIdx));

                    if isempty(obj.Portfolios(k).NettingSets)
                        % No Netting Set
                        SystematicUncollateralizedQL = COAddonUncollateralizedQL;
                        IdiosyncraticUncollateralizedQL = 0;

                        SystematicCollateralizedQL = COAddonCollateralizedQL;
                        IdiosyncraticCollateralizedQL = 0;

                    else
                        SupervisoryCorrelation = COTradeTable.SupervisoryCorrelation(qlIdx);
                        SupervisoryCorrelation = SupervisoryCorrelation(1);

                        SystematicUncollateralizedQL = ...
                            SupervisoryCorrelation.*COAddonUncollateralizedQL;
                        IdiosyncraticUncollateralizedQL = ...
                            (1-SupervisoryCorrelation.^2).*COAddonUncollateralizedQL.^2;

                        SystematicCollateralizedQL = ...
                            SupervisoryCorrelation.*COAddonCollateralizedQL;
                        IdiosyncraticCollateralizedQL = ...
                            (1-SupervisoryCorrelation.^2).*COAddonCollateralizedQL.^2;
                    end

                    COQualifierTable(ql,:) = ...
                        table(QualifierClasses(ql), EquivalenceClass, ...
                        COAddonUncollateralizedQL, SystematicUncollateralizedQL, IdiosyncraticUncollateralizedQL, ...
                        COAddonCollateralizedQL, SystematicCollateralizedQL, IdiosyncraticCollateralizedQL, ...
                        VariableNames=["Qualifier","EquivalenceClass", ...
                        "COAddonUncollateralized","SystematicUncollateralized",...
                        "IdiosyncraticUncollateralized","COAddonCollateralized",...
                        "SystematicCollateralized", "IdiosyncraticCollateralized"]);
                end

                % Aggregate CO Add-Ons
                COAddonUncollateralizedEq = nan(NumEquivalenceClasses,1);
                SystematicUncollateralizedEq = COAddonUncollateralizedEq;
                IdiosyncraticUncollateralizedEq = COAddonUncollateralizedEq;

                COAddonCollateralizedEq = nan(NumEquivalenceClasses,1);
                SystematicCollateralizedEq = COAddonCollateralizedEq;
                IdiosyncraticCollateralizedEq = COAddonCollateralizedEq;

                for ec=1:NumEquivalenceClasses
                    ecIdx = strcmpi(EquivalenceClasses(ec),COQualifierTable.EquivalenceClass);
                    SystematicUncollateralizedEq(ec) = sum(COQualifierTable.SystematicUncollateralized(ecIdx));
                    IdiosyncraticUncollateralizedEq(ec) = sum(COQualifierTable.IdiosyncraticUncollateralized(ecIdx));
                    COAddonUncollateralizedEq(ec) = sqrt((SystematicUncollateralizedEq(ec).^2) + IdiosyncraticUncollateralizedEq(ec));

                    SystematicCollateralizedEq(ec) = sum(COQualifierTable.SystematicCollateralized(ecIdx));
                    IdiosyncraticCollateralizedEq(ec) = sum(COQualifierTable.IdiosyncraticCollateralized(ecIdx));
                    COAddonCollateralizedEq(ec) = sqrt((SystematicCollateralizedEq(ec).^2) + IdiosyncraticCollateralizedEq(ec));
                end

                outAddOnCOResults.AddOnAssetClassesUncollateralized.AddOnCommodity(k) = ...
                    sum(COAddonUncollateralizedEq);

                if ~isempty(obj.Portfolios(k).NettingSets) && ...
                        ~isempty(obj.Portfolios(k).NettingSets.CollateralSets)
                    if ~all([obj.Portfolios(k).NettingSets.CollateralSets.STM]')
                        outAddOnCOResults.AddOnAssetClassesCollateralized.AddOnCommodity(k) = ...
                            sum(COAddonCollateralizedEq);
                    end
                end
            end
        end

        function outFXSpotRate = exchangeRate(obj, Base, Quote)
            Idx  = strcmpi(obj.FXSpotRates.Base,Base) & strcmpi(obj.FXSpotRates.Quote,Quote);
            FlippedIdx = strcmpi(obj.FXSpotRates.Quote,Base) & strcmpi(obj.FXSpotRates.Base,Quote);
            if all(~Idx) && all(~FlippedIdx)
                error(message('fininst:saccr:missingCurrencyPairFXSpotRates', Base, Quote));
            elseif any(Idx)
                outFXSpotRate = obj.FXSpotRates(Idx,:).SpotRate;
            else
                outFXSpotRate = 1./obj.FXSpotRates(FlippedIdx,:).SpotRate;
            end
            outFXSpotRate = outFXSpotRate(1);
        end

        function [VminusCUncoll, VminusCColl, RCUncollateralized, RCCollateralized] = netValueRC(obj, k)
            if nargout > 2
                RCRequested = true;
            else
                RCRequested = false;
            end
            NumTrades = length(obj.Portfolios(k).Trades);
            if strcmpi(obj.DomesticCurrency,"USD")
                MTMs = [obj.Portfolios(k).Trades.PVUSD]';
            else
                PVCurrencies = [obj.Portfolios(k).Trades.PVCurrency]';
                MTMs = [obj.Portfolios(k).Trades.PV]';
                if isempty(obj.FXSpotRates) && any(~(strcmpi(obj.DomesticCurrency, PVCurrencies)))
                    error(message('fininst:saccr:missingFXSpotRates'));
                end
                for tr=1:NumTrades
                    if ~strcmpi(obj.DomesticCurrency, PVCurrencies(tr))
                        FXSpotRate = exchangeRate(obj, PVCurrencies(tr), obj.DomesticCurrency);
                        MTMs(tr) = MTMs(tr).*FXSpotRate;
                    end
                end
            end

            NettingCollateralComboCellTable = table(...
                {obj.Portfolios(k).Trades.NettingSetID}',...
                {obj.Portfolios(k).Trades.CollateralSetID}',...
                VariableNames=["NettingSetID","CollateralSetID"]);

            NettingCollateralComboTable = table(Size=[NumTrades,2],...
                VariableTypes=["string","string"],...
                VariableNames=["NettingSetID","CollateralSetID"]);

            for tr=1:NumTrades
                if isempty(NettingCollateralComboCellTable.NettingSetID{tr})
                    NettingCollateralComboTable.NettingSetID(tr)="";
                else
                    NettingCollateralComboTable.NettingSetID(tr)=...
                        NettingCollateralComboCellTable.NettingSetID{tr};
                end

                if isempty(NettingCollateralComboCellTable.CollateralSetID{tr})
                    NettingCollateralComboTable.CollateralSetID(tr)="";
                else
                    NettingCollateralComboTable.CollateralSetID(tr)=...
                        NettingCollateralComboCellTable.CollateralSetID{tr};
                end
            end

            EquivalenceClasses = unique(NettingCollateralComboTable);
            NumEquivalenceClasses = height(EquivalenceClasses);

            Value = nan(NumEquivalenceClasses,1);
            C_Uncoll = zeros(NumEquivalenceClasses,1);
            C_Coll = C_Uncoll;
            if RCRequested
                TA = C_Uncoll;
                MTA = C_Uncoll;
                NICA = C_Uncoll;
            end

            for ec=1:NumEquivalenceClasses
                NettingSetID = EquivalenceClasses.NettingSetID(ec);
                CollateralSetID = EquivalenceClasses.CollateralSetID(ec);

                EqClassIdx = strcmpi(NettingSetID, ...
                    NettingCollateralComboTable.NettingSetID) & ...
                    strcmpi(CollateralSetID, ...
                    NettingCollateralComboTable.CollateralSetID);

                Value(ec) = sum(MTMs(EqClassIdx));

                if ~isempty(obj.Portfolios(k).NettingSets)
                    if ~isempty(obj.Portfolios(k).NettingSets.CollateralSets) & ...
                            any(strcmpi(CollateralSetID, [obj.Portfolios(k).NettingSets.CollateralSets.ID]'))
                        CSIdx = strcmpi(CollateralSetID, [obj.Portfolios(k).NettingSets.CollateralSets.ID]');
                        if obj.Portfolios(k).NettingSets.CollateralSets(CSIdx).STM
                            Value(ec) = 0;
                        end
                    end
                end

                if ~isempty(obj.Portfolios(k).NettingSets) & ...
                        any(strcmpi(NettingSetID, [obj.Portfolios(k).NettingSets.ID]'))

                    NettingSetIdx = strcmpi(NettingSetID, [obj.Portfolios(k).NettingSets.ID]');
                    NettingSet = obj.Portfolios(k).NettingSets(NettingSetIdx);
                    CollateralSets = NettingSet.CollateralSets;
                    if ~isempty(CollateralSets)
                        NumCollateralSets = length(CollateralSets);
                        for ColSetIdx = 1:NumCollateralSets
                            CollateralPositions =...
                                CollateralSets(ColSetIdx).CollateralPositions;

                            if RCRequested
                                TA(ec) = CollateralSets(ColSetIdx).Threshold;
                                if ~strcmpi(obj.DomesticCurrency,CollateralSets(ColSetIdx).ThresholdCurrency)
                                    if isempty(obj.FXSpotRates)
                                        error(message('fininst:saccr:missingFXSpotRates'));
                                    end
                                    Currency = CollateralSets(ColSetIdx).ThresholdCurrency;
                                    FXSpotRate = exchangeRate(obj, Currency, obj.DomesticCurrency);
                                    TA(ec) = TA(ec).*FXSpotRate;
                                end

                                MTA(ec) = CollateralSets(ColSetIdx).MTA;
                                if ~strcmpi(obj.DomesticCurrency,CollateralSets(ColSetIdx).MTACurrency)
                                    Currency = CollateralSets(ColSetIdx).MTACurrency;
                                    if isempty(obj.FXSpotRates)
                                        error(message('fininst:saccr:missingFXSpotRates'));
                                    end
                                    FXSpotRate = exchangeRate(obj, Currency, obj.DomesticCurrency);
                                    MTA(ec) = MTA(ec).*FXSpotRate;
                                end
                            end

                            if ~isempty(CollateralPositions)
                                NumCollateralPositions = length(CollateralPositions);
                                PostHaircutAmountUncoll = zeros(NumCollateralPositions,1);
                                PostHaircutAmountColl = PostHaircutAmountUncoll;
                                for ColPosIdx = 1:NumCollateralPositions
                                    % Pre Haircut Market Value
                                    if strcmpi(obj.DomesticCurrency,"USD")
                                        PreHaircutMarketValue = ...
                                            CollateralPositions(ColPosIdx).NotionalUSD;
                                    else
                                        PreHaircutMarketValue = ...
                                            CollateralPositions(ColPosIdx).Notional;
                                        Currency = CollateralPositions(ColPosIdx).Currency;
                                        if ~strcmpi(obj.DomesticCurrency,Currency)
                                            if isempty(obj.FXSpotRates)
                                                error(message('fininst:saccr:missingFXSpotRates'));
                                            end
                                            FXSpotRate = exchangeRate(obj, Currency, obj.DomesticCurrency);
                                            PreHaircutMarketValue = PreHaircutMarketValue.*FXSpotRate;
                                        end
                                    end
                                    % Scaling Factors
                                    if strncmpi(obj.Regulation,"CRR",3) || strncmpi(obj.Regulation,"UK",2)
                                        ScalingFactorUncoll = ...
                                            sqrt(obj.NumBusinessDaysYear./obj.MaturityBusinessDaysFloor);
                                    elseif strncmpi(obj.Regulation,"Basel",5)
                                        ScalingFactorUncoll = ...
                                            sqrt(min(obj.Portfolios(k).MaxMaturityTime.*obj.NumBusinessDaysYear, ...
                                            obj.NumBusinessDaysYear)./obj.MaturityBusinessDaysFloor);
                                    else
                                        ScalingFactorUncoll = ...
                                            sqrt(CollateralSets(ColSetIdx).MPOR./obj.MaturityBusinessDaysFloor);
                                    end

                                    if strcmpi("ONEWAYOUT",CollateralSets(ColSetIdx).Direction)
                                        if strncmpi(obj.Regulation,"CRR",3) || strncmpi(obj.Regulation,"UK",2)
                                            ScalingFactorColl = ...
                                                sqrt(obj.NumBusinessDaysYear./obj.MaturityBusinessDaysFloor);
                                        elseif strncmpi(obj.Regulation,"Basel",5)
                                            ScalingFactorColl = ...
                                                sqrt(min(obj.Portfolios(k).MaxMaturityTime.*obj.NumBusinessDaysYear, ...
                                                obj.NumBusinessDaysYear)./obj.MaturityBusinessDaysFloor);
                                        else
                                            ScalingFactorColl = ...
                                                sqrt(CollateralSets(ColSetIdx).MPOR./obj.MaturityBusinessDaysFloor);
                                        end
                                    else
                                        ScalingFactorColl = ...
                                            sqrt(CollateralSets(ColSetIdx).MPOR./obj.MaturityBusinessDaysFloor);
                                    end

                                    TotalHaircut = CollateralPositions(ColPosIdx).FXHaircut + ...
                                        CollateralPositions(ColPosIdx).BondHaircut + ...
                                        CollateralPositions(ColPosIdx).EquityHaircut + ...
                                        CollateralPositions(ColPosIdx).GoldHaircut;

                                    ScaledHaircutUncoll = min([TotalHaircut.*ScalingFactorUncoll,1]);
                                    ScaledHaircutColl = min([TotalHaircut.*ScalingFactorColl,1]);

                                    % Compute Post Haircut Amounts
                                    if CollateralPositions(ColPosIdx).Segregated && ...
                                            PreHaircutMarketValue < 0
                                        PostHaircutAmountUncoll(ColPosIdx) = 0;
                                        PostHaircutAmountColl(ColPosIdx) = 0;
                                    elseif PreHaircutMarketValue >= 0
                                        PostHaircutAmountUncoll(ColPosIdx) = ...
                                            PreHaircutMarketValue.*(1-ScaledHaircutUncoll);
                                        PostHaircutAmountColl(ColPosIdx) = ...
                                            PreHaircutMarketValue.*(1-ScaledHaircutColl);
                                    elseif PreHaircutMarketValue < 0
                                        PostHaircutAmountUncoll(ColPosIdx) = ...
                                            PreHaircutMarketValue.*(1+ScaledHaircutUncoll);
                                        PostHaircutAmountColl(ColPosIdx) = ...
                                            PreHaircutMarketValue.*(1+ScaledHaircutColl);
                                    end
                                end

                                % Compute IM_Uncoll
                                ColPosIMIdx = strcmpi([CollateralPositions.MarginType]',"IM") | ...
                                    strcmpi([CollateralPositions.MarginType]',"IA");
                                IM_Uncoll = sum(PostHaircutAmountUncoll(ColPosIMIdx));
                                C_Uncoll(ec) = C_Uncoll(ec) + IM_Uncoll;

                                % Compute IM_Coll
                                IM_Coll = sum(PostHaircutAmountColl(ColPosIMIdx));
                                C_Coll(ec) = C_Coll(ec) + IM_Coll;
                                if RCRequested
                                    NICA(ec) = IM_Coll;
                                end

                                % Compute VM_Uncoll (for ONEWAYOUT)
                                ColPosVMIdx = strcmpi([CollateralPositions.MarginType]',"VM");
                                VM_Uncoll = sum(PostHaircutAmountUncoll(ColPosVMIdx));
                                if strcmpi("ONEWAYOUT",CollateralSets(ColSetIdx).Direction) & VM_Uncoll<0
                                    C_Uncoll(ec) = C_Uncoll(ec) + VM_Uncoll;
                                end

                                % Compute VM_Coll
                                VM_Coll = sum(PostHaircutAmountColl(ColPosVMIdx));
                                C_Coll(ec) = C_Coll(ec) + VM_Coll;
                            end
                        end
                    end
                end
            end

            NettingSetIDs = unique([obj.Portfolios(k).Trades.NettingSetID]');
            NumNettingSets = length(NettingSetIDs);
            RCUncollateralized = nan(NumNettingSets,1);
            RCCollateralized = RCUncollateralized;
            VminusCUncoll = RCUncollateralized;
            VminusCColl = RCCollateralized;
            TAplusMTAminusNICA = RCCollateralized;

            for ns=1:NumNettingSets
                if ~isempty(obj.Portfolios(k).NettingSets)
                    NettingSetIdx = strcmpi(obj.Portfolios(k).NettingSets(ns).ID, ...
                        EquivalenceClasses.NettingSetID);
                    % Compute V minus C
                    VminusCUncoll(ns) = sum(Value(NettingSetIdx)) - sum(C_Uncoll(NettingSetIdx));
                    VminusCColl(ns) = sum(Value(NettingSetIdx)) - sum(C_Coll(NettingSetIdx));
                    % Compute TA plus MTA minus NICA
                    if RCRequested
                        TAplusMTAminusNICA(ns) = ...
                            sum(TA(NettingSetIdx)) + sum(MTA(NettingSetIdx)) - sum(NICA(NettingSetIdx));
                    end
                else
                    VminusCUncoll(ns) = Value(ns) - C_Uncoll(ns);
                    VminusCColl(ns) = Value(ns) - C_Coll(ns);
                end
            end
            if RCRequested
                RCUncollateralized = max(VminusCUncoll,0);
                RCCollateralized = max(max(VminusCColl, TAplusMTAminusNICA), 0);
            end
        end
    end
end