Skip to content

Commit 4d19338

Browse files
committed
Working on historical and MC simulation
1 parent cb01869 commit 4d19338

File tree

7 files changed

+914
-256
lines changed

7 files changed

+914
-256
lines changed

pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,13 @@
335335
<artifactId>gson</artifactId>
336336
<version>2.11.0</version>
337337
</dependency>
338+
339+
<dependency>
340+
<groupId>org.apache.poi</groupId>
341+
<artifactId>poi-ooxml</artifactId>
342+
<version>5.2.5</version>
343+
</dependency>
344+
338345
</dependencies>
339346

340347
<profiles>
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
package net.finmath.smartcontract.simulation;
2+
3+
import java.time.LocalDate;
4+
import java.util.ArrayList;
5+
import java.util.Arrays;
6+
import java.util.Comparator;
7+
import java.util.List;
8+
import java.util.stream.IntStream;
9+
import java.util.stream.Stream;
10+
11+
import org.apache.commons.lang3.ArrayUtils;
12+
13+
import net.finmath.marketdata.calibration.CalibratedCurves;
14+
import net.finmath.marketdata.model.AnalyticModel;
15+
import net.finmath.marketdata.model.AnalyticModelFromCurvesAndVols;
16+
import net.finmath.marketdata.model.curves.Curve;
17+
import net.finmath.marketdata.model.curves.CurveInterpolation;
18+
import net.finmath.marketdata.model.curves.DiscountCurveInterpolation;
19+
import net.finmath.marketdata.model.curves.ForwardCurve;
20+
import net.finmath.marketdata.model.curves.ForwardCurveFromDiscountCurve;
21+
import net.finmath.marketdata.model.curves.ForwardCurveInterpolation;
22+
import net.finmath.marketdata.model.curves.ForwardCurveWithFixings;
23+
import net.finmath.optimizer.SolverException;
24+
import net.finmath.smartcontract.valuation.marketdata.curvecalibration.CalibrationDataItem;
25+
import net.finmath.time.FloatingpointDate;
26+
import net.finmath.time.Period;
27+
import net.finmath.time.Schedule;
28+
import net.finmath.time.ScheduleFromPeriods;
29+
import net.finmath.time.ScheduleGenerator;
30+
import net.finmath.time.businessdaycalendar.BusinessdayCalendar;
31+
import net.finmath.time.businessdaycalendar.BusinessdayCalendarExcludingTARGETHolidays;
32+
import net.finmath.time.daycount.DayCountConvention_ACT_360;
33+
34+
35+
public class InterestRateAnalyticCalibration {
36+
37+
public static String DISCOUNT_EUR_OIS = "discount-EUR-OIS";
38+
public static String FORWARD_EUR_6M = "forward-EUR-6M";
39+
private static String FORWARD_EUR_OIS = "forward-EUR-OIS";
40+
private static String FIXED_EUR_6M = "fixed-EUR-6M";
41+
42+
private List<CalibrationDataItem> fixings;
43+
44+
public InterestRateAnalyticCalibration() {
45+
this.fixings = new ArrayList<>();
46+
}
47+
48+
49+
public AnalyticModel getCalibratedModel(LocalDate referenceDate, double[] discountCurveQuotes, double[] forwardCurveQuotes) throws CloneNotSupportedException, SolverException {
50+
51+
final AnalyticModelFromCurvesAndVols model = new AnalyticModelFromCurvesAndVols(new Curve[] { getDiscountCurveEurOIS(referenceDate), getForwardCurveEurOIS(referenceDate), getForwardCurveEur6M(referenceDate)});
52+
CalibratedCurves.CalibrationSpec[] specs =
53+
Stream.of(getCalibrationSpecsEurOIS(referenceDate, discountCurveQuotes), getCalibrationSpecsEur6M(referenceDate, forwardCurveQuotes))
54+
.flatMap(Arrays::stream)
55+
.toArray(CalibratedCurves.CalibrationSpec[]::new);
56+
57+
CalibratedCurves calibratedCurves = new CalibratedCurves(specs, model, 1E-9);
58+
return calibratedCurves.getModel();
59+
}
60+
61+
62+
public void addFixingItem(CalibrationDataItem fixingItem) {
63+
this.fixings.add(fixingItem);
64+
}
65+
66+
67+
private DiscountCurveInterpolation getDiscountCurveEurOIS(LocalDate referenceDate) {
68+
ArrayList<Double> fixingValuesList = new ArrayList<>();
69+
ArrayList<Double> fixingTimesList = new ArrayList<>();
70+
ArrayList<Double> dfList = new ArrayList<>();
71+
ArrayList<Double> dfTimesList = new ArrayList<>();
72+
fixings.stream().filter(x -> x.getCurveName().equals("ESTR"))
73+
.sorted(Comparator.comparing(CalibrationDataItem::getDate).reversed())
74+
.forEach(x -> {
75+
double time = FloatingpointDate.getFloatingPointDateFromDate(
76+
referenceDate,
77+
x.getDate());
78+
if (time < 0) {
79+
fixingTimesList.add(time);
80+
fixingValuesList.add(365.0 * Math.log(1 + x.getQuote() / 360.0));
81+
//conversion from 1-day ESTR (ACT/360) to zero-rate (ACT/ACT)
82+
//see https://quant.stackexchange.com/questions/73522/how-does-bloomberg-calculate-the-discount-rate-from-eur-estr-curve
83+
}
84+
});
85+
// Add initial zero entries for calculations
86+
fixingTimesList.add(0, 0.0);
87+
fixingValuesList.add(0, 0.0);
88+
// Add time zero discount factor
89+
dfTimesList.add(0, 0.0);
90+
dfList.add(0, 1.0);
91+
IntStream.range(1, fixingTimesList.size()).forEach(i -> {
92+
double df = dfList.get(i - 1) * Math.exp(-fixingValuesList.get(i) * (fixingTimesList.get(i) - fixingTimesList.get(i - 1)));
93+
dfList.add(df);
94+
dfTimesList.add(fixingTimesList.get(i));
95+
});
96+
boolean[] isParameters = ArrayUtils.toPrimitive(
97+
IntStream.range(0, dfTimesList.size()).boxed().map(x -> false).toList().toArray(Boolean[]::new));
98+
double[] dfTimes = dfTimesList.stream().mapToDouble(Double::doubleValue).toArray();
99+
double[] dfValues = dfList.stream().mapToDouble(Double::doubleValue).toArray();
100+
return DiscountCurveInterpolation.createDiscountCurveFromDiscountFactors(DISCOUNT_EUR_OIS,
101+
referenceDate, dfTimes, dfValues, isParameters, CurveInterpolation.InterpolationMethod.LINEAR,
102+
CurveInterpolation.ExtrapolationMethod.CONSTANT, CurveInterpolation.InterpolationEntity.LOG_OF_VALUE);
103+
104+
}
105+
106+
private ForwardCurve getForwardCurveEurOIS(LocalDate referenceDate) {
107+
final ForwardCurve forwardCurve = new ForwardCurveFromDiscountCurve(FORWARD_EUR_OIS,
108+
DISCOUNT_EUR_OIS,
109+
DISCOUNT_EUR_OIS,
110+
referenceDate,
111+
"1D",
112+
new BusinessdayCalendarExcludingTARGETHolidays(),
113+
BusinessdayCalendar.DateRollConvention.FOLLOWING,
114+
365.0 / 360.0, 0.0);
115+
return forwardCurve;
116+
}
117+
118+
private ForwardCurve getForwardCurveEur6M(LocalDate referenceDate) {
119+
double[] fixingTimes = fixings.stream().filter(x -> x.getCurveName().equals("Euribor6M")).map(x -> x.getDate())
120+
.map(x -> FloatingpointDate.getFloatingPointDateFromDate(referenceDate, x))
121+
.mapToDouble(Double::doubleValue).sorted().toArray();
122+
if (fixingTimes.length == 0) { //if there are no fixings return empty curve
123+
return new ForwardCurveInterpolation(FORWARD_EUR_6M,
124+
referenceDate,
125+
"6M",
126+
new BusinessdayCalendarExcludingTARGETHolidays(),
127+
BusinessdayCalendar.DateRollConvention.FOLLOWING,
128+
CurveInterpolation.InterpolationMethod.LINEAR,
129+
CurveInterpolation.ExtrapolationMethod.CONSTANT,
130+
CurveInterpolation.InterpolationEntity.VALUE,
131+
ForwardCurveInterpolation.InterpolationEntityForward.FORWARD,
132+
DISCOUNT_EUR_OIS);
133+
}
134+
double[] fixingValues = fixings.stream().filter(x -> x.getCurveName().equals("Euribor6M"))
135+
.sorted(Comparator.comparing(CalibrationDataItem::getDate)).map(CalibrationDataItem::getQuote)
136+
.mapToDouble(Double::doubleValue).toArray();
137+
ForwardCurve fixedPart = ForwardCurveInterpolation.createForwardCurveFromForwards(FIXED_EUR_6M,
138+
referenceDate,
139+
"6M",
140+
new BusinessdayCalendarExcludingTARGETHolidays(),
141+
BusinessdayCalendar.DateRollConvention.FOLLOWING,
142+
CurveInterpolation.InterpolationMethod.LINEAR,
143+
CurveInterpolation.ExtrapolationMethod.CONSTANT,
144+
CurveInterpolation.InterpolationEntity.VALUE,
145+
ForwardCurveInterpolation.InterpolationEntityForward.FORWARD,
146+
DISCOUNT_EUR_OIS, null, fixingTimes, fixingValues);
147+
ForwardCurve forwardPart = new ForwardCurveInterpolation(FORWARD_EUR_6M,
148+
referenceDate,
149+
"6M",
150+
new BusinessdayCalendarExcludingTARGETHolidays(),
151+
BusinessdayCalendar.DateRollConvention.FOLLOWING,
152+
CurveInterpolation.InterpolationMethod.LINEAR,
153+
CurveInterpolation.ExtrapolationMethod.CONSTANT,
154+
CurveInterpolation.InterpolationEntity.VALUE,
155+
ForwardCurveInterpolation.InterpolationEntityForward.FORWARD,
156+
DISCOUNT_EUR_OIS);
157+
// this is a dirty-ish fix: if the extrema of the fixed part lay exactly on the time specified by the data point,
158+
// some weird jumpiness occurs... TODO: mayb there's a smarter solution
159+
return new ForwardCurveWithFixings(forwardPart, fixedPart,
160+
Arrays.stream(fixingTimes).min().orElseThrow() - 1.0 / 365.0,
161+
Arrays.stream(fixingTimes).max().orElseThrow() + 1.0 / 365.0);
162+
}
163+
164+
private static CalibratedCurves.CalibrationSpec[] getCalibrationSpecsEurOIS(LocalDate referenceDate, double[] quotes) {
165+
final String[] maturities = { "1D", "7D", "14D", "21D", "1M", "2M", "3M", "4M", "5M", "6M", "7M", "8M", "9M", "10M", "11M", "1Y", "15M", "18M", "21M", "2Y", "3Y", "4Y", "5Y", "6Y", "7Y", "8Y", "9Y", "10Y", "12Y", "15Y", "20Y", "25Y", "30Y"};
166+
final String[] frequency = { "tenor", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual"};
167+
168+
CalibratedCurves.CalibrationSpec[] specs = new CalibratedCurves.CalibrationSpec[maturities.length];
169+
BusinessdayCalendar calendar = new BusinessdayCalendarExcludingTARGETHolidays();
170+
// The first product is an overnight cash deposit, followed by 32 swaps
171+
Schedule scheduleInterfaceRec = ScheduleGenerator.createScheduleFromConventions(referenceDate, 0, "0D", maturities[0], frequency[0], "ACT/360", "first", "follow", calendar, 0, 0);
172+
double calibrationTime = scheduleInterfaceRec.getPayment(scheduleInterfaceRec.getNumberOfPeriods() - 1);
173+
specs[0] = new CalibratedCurves.CalibrationSpec(String.format("EUR-OIS-%1$s", maturities[0]), "Deposit", scheduleInterfaceRec, FORWARD_EUR_OIS, quotes[0], DISCOUNT_EUR_OIS, null, "", 0.0, DISCOUNT_EUR_OIS, DISCOUNT_EUR_OIS, calibrationTime);
174+
175+
// OIS ESTR Swaps
176+
for (int i=1; i < quotes.length; i++) {
177+
Schedule scheduleRec = ScheduleGenerator.createScheduleFromConventions(referenceDate, 2, "0D", maturities[i], frequency[i], "ACT/360", "first", "modified_following", calendar, 0, 1);
178+
Schedule schedulePay = ScheduleGenerator.createScheduleFromConventions(referenceDate, 2, "0D", maturities[i], frequency[i], "ACT/360", "first", "modified_following", calendar, 0, 1);
179+
calibrationTime = scheduleRec.getPayment(scheduleRec.getNumberOfPeriods() - 1);
180+
specs[i] = new CalibratedCurves.CalibrationSpec(String.format("EUR-OIS-%1$s", maturities[i]), "Swap", scheduleRec, FORWARD_EUR_OIS, 0.0, DISCOUNT_EUR_OIS, schedulePay, "", quotes[i], DISCOUNT_EUR_OIS, DISCOUNT_EUR_OIS, calibrationTime);
181+
}
182+
return specs;
183+
}
184+
185+
private static CalibratedCurves.CalibrationSpec[] getCalibrationSpecsEur6M(LocalDate referenceDate, double[] quotes) {
186+
final String tenorLabel = "6M";
187+
final String[] maturities = { "7M", "8M", "9M", "10M", "12M", "15M", "18M", "2Y", "3Y", "4Y", "5Y", "6Y", "7Y", "8Y", "9Y", "10Y", "12Y", "15Y", "20Y", "25Y", "30Y"};
188+
final String[] frequencyFloat = { "", "", "", "", "", "", "", "semiannual", "semiannual", "semiannual", "semiannual", "semiannual", "semiannual", "semiannual", "semiannual", "semiannual", "semiannual", "semiannual", "semiannual", "semiannual", "semiannual"};
189+
final String[] frequency = { "tenor", "tenor", "tenor", "tenor", "tenor", "tenor", "tenor", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual", "annual"};
190+
final String[] daycountConventionsFloat = { "", "", "", "", "", "", "", "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360"};
191+
final String[] daycountConventions = { "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360", "ACT/360", "E30/360", "E30/360", "E30/360", "E30/360", "E30/360", "E30/360", "E30/360", "E30/360", "E30/360", "E30/360", "E30/360", "E30/360", "E30/360", "E30/360"};
192+
193+
CalibratedCurves.CalibrationSpec[] specs = new CalibratedCurves.CalibrationSpec[maturities.length];
194+
BusinessdayCalendar calendar = new BusinessdayCalendarExcludingTARGETHolidays();
195+
// The first 7 product are FRAs, followed by 14 swaps
196+
for (int i=0; i < 8; i++) {
197+
int nMonthMaturity = Integer.parseInt(maturities[i].replace("M", ""));
198+
int nMonthOffset = nMonthMaturity - Integer.parseInt(tenorLabel.replace("M", ""));
199+
String startOffsetLabel = nMonthOffset + "M";
200+
201+
Schedule scheduleInterfacePrelim = ScheduleGenerator.createScheduleFromConventions(referenceDate, 2, startOffsetLabel, tenorLabel, frequency[i], daycountConventions[i], "first", "follow", calendar, -2, 0);
202+
LocalDate periodStartDate = scheduleInterfacePrelim.getPeriod(0).getPeriodStart();
203+
LocalDate paymentDate = scheduleInterfacePrelim.getPeriod(0).getPayment();
204+
LocalDate fixingDate = scheduleInterfacePrelim.getPeriod(0).getFixing();
205+
LocalDate periodEndDate = calendar.getAdjustedDate(periodStartDate, tenorLabel, BusinessdayCalendar.DateRollConvention.FOLLOWING);
206+
207+
Period period = new Period(fixingDate, paymentDate, periodStartDate, periodEndDate);
208+
Schedule scheduleInterfaceFinal = new ScheduleFromPeriods(referenceDate, new DayCountConvention_ACT_360(), period);
209+
double calibrationTime = scheduleInterfaceFinal.getFixing(scheduleInterfaceFinal.getNumberOfPeriods() - 1);
210+
// Check correctness of discountCurvePayerName = null
211+
specs[i] = new CalibratedCurves.CalibrationSpec("EUR-" + tenorLabel + maturities[i], "FRA", scheduleInterfaceFinal, FORWARD_EUR_6M, quotes[i], DISCOUNT_EUR_OIS, null, "", 0.0, null, FORWARD_EUR_6M, calibrationTime);
212+
}
213+
// 6M Swaps
214+
for (int i=8; i < quotes.length; i++) {
215+
Schedule scheduleInterfaceRec = ScheduleGenerator.createScheduleFromConventions(referenceDate, 2, "0D", maturities[i], frequencyFloat[i], daycountConventionsFloat[i], "first", "modfollow", new BusinessdayCalendarExcludingTARGETHolidays(), -2, 0);
216+
Schedule scheduleInterfacePay = ScheduleGenerator.createScheduleFromConventions(referenceDate, 2, "0D", maturities[i], frequency[i], daycountConventions[i], "first", "modfollow", new BusinessdayCalendarExcludingTARGETHolidays(), -2, 0);
217+
double calibrationTime = scheduleInterfaceRec.getFixing(scheduleInterfaceRec.getNumberOfPeriods() - 1);
218+
specs[i] = new CalibratedCurves.CalibrationSpec("EUR-" + tenorLabel + maturities[i], "Swap", scheduleInterfaceRec, FORWARD_EUR_6M, 0.0, DISCOUNT_EUR_OIS, scheduleInterfacePay, "", quotes[i], DISCOUNT_EUR_OIS, FORWARD_EUR_6M, calibrationTime);
219+
}
220+
return specs;
221+
}
222+
223+
224+
}

0 commit comments

Comments
 (0)