1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2018 Roy Zywina
5 Copyright (C) 2018 StatPro Italia srl
6
7 This file is part of QuantLib, a free-software/open-source library
8 for financial quantitative analysts and developers - http://quantlib.org/
9
10 QuantLib is free software: you can redistribute it and/or modify it
11 under the terms of the QuantLib license. You should have received a
12 copy of the license along with this program; if not, please email
13 <quantlib-dev@lists.sf.net>. The license is also available online at
14 <http://quantlib.org/license.shtml>.
15
16 This program is distributed in the hope that it will be useful, but WITHOUT
17 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18 FOR A PARTICULAR PURPOSE. See the license for more details.
19*/
20
21#include "sofrfutures.hpp"
22#include "utilities.hpp"
23#include <ql/instruments/overnightindexfuture.hpp>
24#include <ql/indexes/ibor/sofr.hpp>
25#include <ql/termstructures/yield/piecewiseyieldcurve.hpp>
26#include <ql/termstructures/yield/overnightindexfutureratehelper.hpp>
27#include <iomanip>
28
29using namespace QuantLib;
30using namespace boost::unit_test_framework;
31
32namespace {
33
34 struct SofrQuotes {
35 Frequency freq;
36 Month month;
37 Year year;
38 Real price;
39 RateAveraging::Type averagingMethod;
40 };
41
42}
43
44
45void SofrFuturesTest::testBootstrap() {
46 BOOST_TEST_MESSAGE("Testing bootstrap over SOFR futures...");
47
48 Date today = Date(26, October, 2018);
49 Settings::instance().evaluationDate() = today;
50
51 const SofrQuotes sofrQuotes[] = {
52 {.freq: Monthly, .month: Oct, .year: 2018, .price: 97.8175, .averagingMethod: RateAveraging::Simple},
53 {.freq: Monthly, .month: Nov, .year: 2018, .price: 97.770, .averagingMethod: RateAveraging::Simple},
54 {.freq: Monthly, .month: Dec, .year: 2018, .price: 97.685, .averagingMethod: RateAveraging::Simple},
55 {.freq: Monthly, .month: Jan, .year: 2019, .price: 97.595, .averagingMethod: RateAveraging::Simple},
56 {.freq: Monthly, .month: Feb, .year: 2019, .price: 97.590, .averagingMethod: RateAveraging::Simple},
57 {.freq: Monthly, .month: Mar, .year: 2019, .price: 97.525, .averagingMethod: RateAveraging::Simple},
58 {.freq: Quarterly, .month: Mar, .year: 2019, .price: 97.440, .averagingMethod: RateAveraging::Compound},
59 {.freq: Quarterly, .month: Jun, .year: 2019, .price: 97.295, .averagingMethod: RateAveraging::Compound},
60 {.freq: Quarterly, .month: Sep, .year: 2019, .price: 97.220, .averagingMethod: RateAveraging::Compound},
61 {.freq: Quarterly, .month: Dec, .year: 2019, .price: 97.170, .averagingMethod: RateAveraging::Compound},
62 {.freq: Quarterly, .month: Mar, .year: 2020, .price: 97.160, .averagingMethod: RateAveraging::Compound},
63 {.freq: Quarterly, .month: Jun, .year: 2020, .price: 97.165, .averagingMethod: RateAveraging::Compound},
64 {.freq: Quarterly, .month: Sep, .year: 2020, .price: 97.175, .averagingMethod: RateAveraging::Compound},
65 };
66
67 ext::shared_ptr<OvernightIndex> index = ext::make_shared<Sofr>();
68 index->addFixing(fixingDate: Date(1, October, 2018), fixing: 0.0222);
69 index->addFixing(fixingDate: Date(2, October, 2018), fixing: 0.022);
70 index->addFixing(fixingDate: Date(3, October, 2018), fixing: 0.022);
71 index->addFixing(fixingDate: Date(4, October, 2018), fixing: 0.0218);
72 index->addFixing(fixingDate: Date(5, October, 2018), fixing: 0.0216);
73 index->addFixing(fixingDate: Date(9, October, 2018), fixing: 0.0215);
74 index->addFixing(fixingDate: Date(10, October, 2018), fixing: 0.0215);
75 index->addFixing(fixingDate: Date(11, October, 2018), fixing: 0.0217);
76 index->addFixing(fixingDate: Date(12, October, 2018), fixing: 0.0218);
77 index->addFixing(fixingDate: Date(15, October, 2018), fixing: 0.0221);
78 index->addFixing(fixingDate: Date(16, October, 2018), fixing: 0.0218);
79 index->addFixing(fixingDate: Date(17, October, 2018), fixing: 0.0218);
80 index->addFixing(fixingDate: Date(18, October, 2018), fixing: 0.0219);
81 index->addFixing(fixingDate: Date(19, October, 2018), fixing: 0.0219);
82 index->addFixing(fixingDate: Date(22, October, 2018), fixing: 0.0218);
83 index->addFixing(fixingDate: Date(23, October, 2018), fixing: 0.0217);
84 index->addFixing(fixingDate: Date(24, October, 2018), fixing: 0.0218);
85 index->addFixing(fixingDate: Date(25, October, 2018), fixing: 0.0219);
86
87 std::vector<ext::shared_ptr<RateHelper> > helpers;
88 for (const auto& sofrQuote : sofrQuotes) {
89 helpers.push_back(x: ext::make_shared<SofrFutureRateHelper>(
90 args: sofrQuote.price, args: sofrQuote.month, args: sofrQuote.year, args: sofrQuote.freq));
91 }
92
93 ext::shared_ptr<PiecewiseYieldCurve<Discount, Linear> > curve =
94 ext::make_shared<PiecewiseYieldCurve<Discount, Linear> >(args&: today, args&: helpers,
95 args: Actual365Fixed());
96
97 // test curve with one of the futures
98 ext::shared_ptr<OvernightIndex> sofr =
99 ext::make_shared<Sofr>(args: Handle<YieldTermStructure>(curve));
100 OvernightIndexFuture sf(sofr, Date(20, March, 2019), Date(19, June, 2019));
101
102 Real expected_price = 97.44;
103 Real tolerance = 1.0e-9;
104
105 Real error = std::fabs(x: sf.NPV() - expected_price);
106 if (error > tolerance) {
107 BOOST_ERROR("sample futures:\n"
108 << std::setprecision(8)
109 << "\n estimated price: " << sf.NPV()
110 << "\n expected price: " << expected_price
111 << "\n error: " << error
112 << "\n tolerance: " << tolerance);
113 }
114}
115
116
117test_suite* SofrFuturesTest::suite() {
118 auto* suite = BOOST_TEST_SUITE("SOFR futures tests");
119
120 suite->add(QUANTLIB_TEST_CASE(&SofrFuturesTest::testBootstrap));
121
122 return suite;
123}
124

source code of quantlib/test-suite/sofrfutures.cpp