Foundations of a Risk Management System
FE5226 Group Project
Fabio Cannizzo
version: 2021-08-28
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. sophisticate 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 recomputations 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 and build instructions
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 target platform is x86 64 (64 bits). The C++ language standard used is: C++17. In Visual Studio you must:
? Select platform ”x64” (the default is ”x86”, i.e. 32 bits).
? Go to the property page for the project (right click on the project from solution explorer), select ”all platform”
and ”all configurations”, then navigate the configuration tree to C++/AllOptions and set:
– C++ language standard: C++17
– Disable language extensions: yes
– Treat all errors as warnings: yes
– Warning level: W3
The src subdirectory also contains a Makefile for gcc compilation. If you use Visual Studio, you need to: create a new
solution;
in the solution 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 with name Demo.cpp or Test.cpp), so that in each project you have one
and only one cpp file containing the function main. You can work in ”Debug” mode, which allows you to execute code
step by step and use breakpoints, but when you are finished with development and want to test the performance of your
final program you should use ”Release” mode. The language standard can be selected from the project configuration
(right click on the project in the Solution Explorer pane, then select C++/Lanugage/C++LanguageStandards). Note
that this must be repeated for all modes (debug and release).
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 enumeration 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.
1
3 What to do
You have to complete sequentially all tasks mentioned in section 6. After completing each task you should test
correctness of your work by succesfully running the command
DemoRisk portfolio i.txt risk factors i.txt
and comparing the output of your program with the output given in output i.txt. To compare files you can use the free
Windows program WinMerge, or sdiff on Linux. Your goal is to generate an output file identical to the one provided.
4 Marking criteria
Marking is going to be based on the following criteria.
4.1 Correctly follow submission instructions (-2 marks)
By simply following correctly submission procedures described in section 5, you get full marks. Otherwise, up to 2
marks are subtracted from your total mark. While this seems trivial, every year some student group fails to do this, so
please be careful.
4.2 Coding style (3 marks)
Follow good practice
? correct use of indentation (configure your editor to convert tab to spaces and to use tabs of 4 characters)
? if called with no arguments or with incorrect arguments, the program must produce an explanation of the correct
command line syntax required to invoke the program
? 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 wheel (use library functions when available)
? minimal changes to original files: only add or 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 WinMerge to check differences.
4.3 Date class correctness (2 marks)
I will look at you date class implementation and the test, focusing on correctness. I will run your date class against
my test.
4.4 Basic correctness (5 marks)
If your code compiles I will run your DemoRisk with the given trade portfolio and market data and compare your
output with mine using WinMerge. If there is a no difference, you get full marks. Note that if your code does not
compile, you will get zero marks. The command line help message should also be correct (include all accepted command
line arguments).
4.5 Performance (4 marks)
I create a very large test portfolio (millions of trades). As simple way to do this is to simply copy the given test set
portfolio many times in a bigger file. If your DemoRisk runs in a time comparable or better than my own implementation,
you get full marks. If section 4.4 fails, here you get zero marks. Common mistakes that can make your program slow are:
poor choice of data structures and redundant calculations (in particular, for market objects, do as much calculations
as possible when you construct the objects, and reuse this information when pricers query the market data objects).
4.6 Advanced correctness (4 marks)
I create an extended set of test test files, with more currencies and more special cases than what covered by the sample
test files. This may include 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 and other corner cases. If
your DemoRisk runs correctly reproducing the output file provided you get full marks. I will compare your output with
mine using WinMerge. If section 4.4 fails, here you get zero marks.
2
4.7 GCC build (3 marks)
Before submitting, verify that your program compiles and run also with the g++ compiler. To do that on Windows
64 bits you can:
? download setup-x86 64.exe from https://www.cygwin.com/instal...
? Run the installer. When you install, select at least the packages ”gcc-g++” and ”make”.
? launch cygwin and type the command cygpath -w $(pwd).
? Copy your cpp and h files and the Makefile I provided to the directory revealed by the previous command
? Type ”make”
? If all is well, you should see all your files compiling without errors and an executable file created.
? Test the executable file from the command line.
5 Submission instructions
? 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, visual studio project files, object files, executable files, output files).
? Files should be zipped without directory path. I.e. your zip file should contain file1.cpp, file2.cpp, ..., not
dirname\file1.cpp, dirname\file2.cpp, ....
? You need to include in the zip file also a file with name GROUP.TXT containing team members. The file must
contains the student IDs separated by a newline, i.e.
studentID1
studentID2
...
? Submit only a zip file, other formats are not allowed (e.g. arj, 7z)
? Submit only only once.
? Only one member of the team must submit, not all members.
6 Tasks
6.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 representation, 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 converting to a string in calendar format for display to the screen, you need to convert serial
on-the-fly into day, month and year.
6.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:
- Construction of an invalid date should generate an error: generate intentionally 1000 random invalid dates (e.g.
31-Apr-2010) and verify that the Date class constructor throws an error (use try...catch). - 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). - 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.
3
6.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 hexadecimal 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 sequence 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).
6.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.
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 theSTL library to search for a string pattern and implement the method
MarketDataServer::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 member 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.
6.4.1 PV01 with tenors
The PV01 risk function needs to be modified into 2 different risk functions: 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 in Demo.cpp. The 2 new functions
must have the same arguments and return type as the existing one.
6.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 problematic trades fail
to price while all remaining trades price succesfully.
4
Modify the typedef portfolio values t to vector【FE5226 风险管理】>. 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 number of trades which fail to price.
Compare the output of your program with the one given.
6.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
appropriately.
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. Ignore 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 account.
Modify the PricerPayment class to use the new class CurveFXSpot.
6.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. Modify the function
ITrade::Pricer adding an argument configuration of type string. Remember to update the command line help 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.
6.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 currency 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
6.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.
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 fixings.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. Remember to update the command line help string.
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 hexadecimal in the data file.
6.10 Add FX Spot Greek function
Add another function for Greeks calculation, named fx delta, which computes 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.
6.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 serialized representation:
ID; NOTIONAL; CCY1; CCY2; STRIKE; FIXINGDATE; SETTLEDATE;
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).
6.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
6
? 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 - Q&A
7.1 How do I save the output of my program to a file?
You can redirect the output of your program to a file adding at the end of the command line: > filename.txt.
7.2 How do I measure the execution time of my program? - Compile your program for x64 platform in Release mode.
- Redirect the console to a file, as explained above (this is because the console is very slow, and your time measurement
would not be reflective of the real computation time) - Run the command echo %time% before and after calling your command. You might consider creating a batch
file (.bat) and run it from the windows shell (cmd).
7.3 How can I know if the performace of my program is reasonable?
I will post my implementation as an executable for Win64. You can compare your implementation with mine. I will
not post an equivalent binary file for MacOS.
推荐阅读
- 使用OpenCV和Swift进行对象检测
- HorusLP-Gurobi(Gurobi的高级优化架构)
- 使用Google Cloud自然语言API的NLP
- 建立你的第一个电报机器人(分步指南)
- 深入了解JSON与XML,第2部分(两者的优点和缺点)
- 深入了解JSON与XML,第1部分(每个标准的历史)
- Stork,第2部分(创建表达式解析器)
- 2019年的增压测试技巧(Java自动化测试教程)
- 深入了解JSON与XML,第3部分(XML和JSON的未来)