Foundations of a Risk Management System FE5226 Class Project Fabio Cannizzo 2017-9-1 version 1.1 Abstract This source code constitutes the foundation of a risk management system. It can load trades from a database, compute the price of trades and their sensitivities with respect to risk factors and construct market objects on demand. A real risk management system entails of many more features, e.g. so- phisticate market objects and pricing models; efficient algorithms to avoid unnecessary recomputations when calculating Greeks; support for a large number of trade types; extensive set of tools to orchestrate recomputa- tions and post-process their results (e.g. P&L explain, VaR, PFE, XVA); tools to manage life-cycle of trades; connections to external databases and systems of various nature; client-server pricing APIs; connections to cash flow management systems and payment systems; support for parallel computations (thread safety). All these features can be implemented as modifications or extensions of the code provided. You are supposed to carry out various improvements and extensions as specified below. 1 License The license for the source code is in the main directory. Please read the license file. 2 Source code organization All source code files are in the subdirectory src. Source files with names matching the patterns Demo*.cpp or Test*.cpp contain the main routine for separate programs. The other cpp files are library components used by the various programs. The src subdirectory also contains a Makefile for gcc compilation. If you use Visual Studio, you need to: create a new solution; in the solu- tion add one console project for each cpp file with name Demo*.cpp or Test*.cpp; in each project also add all other cpp files (except the ones 1 with name Demo*.cpp or Test*.cpp), so that in each project you have one and only one cpp file containing the function main. A number of txt files are given. portfolio 0.txt and risk factors 0.txt work with the program in its current state. portfolio i.txt and risk factors i.txt work after completion of the first i tasks. If a file with appropriate nu- meration is missing, you can use the last one available (e.g. in task 11, you can use risk factors 5.txt) To run the program type from the command line: DemoRisk -p portfolio.txt -f risk factors.txt To run the program directly from inside Visual Studio, you can specify the working directory where your txt files are located and the command line arguments (portfolio.txt risk factors.txt) in the Debugging section of the project configuration. 3 What to do You have to complete sequentially all tasks mentioned in section 5. After completing each task you should test correctness of your work by succes- fully running the command DemoRisk portfolio i.txt risk factors i.txt and comparing the output of your program with the output given in out- put i.txt. To compare files you can use the free program WinMerge, or sdiff on Linux. Your goal is to generate an identical output. To compare if files are identical, on Windows you can use WinMerge. After completing all tasks you need to zip and submit all files with extension *.h and *.cpp. Do not submit any other file (e.g. txt files, project files, object files, executable files). 4 Marking criteria Marking is going to be based on the following criterias. • Correctness (60%): – I will run your DemoRisk with my trade portfolio and market data files and compare your output with mine. This may in- clude expired trades (i.e. trades where the last payment date is before the pricing date, which should generate an error), missing market data (e.g. a missing FX spot), missing fixings. – I will run my TestDate program against your Date class. – I will run your DemoRisk with my trade portfolio and market data files and compare your output with mine, using a base currency different from USD. – I will run your DemoRisk with my trade portfolio and market data files and compare your output with mine. – I will check the total computation time of your executable against mine for a large portfolio with multiple currencies. Any major performance difference (> 20% slower) implies a sub-optimal 2 design (inappropriate choice of data structures and/or algo- rithms). • Style (40%): – correct use of indentation (configure your editor to convert tab to spaces and to use tabs of 4 characters) – proper choices of variable and function names – appropriate use of source code comments – avoid code duplication (do not copy and paste, introduce new functions as appropriate) – code robustness (e.g. assert function arguments are valid) – code conciseness (do not do in 10 lines what you could do in one line) – do not reinvent the weel (use library functions when available) – minimal diff: only modify the lines of code strictly necessary, do not insert unnecessary whitespaces (you can use WinMerge to view the difference between your files and the original ones, or use git, which is excellent for checking differences). I will use git to check differences. 5 Tasks 5.1 Improve the Date class The most common operations performed with the Date class when pricing are comparison (e.g. <, ==, >) and computation of distance between two dates. The current internal representation of the date class is not optimal for these operations. Refactor the Date class by modifying the current internal representa- tion, which entails of the 4 data members day, month, year and is leap, to a single data member of type unsigned with name serial, representing the number of days elapsed since 1-Jan-1900. This allows to speed up the operations mentioned above and reduce memory footprint. Change the serialization format to be based directly on serial, so that no extra work is necessary when saving or loading from a file. When the Date class is constructed from the arguments day, month and year, you need to generate the equivalent serial number. When con- verting to a string in calendar format for display to the screen, you need to convert serial on-the-fly into day, month and year. 5.2 Write a test for the Date class Complete the program in TestDate.cpp, so that it tests the correctness of the Date class. It should perform the following tests: 1. Construction of an invalid date should generate an error: generate intentionally 1000 random invalid dates (e.g. 31-Apr-2010) and ver- ify that the Date class constructor throws an error (use try...catch). 3 2. Verify that converting a date in calendar format (day, month, year) to serial format and then converting back to calendar format yields the original date. Repeat for all dates in the valid range (1-Jan-1900, 31-Dec-2199). 3. Verify that the serial number generated for 2 contiguous dates are contiguous. For instance 31-Jan-2012 and 1-Feb-2012 are contiguous dates, hence the serial numbers they generate should only differ by 1. Repeat for all pairs of contiguous dates in the valid range (1-Jan- 1900, 31-Dec-2199). At the begin of the test, randomize the random number generator. If the test fails, the program should throw an exception, if it succeeds it should just print the message ”SUCCESS”. Print out the random seed you used for the random number generator, so that, if the test fails, a programmer can re-use the same seed and reproduce the error, which is essential to be able to debug it. 5.3 Perfect streaming for type double Currently when a floating point number in double precision is saved to a file it is transformed from IEEE-754 format to a decimal representation with a finite number of decimals in scientific notation. The conversion from IEEE-754 to decimal (when saving) and then back to IEEE-754 (when reading) involves rounding and can cause accuracy loss. In other words, if we start from a value of type double, we save it as a string in decimal format, then we read back the string and convert it to double, we may not get back exactly the same value we started with. To overcome the problem, change the streaming representation of a double to the integer interpretation of its binary representation in hex- adecimal format with letters in lowercase. If x is a variable of type double, it binary representation can be re- interpreted as a 64 bits integer, which can be represented exactly. To do that, you can either get the memory address to the variable of type double and reinterpret cast it to a 64-bit int pointer or use a union. To make the textual representation more compact, we use base 16 (hexadecimal). For example, the double number -0.15625 should be saved to file as the se- quence of 16 characters bfc4000000000000 (see example in DemoHex.cpp, which use a union). When reading, you need to read the integer number saved in hexadecimal format and re-interpret it as a double. You need to modify the implementation of the operator<< for double and implement the overload of the operator>> (you may draw inspiration from the ones implemented for the Date class). 5.4 Improve the CurveDiscount class The current CurveDiscount curve assumes that the interest rate curve is constant. Modify it so that it takes a set of rates related to different tenors and modify it so that when querying for a discount factor, the value returned is correctly interpolated. 4 For example, given the data points IR.1W.USD, IR.2W.USD, IR.1M.USD, IR.2M.USD, IR.3M.USD, IR.6M.USD, IR.9M.USD, IR.1Y.USD, IR.18M.USD, IR.2Y.USD, ..., you need to resolve each of them to an actual date with respect to the anchor date, construct a stepwise constant interpolation scheme, modify the function which returns discount factor. For sake of clarity, D, W, M and Y mean respectively days, weeks, months and years. Although this is not consistent with market conventions, for simplicity assumes that 1M=30 days and 1Y=365 days, and ignore the distinction between weekdays and weekends when computing tenor dates. Since you do not know in advance which points are available in the market data server, if you want all the available points for the currency EUR, you need to modify the market data server to be able to return the array of risk factors with pattern IR.*.EUR. Use the
STL library to search for a string pattern and implement the method Market- DataServer::matching, whose header is already defined in MarketDataServer.h. Note that ”IR.*.EUR” is not the correct regular expression string to be used here, you need to read about regular expressions and figure out what is the most appropriate string to use, i.e. it should match ”just” what needed. For instance, ”IR.*EUR” would match ”IR.1W.EUR”, but also ”IRwhateverEUR”, which is undesirable. The interpolation scheme requires computation of the local rates. If ri and ri+1 are the absolute rates for tenor Ti and Ti+1, as returned from the market data server, the local rate ri,i+1 is the one that solves the following equation: e−riTi−ri,i+1(Ti+1−Ti) = e−ri+1Ti+1 Then the discount factor for any date t ∈ [Ti, Ti+1] is df(t) = e−riTi−ri,i+1(t−Ti) In an efficient implementation as much calculation as possible should be done in the constructor, so that the function df , which will be invoked millions of times, is as fast as possible. For instance, the values of the products riTi could be precomputed for all i and cached in a data mem- ber of the class. In the df function use binary search (see lower bound) to determine the interval i such that Ti ≤ t < Ti+1. If the function df is called with a date beyond the last tenor or before the anchor date (today), it should generate an error. 5.4.1 PV01 with tenors The PV01 risk function needs to be modified into 2 different risk func- tions: PV01Parallel that computes risk with respect to parallel shift of the yield curve (all risk factor move simultaneously); and PV01Bucketed that computes risk with respect to individual yield curves (the yield curve for each tenor Ti change, with all the rest remaining constant). In both cases use central differences, with the same bump size as currently defined 5 in Demo.cpp. The 2 new functions must have the same arguments and return type as the existing one. 5.5 Recover from pricing failures The function IPricer::price fails and throws an exception when an error occurs. For instance, if the settlement date of a Payment is in the past, pricing of the entire portfolio fails. We would like instead that only prob- lematic trades fail to price while all remaining trades price succesfully. Modify the typedef portfolio values t to vector>. In the compute prices function use try{}catch{} to catch exceptions. If there is no error set the double to the price and the string to an empty string. If there is an error set the double to NaN (see header < limits >) and the string to the error message embedded in the exception. When computing thet total for the book (function portfolio total), you should return a pair containing the total for trades which price succesfully and an array of pairs with the index of the trades which price to fail and the associated error message. pair < double, vector < pair < size t, string >>> To identify trades with errors, check if the value is a NaN, not if the error message string is empty, because a trade could potentially fail and still have an empty error message. When computing finite differences, if either the up or down bump are NaN, then set the result to NaN and set the error message to the correspnding error message (if both up and down states have error, you can ignore one of the two error messages). Modify DemoRisk to display both the total for the book and the num- ber of trades which fail to price. Compare the output of your program with the one given. 5.6 Add a new market object CurveFXSpot The new market object should implement the interface ICurveFXSpot. Note that the risk factors contained in the market data server only include spot currencies in the format CCYUSD, i.e. the prices of CCY in USD. The class must support direct, inverse and cross currency pairs (e.g. EURUSD, USDJPY, EURGBP). It is up to you to make the correct queries to the market data server and construct the CurveFXSpot appro- priately. You need to modity the Market class as appropriate. Note that according with market conventions the spot price observed at time T is the forward price for an exchange of money at T + 2. Ig- nore this difference and assume that the spot price observed at time T is defined as the exchange rate for an instantaneous exchange of money at time T . Thanks to this simplification we can simply use the spot directly to convert the present value of a trade price from one currency to another. A real risk management system however, should take this difference into 6 account. Modify the PricerPayment class to use the new class CurveFXSpot. 5.7 Add pricer configuration Modify DemoRisk to read another optional argument form the command line, the base currency (argument name -b CCY ). If not explicitly passed, this argument takes the value USD. Pass this to the run function. Mod- ify the function ITrade::Pricer adding an argument configuration of type string. In a real risk management system the configuration argument could be very complex and allow for instance to select a pricing model (e.g. Black Scholes or Heston) or a numerical framework (e.g. PDE or Monte Carlo). In this project the configuration string is simply the base currency in which all risk is to be computed. At the moment, by default everything is computed in USD. After your changes, pricer(”EUR”) should return a pricer that computes the price in EUR. You need to modify all pricers accordingly. It may be convenient adding a base class Pricer which all pricers inherit from and take care of this conversion. 5.8 Add a new market object CurveFXForward The new market object should implement the interface ICurveFXForward. The forward price observed at time T0 of currency ccy1 expressed in currency ccy2 for delivery at time T can be computed as F (T0, T ) = E[S(T )|FT0 ] = S(T0) B1(T0, T ) B2(T0, T ) where T0 is the pricing date, S(T0) is the current spot price of currency ccy1 expressed in currency ccy2, Bi(T0, T ) is the discount factor in cur- rency ccyi for time T observed at time T0. This class should use the CurveFXSpot class to get S(T0) and the CurveDiscount classes to get the discount factors. Modify the Market class as appropriate. 5.9 Add fixing data server To compute the price of some trades we need information in the past as well as in the future. For example, if today is the 15th of November and we want to compute the price of an Asian option that pays the average of the daily closing price in the month of November, some of these closing prices are in the past and need to be accounted for. 7 Past observations are called fixings. Introduce a FixingDataServer class, which implements the following methods: // return the fixing if available, otherwise trigger an error double get(const string& name, const Date& t) const; // return the fixing if available, NaN otherwise, and set the flag if found pair < double, bool > lookup(const string& name, const Date& t) const; Populate the fixing data server with the data provided in the file fix- ings.txt. Add an extra command line argument (-x ) to DemoRisk.cpp and use it to communicate to the program the name of the data file containing the fixings. Create a FixingDataServer object in DemoRisk.cpp and pass it as an extra argument to the IPricer::price function. Only the pricers of trades requiring fixings will do something with it, other pricers will ignore it (note that TradePayment does not have any fixing, hence it will not use it). In particular: fixings occurring in the past should be obtained from this argument and if not available an exception should be thrown; fixings occurring exactly on the pricing date should be taken from this argument if available, resolved through the Market otherwise; fixings occurring in the future should be resolved through the Market (e.g. forward prices). Do not change the format of dates to serial and of double to hexadec- imal in the data file. 5.10 Add FX Spot Greek function Add another function for Greeks calculation, named fx delta, which com- putes the risk of the book with respect to each of the FX spot rates quoted against USD in the market data server. It should use central difference with a relative bump of 0.1%. It should have the same return value and argument types as the PV01 functions. Add calculation of FXDelta at the end of DemoRisk.cpp and display the cumulative book value for each fx spot rate. 5.11 Add new trade type TradeFXForward Given two dates T1 and T2, at time T2 it pays the following amount of ccy2 payoff = N [S(T1)−K] where S(T1) is the spot price of currency ccy1 expressed in currency ccy2 observed at the fixing date T1, K is a predefined strike price, N is the notional amount and T2 is the settlement date (it must be T1 < T2 for the trade to be valid). Assign to the trade ID=3 (arbitrarily chosen) and the following seri- alized representation: ID;NOTIONAL;CCY1;CCY2;STRIKE;FIXINGDATE;SETTLEDATE; 8 Example: 3;EUR;USD;notional in hex;strike in hex;date in serial;date in serial; The currency pair ccy1ccy2 can be any direct, inverse or cross pair. Implement the corresponding pricer object. The price in ccy2 can be computed as: price = B2(T0, T2)[F (T0, T1)−K] where B2(T0, T2) is the discount factor in currency 2 for settlement date T2 and F (T0, T1) is the currency forward price for date T1. Do not forget to convert this price into USD (or any other specified base currency). 5.11.1 Historical fixings Note that when the pricing date T0 is between T1 and T2, you need to know the value of ST1 , which is in the past, i.e. you need to know its fixed value. Note that there can be various situations with respect to the pricing date T0: • T0 < T1: both fixing and settlement are in the future • T0 = T1 and fixing for T1 not available: there is still full delta risk and PV01 risk • T1 ≤ T0 < T2 and fixing for T1 available: there is no more delta risk with respect to the underlying, but we still have PV01 risk and there may be delta risk for conversion to base currency • T0 = T2: no delta with respect to the underlying or PV01 risk, we assume the payment has not been settled yet, hence we can still have a non-zero price, there may be still delta risk with respect to conversion to base currency. • T0 > T2: trade is expired, an error should be generated 9