| Signature | Description |
|---|---|
enum class var_policy : unsigned char { historical = 1, // Non-parametric. Use the empirical return distribution gaussian = 2, // Parametric. Assume returns are normally distributed }; |
Method used by ValueAtRiskVisitor to estimate the tail of a return distribution |
| Signature | Description | Parameters |
|---|---|---|
template<arithmetic T, typename I = unsigned long, std::size_t A = 0> struct ValueAtRiskVisitor; // --------------------------------- template<typename T, typename I = unsigned long, std::size_t A = 0> using varisk_v = ValueAtRiskVisitor<T, I, A>; |
This is a "single action visitor", meaning it is passed the whole data vector in one call and you must use the single_act_visit() interface. Value at Risk (VaR) at confidence level c (for example 0.95) is the loss that is not expected to be exceeded, over one return period, with probability c. Conditional Value at Risk (CVaR, also known as Expected Shortfall) is the expected loss, given that the loss is at least as large as VaR. CVaR is always greater than or equal to VaR, and it describes how bad the tail is, not just where it starts. Both numbers are reported as losses: a positive number means a loss. A negative VaR is possible when even the tail of the distribution is a gain. The visitor takes one column of returns and supports two estimation methods, selected with var_policy: var_policy::historical (default). This is non-parametric and makes no assumption about the shape of the distribution. VaR is the negated (1 - c) quantile of the empirical returns, linearly interpolated between the two neighboring order statistics (the same as numpy.quantile with its default). CVaR is the negated mean of all returns that are less than or equal to that quantile. The implementation uses std::nth_element, so it runs in O(n) and does not sort the data. var_policy::gaussian. This is parametric. It assumes returns are normally distributed with sample mean mu and sample standard deviation sigma. With z being the inverse normal CDF at c:
VaR = -mu + z * sigma
CVaR = -mu + sigma * pdf(z) / (1 - c)
If there is no valid (non-NaN) return, both results are NaN. For the gaussian policy at least 2 valid returns are required, otherwise the results are NaN.get_result() and get_var(): return VaR. get_cvar(): returns CVaR.
explicit
ValueAtRiskVisitor(double confidence = 0.95,
var_policy policy = var_policy::historical,
bool biased = false);
confidence: The confidence level. It must be greater than 0 and less than 1.
Typical values are 0.95 and 0.99.
policy: var_policy::historical or var_policy::gaussian from above.
biased: Used by the gaussian policy only. If true, the population standard
deviation is used instead of the sample standard deviation.
|
T: Column data type. I: Index type. A: Memory alignment boundary for vectors. Default is system default alignment |
static void test_ValueAtRiskVisitor() { std::cout << "\nTesting ValueAtRiskVisitor{ } ..." << std::endl; constexpr double nval = std::numeric_limits<double>::quiet_NaN(); ULDataFrame df; StlVecType<unsigned long> idxvec = { 123450, 123451, 123452, 123453, 123454, 123455, 123456, 123457, 123458, 123459, 123460, 123461, 123462, 123466, 123467, 123468, 123469, 123470, 123471, 123472, 123473 }; StlVecType<double> d1 = { 2.5, 2.45, -0.65, -0.1, -1.1, 1.87, 0.98, 0.34, 1.56, -0.34, 2.3, -0.34, -1.9, 0.387, 0.123, 1.06, -0.65, 2.03, 0.4, -1.0, 0.59 }; StlVecType<double> with_nan = { nval, 0.01, -0.02, 0.005, nval, 0.03, -0.015, 0.007, -0.04, 0.02, 0.011, -0.003, 0.0, 0.025, -0.01, nval, nval, nval, nval, nval, nval }; df.load_data(std::move(idxvec), std::make_pair("d1", d1), std::make_pair("with_nan", with_nan)); // Historical { varisk_v<double> v90 { 0.90 }; varisk_v<double> v95; // Default confidence is 95% varisk_v<double> v99 { 0.99 }; df.single_act_visit<double>("d1", v90); df.single_act_visit<double>("d1", v95); df.single_act_visit<double>("d1", v99); assert(std::abs(v90.get_var() - 1.0) < 0.000001); assert(std::abs(v90.get_cvar() - 1.333333) < 0.000001); assert(std::abs(v95.get_var() - 1.1) < 0.000001); assert(std::abs(v95.get_cvar() - 1.5) < 0.000001); assert(v95.get_result() == v95.get_var()); assert(std::abs(v99.get_var() - 1.74) < 0.000001); assert(std::abs(v99.get_cvar() - 1.9) < 0.000001); assert(v90.get_cvar() >= v90.get_var()); assert(v95.get_cvar() >= v95.get_var()); assert(v99.get_cvar() >= v99.get_var()); } // Gaussian { varisk_v<double> g90 { 0.90, var_policy::gaussian }; varisk_v<double> g95 { 0.95, var_policy::gaussian }; varisk_v<double> g95b { 0.95, var_policy::gaussian, true }; varisk_v<double> g99 { 0.99, var_policy::gaussian }; df.single_act_visit<double>("d1", g90); df.single_act_visit<double>("d1", g95); df.single_act_visit<double>("d1", g95b); df.single_act_visit<double>("d1", g99); assert(std::abs(g90.get_var() - 1.123424) < 0.000001); assert(std::abs(g90.get_cvar() - 1.723327) < 0.000001); assert(std::abs(g95.get_var() - 1.583778) < 0.000001); assert(std::abs(g95.get_cvar() - 2.113262) < 0.000001); assert(std::abs(g95b.get_var() - 1.533547) < 0.000001); assert(std::abs(g95b.get_cvar() - 2.050271) < 0.000001); assert(std::abs(g99.get_var() - 2.447324) < 0.000001); assert(std::abs(g99.get_cvar() - 2.876714) < 0.000001); } // NaN values are skipped { varisk_v<double> hist { 0.90 }; varisk_v<double> gaus { 0.90, var_policy::gaussian }; df.single_act_visit<double>("with_nan", hist); df.single_act_visit<double>("with_nan", gaus); assert(std::abs(hist.get_var() - 0.019) < 0.000001); assert(std::abs(hist.get_cvar() - 0.03) < 0.000001); assert(std::abs(gaus.get_var() - 0.0233427) < 0.0000001); assert(std::abs(gaus.get_cvar() - 0.0325343) < 0.0000001); } // No valid data { StlVecType<unsigned long> idx2 = { 1, 2, 3 }; StlVecType<double> all_nan = { nval, nval, nval }; ULDataFrame df2; df2.load_data(std::move(idx2), std::make_pair("all_nan", all_nan)); varisk_v<double> hist; varisk_v<double> gaus { 0.95, var_policy::gaussian }; df2.single_act_visit<double>("all_nan", hist); df2.single_act_visit<double>("all_nan", gaus); assert(std::isnan(hist.get_var()) && std::isnan(hist.get_cvar())); assert(std::isnan(gaus.get_var()) && std::isnan(gaus.get_cvar())); } #ifdef HMDF_SANITY_EXCEPTIONS for (const double c : { 0.0, 1.0, -0.5, 1.5 }) { varisk_v<double> bad_conf { c }; bool thrown { false }; try { df.single_act_visit<double>("d1", bad_conf); } catch (const DataFrameError &) { thrown = true; } assert(thrown); } #endif // HMDF_SANITY_EXCEPTIONS StrDataFrame ibm_df; // Real data try { ibm_df.read("IBM.csv", io_format::csv2); } catch (const DataFrameError &ex) { std::cout << ex.what() << std::endl; ::exit(-1); } ibm_df.load_result_as_column<double>("IBM_Close", ReturnVisitor<double, std::string>(return_policy::percentage), "IBM_Return"); varisk_v<double, std::string> h90 { 0.90 }; varisk_v<double, std::string> h95 { 0.95 }; varisk_v<double, std::string> h99 { 0.99 }; varisk_v<double, std::string> g90b { 0.90, var_policy::gaussian, true }; varisk_v<double, std::string> g95 { 0.95, var_policy::gaussian }; varisk_v<double, std::string> g99 { 0.99, var_policy::gaussian }; ibm_df.single_act_visit<double>("IBM_Return", h90); ibm_df.single_act_visit<double>("IBM_Return", h95); ibm_df.single_act_visit<double>("IBM_Return", h99); ibm_df.single_act_visit<double>("IBM_Return", g90b); ibm_df.single_act_visit<double>("IBM_Return", g95); ibm_df.single_act_visit<double>("IBM_Return", g99); assert(std::abs(h90.get_var() - 0.0155130) < 0.0000001); assert(std::abs(h90.get_cvar() - 0.0289660) < 0.0000001); assert(std::abs(h95.get_var() - 0.0235495) < 0.0000001); assert(std::abs(h95.get_cvar() - 0.0388069) < 0.0000001); assert(std::abs(h99.get_var() - 0.0490875) < 0.0000001); assert(std::abs(h99.get_cvar() - 0.0623592) < 0.0000001); assert(std::abs(g90b.get_var() - 0.0203943) < 0.0000001); assert(std::abs(g90b.get_cvar() - 0.0279850) < 0.0000001); assert(std::abs(g95.get_var() - 0.0262219) < 0.0000001); assert(std::abs(g95.get_cvar() - 0.0329222) < 0.0000001); assert(std::abs(g99.get_var() - 0.0371496) < 0.0000001); assert(std::abs(g99.get_cvar() - 0.0425833) < 0.0000001); }