com.opengamma.strata.loader.impl.fpml.SwapFpmlParserPlugin Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of strata-loader Show documentation
Show all versions of strata-loader Show documentation
Loaders from standard and Strata-specific data formats
/*
* Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.loader.impl.fpml;
import java.time.LocalDate;
import java.time.Period;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import com.google.common.collect.ImmutableList;
import com.opengamma.strata.basics.date.AdjustableDate;
import com.opengamma.strata.basics.date.BusinessDayAdjustment;
import com.opengamma.strata.basics.date.DaysAdjustment;
import com.opengamma.strata.basics.index.FloatingRateName;
import com.opengamma.strata.basics.index.FloatingRateType;
import com.opengamma.strata.basics.index.IborIndex;
import com.opengamma.strata.basics.index.Index;
import com.opengamma.strata.basics.index.OvernightIndex;
import com.opengamma.strata.basics.schedule.Frequency;
import com.opengamma.strata.basics.schedule.PeriodicSchedule;
import com.opengamma.strata.basics.schedule.StubConvention;
import com.opengamma.strata.basics.value.ValueAdjustment;
import com.opengamma.strata.basics.value.ValueSchedule;
import com.opengamma.strata.basics.value.ValueStep;
import com.opengamma.strata.basics.value.ValueStepSequence;
import com.opengamma.strata.collect.io.XmlElement;
import com.opengamma.strata.loader.fpml.FpmlDocument;
import com.opengamma.strata.loader.fpml.FpmlParseException;
import com.opengamma.strata.loader.fpml.FpmlParserPlugin;
import com.opengamma.strata.product.Trade;
import com.opengamma.strata.product.TradeInfoBuilder;
import com.opengamma.strata.product.common.PayReceive;
import com.opengamma.strata.product.swap.CompoundingMethod;
import com.opengamma.strata.product.swap.FixedRateCalculation;
import com.opengamma.strata.product.swap.FixedRateStubCalculation;
import com.opengamma.strata.product.swap.FixingRelativeTo;
import com.opengamma.strata.product.swap.FutureValueNotional;
import com.opengamma.strata.product.swap.IborRateCalculation;
import com.opengamma.strata.product.swap.IborRateResetMethod;
import com.opengamma.strata.product.swap.IborRateStubCalculation;
import com.opengamma.strata.product.swap.InflationRateCalculation;
import com.opengamma.strata.product.swap.KnownAmountSwapLeg;
import com.opengamma.strata.product.swap.NegativeRateMethod;
import com.opengamma.strata.product.swap.NotionalSchedule;
import com.opengamma.strata.product.swap.OvernightAccrualMethod;
import com.opengamma.strata.product.swap.OvernightRateCalculation;
import com.opengamma.strata.product.swap.PaymentRelativeTo;
import com.opengamma.strata.product.swap.PaymentSchedule;
import com.opengamma.strata.product.swap.PriceIndexCalculationMethod;
import com.opengamma.strata.product.swap.RateCalculation;
import com.opengamma.strata.product.swap.RateCalculationSwapLeg;
import com.opengamma.strata.product.swap.ResetSchedule;
import com.opengamma.strata.product.swap.Swap;
import com.opengamma.strata.product.swap.SwapLeg;
import com.opengamma.strata.product.swap.SwapTrade;
/**
* FpML parser for Swaps.
*
* This parser handles the subset of FpML necessary to populate the trade model.
*
* The following features are not available in the Strata trade model:
*
* - initial fixing date
*
- first payment date
*
- last regular payment date
*
- weekly reset frequency
*
- spread/gearing in a stub
*
- overnight leg first rate is known
*
- overnight leg stubs
*
- FRA discounting
*
- non-delivered settlement
*
- rate treatment
*
- FX reset first rate is known
*
*/
final class SwapFpmlParserPlugin
implements FpmlParserPlugin {
// this class is loaded by ExtendedEnum reflection
/**
* The singleton instance of the parser.
*/
public static final SwapFpmlParserPlugin INSTANCE = new SwapFpmlParserPlugin();
/**
* Restricted constructor.
*/
private SwapFpmlParserPlugin() {
}
//-------------------------------------------------------------------------
@Override
public Trade parseTrade(FpmlDocument document, XmlElement tradeEl) {
TradeInfoBuilder tradeInfoBuilder = document.parseTradeInfo(tradeEl);
Swap swap = parseSwap(document, tradeEl, tradeInfoBuilder);
return SwapTrade.builder()
.info(tradeInfoBuilder.build())
.product(swap)
.build();
}
// parses the swap
Swap parseSwap(FpmlDocument document, XmlElement tradeEl, TradeInfoBuilder tradeInfoBuilder) {
// supported elements:
// 'swapStream+'
// 'swapStream/buyerPartyReference'
// 'swapStream/sellerPartyReference'
// 'swapStream/calculationPeriodDates'
// 'swapStream/paymentDates'
// 'swapStream/resetDates?'
// 'swapStream/calculationPeriodAmount'
// 'swapStream/stubCalculationPeriodAmount?'
// 'swapStream/principalExchanges?'
// 'swapStream/calculationPeriodAmount/knownAmountSchedule'
// ignored elements:
// 'Product.model?'
// 'swapStream/cashflows?'
// 'swapStream/settlementProvision?'
// 'swapStream/formula?'
// 'earlyTerminationProvision?'
// 'cancelableProvision?'
// 'extendibleProvision?'
// 'additionalPayment*'
// 'additionalTerms?'
// rejected elements:
// 'swapStream/calculationPeriodAmount/calculation/fxLinkedNotionalSchedule'
XmlElement swapEl = tradeEl.getChild("swap");
ImmutableList legEls = swapEl.getChildren("swapStream");
ImmutableList.Builder legsBuilder = ImmutableList.builder();
for (XmlElement legEl : legEls) {
// calculation
XmlElement calcPeriodAmountEl = legEl.getChild("calculationPeriodAmount");
XmlElement calcEl = calcPeriodAmountEl.findChild("calculation")
.orElse(XmlElement.ofChildren("calculation", ImmutableList.of()));
PeriodicSchedule accrualSchedule = parseSwapAccrualSchedule(legEl, document);
PaymentSchedule paymentSchedule = parseSwapPaymentSchedule(legEl, calcEl, document);
// known amount or rate calculation
Optional knownAmountOptEl = calcPeriodAmountEl.findChild("knownAmountSchedule");
if (knownAmountOptEl.isPresent()) {
XmlElement knownAmountEl = knownAmountOptEl.get();
document.validateNotPresent(legEl, "stubCalculationPeriodAmount");
document.validateNotPresent(legEl, "resetDates");
// pay/receive and counterparty
PayReceive payReceive = document.parsePayerReceiver(legEl, tradeInfoBuilder);
ValueSchedule amountSchedule = parseSchedule(knownAmountEl, document);
// build
legsBuilder.add(KnownAmountSwapLeg.builder()
.payReceive(payReceive)
.accrualSchedule(accrualSchedule)
.paymentSchedule(paymentSchedule)
.amount(amountSchedule)
.currency(document.parseCurrency(knownAmountEl.getChild("currency")))
.build());
} else {
document.validateNotPresent(calcEl, "fxLinkedNotionalSchedule");
// pay/receive and counterparty
PayReceive payReceive = document.parsePayerReceiver(legEl, tradeInfoBuilder);
NotionalSchedule notionalSchedule = parseSwapNotionalSchedule(legEl, calcEl, document);
RateCalculation calculation = parseSwapCalculation(legEl, calcEl, accrualSchedule, document);
// build
legsBuilder.add(RateCalculationSwapLeg.builder()
.payReceive(payReceive)
.accrualSchedule(accrualSchedule)
.paymentSchedule(paymentSchedule)
.notionalSchedule(notionalSchedule)
.calculation(calculation)
.build());
}
}
return Swap.of(legsBuilder.build());
}
// parses the accrual schedule
private PeriodicSchedule parseSwapAccrualSchedule(XmlElement legEl, FpmlDocument document) {
// supported elements:
// 'calculationPeriodDates/effectiveDate'
// 'calculationPeriodDates/relativeEffectiveDate'
// 'calculationPeriodDates/terminationDate'
// 'calculationPeriodDates/relativeTerminationDate'
// 'calculationPeriodDates/calculationPeriodDates'
// 'calculationPeriodDates/calculationPeriodDatesAdjustments'
// 'calculationPeriodDates/firstPeriodStartDate?'
// 'calculationPeriodDates/firstRegularPeriodStartDate?'
// 'calculationPeriodDates/lastRegularPeriodEndDate?'
// 'calculationPeriodDates/stubPeriodType?'
// 'calculationPeriodDates/calculationPeriodFrequency'
// ignored elements:
// 'calculationPeriodDates/firstCompoundingPeriodEndDate?'
PeriodicSchedule.Builder accrualScheduleBuilder = PeriodicSchedule.builder();
// calculation dates
XmlElement calcPeriodDatesEl = legEl.getChild("calculationPeriodDates");
// business day adjustments
BusinessDayAdjustment bda = document.parseBusinessDayAdjustments(
calcPeriodDatesEl.getChild("calculationPeriodDatesAdjustments"));
accrualScheduleBuilder.businessDayAdjustment(bda);
// start date
AdjustableDate startDate = calcPeriodDatesEl.findChild("effectiveDate")
.map(el -> document.parseAdjustableDate(el))
.orElseGet(() -> document.parseAdjustedRelativeDateOffset(calcPeriodDatesEl.getChild("relativeEffectiveDate")));
accrualScheduleBuilder.startDate(startDate.getUnadjusted());
if (!bda.equals(startDate.getAdjustment())) {
accrualScheduleBuilder.startDateBusinessDayAdjustment(startDate.getAdjustment());
}
// end date
AdjustableDate endDate = calcPeriodDatesEl.findChild("terminationDate")
.map(el -> document.parseAdjustableDate(el))
.orElseGet(() -> document.parseAdjustedRelativeDateOffset(calcPeriodDatesEl.getChild("relativeTerminationDate")));
accrualScheduleBuilder.endDate(endDate.getUnadjusted());
if (!bda.equals(endDate.getAdjustment())) {
accrualScheduleBuilder.endDateBusinessDayAdjustment(endDate.getAdjustment());
}
// first period start date
calcPeriodDatesEl.findChild("firstPeriodStartDate").ifPresent(el -> {
accrualScheduleBuilder.overrideStartDate(document.parseAdjustableDate(el));
});
// first regular date
calcPeriodDatesEl.findChild("firstRegularPeriodStartDate").ifPresent(el -> {
accrualScheduleBuilder.firstRegularStartDate(document.parseDate(el));
});
// last regular date
calcPeriodDatesEl.findChild("lastRegularPeriodEndDate").ifPresent(el -> {
accrualScheduleBuilder.lastRegularEndDate(document.parseDate(el));
});
// stub type
calcPeriodDatesEl.findChild("stubPeriodType").ifPresent(el -> {
accrualScheduleBuilder.stubConvention(parseStubConvention(el, document));
});
// frequency
XmlElement freqEl = calcPeriodDatesEl.getChild("calculationPeriodFrequency");
Frequency accrualFreq = document.parseFrequency(freqEl);
accrualScheduleBuilder.frequency(accrualFreq);
// roll convention
accrualScheduleBuilder.rollConvention(
document.convertRollConvention(freqEl.getChild("rollConvention").getContent()));
return accrualScheduleBuilder.build();
}
// parses the payment schedule
private PaymentSchedule parseSwapPaymentSchedule(XmlElement legEl, XmlElement calcEl, FpmlDocument document) {
// supported elements:
// 'paymentDates/paymentFrequency'
// 'paymentDates/payRelativeTo'
// 'paymentDates/paymentDaysOffset?'
// 'paymentDates/paymentDatesAdjustments'
// 'calculationPeriodAmount/calculation/compoundingMethod'
// 'paymentDates/firstPaymentDate?'
// 'paymentDates/lastRegularPaymentDate?'
// ignored elements:
// 'paymentDates/calculationPeriodDatesReference'
// 'paymentDates/resetDatesReference'
// 'paymentDates/valuationDatesReference'
PaymentSchedule.Builder paymentScheduleBuilder = PaymentSchedule.builder();
// payment dates
XmlElement paymentDatesEl = legEl.getChild("paymentDates");
// frequency
paymentScheduleBuilder.paymentFrequency(document.parseFrequency(
paymentDatesEl.getChild("paymentFrequency")));
//default for IRS is pay relative to period end; Strata model will apply the defaulting but the values is needed
//here for first and last payment date checks
PaymentRelativeTo payRelativeTo = paymentDatesEl.findChild("payRelativeTo")
.map(el -> parsePayRelativeTo(el))
.orElse(PaymentRelativeTo.PERIOD_END);
paymentScheduleBuilder.paymentRelativeTo(payRelativeTo);
// dates
if (payRelativeTo == PaymentRelativeTo.PERIOD_END) {
// ignore data if not PeriodEnd and hope schedule is worked out correctly by other means
// this provides compatibility for old code that ignored these FpML fields
paymentDatesEl.findChild("firstPaymentDate")
.map(el -> document.parseDate(el))
.ifPresent(date -> paymentScheduleBuilder.firstRegularStartDate(date));
paymentDatesEl.findChild("lastRegularPaymentDate")
.map(el -> document.parseDate(el))
.ifPresent(date -> paymentScheduleBuilder.lastRegularEndDate(date));
}
// offset
Optional paymentOffsetEl = paymentDatesEl.findChild("paymentDaysOffset");
BusinessDayAdjustment payAdjustment = document.parseBusinessDayAdjustments(
paymentDatesEl.getChild("paymentDatesAdjustments"));
if (paymentOffsetEl.isPresent()) {
Period period = document.parsePeriod(paymentOffsetEl.get());
if (period.toTotalMonths() != 0) {
throw new FpmlParseException("Invalid 'paymentDatesAdjustments' value '{value}', expected days-based period", period);
}
Optional dayTypeEl = paymentOffsetEl.get().findChild("dayType");
boolean fixingCalendarDays = period.isZero() ||
(dayTypeEl.isPresent() && dayTypeEl.get().getContent().equals("Calendar"));
if (fixingCalendarDays) {
paymentScheduleBuilder.paymentDateOffset(DaysAdjustment.ofCalendarDays(period.getDays(), payAdjustment));
} else {
paymentScheduleBuilder.paymentDateOffset(DaysAdjustment.ofBusinessDays(period.getDays(), payAdjustment.getCalendar()));
}
} else {
paymentScheduleBuilder.paymentDateOffset(DaysAdjustment.ofCalendarDays(0, payAdjustment));
}
// compounding
calcEl.findChild("compoundingMethod").ifPresent(compoundingEl -> {
paymentScheduleBuilder.compoundingMethod(CompoundingMethod.of(compoundingEl.getContent()));
});
return paymentScheduleBuilder.build();
}
// parses the notional schedule
private NotionalSchedule parseSwapNotionalSchedule(XmlElement legEl, XmlElement calcEl, FpmlDocument document) {
// supported elements:
// 'principalExchanges/initialExchange'
// 'principalExchanges/finalExchange'
// 'principalExchanges/intermediateExchange'
// 'calculationPeriodAmount/calculation/notionalSchedule/notionalStepSchedule'
// 'calculationPeriodAmount/calculation/notionalSchedule/notionalStepParameters'
NotionalSchedule.Builder notionalScheduleBuilder = NotionalSchedule.builder();
// exchanges
legEl.findChild("principalExchanges").ifPresent(el -> {
notionalScheduleBuilder.initialExchange(Boolean.parseBoolean(el.getChild("initialExchange").getContent()));
notionalScheduleBuilder.intermediateExchange(
Boolean.parseBoolean(el.getChild("intermediateExchange").getContent()));
notionalScheduleBuilder.finalExchange(Boolean.parseBoolean(el.getChild("finalExchange").getContent()));
});
// notional schedule
XmlElement notionalEl = calcEl.getChild("notionalSchedule");
XmlElement stepScheduleEl = notionalEl.getChild("notionalStepSchedule");
Optional paramScheduleElOpt = notionalEl.findChild("notionalStepParameters");
double initialValue = document.parseDecimal(stepScheduleEl.getChild("initialValue"));
ValueStepSequence seq = paramScheduleElOpt.map(el -> parseAmountSchedule(el, initialValue, document)).orElse(null);
notionalScheduleBuilder.amount(parseSchedule(stepScheduleEl, initialValue, seq, document));
notionalScheduleBuilder.currency(document.parseCurrency(stepScheduleEl.getChild("currency")));
return notionalScheduleBuilder.build();
}
// parse swap rate calculation
private RateCalculation parseSwapCalculation(
XmlElement legEl,
XmlElement calcEl,
PeriodicSchedule accrualSchedule,
FpmlDocument document) {
// supported elements:
// 'calculationPeriodAmount/calculation/fixedRateSchedule'
// 'calculationPeriodAmount/calculation/floatingRateCalculation'
// 'calculationPeriodAmount/calculation/inflationRateCalculation'
Optional fixedOptEl = calcEl.findChild("fixedRateSchedule");
Optional floatingOptEl = calcEl.findChild("floatingRateCalculation");
Optional inflationOptEl = calcEl.findChild("inflationRateCalculation");
if (fixedOptEl.isPresent()) {
return parseFixed(legEl, calcEl, fixedOptEl.get(), document);
} else if (floatingOptEl.isPresent()) {
return parseFloat(legEl, calcEl, floatingOptEl.get(), accrualSchedule, document);
} else if (inflationOptEl.isPresent()) {
return parseInflation(legEl, calcEl, inflationOptEl.get(), accrualSchedule, document);
} else {
throw new FpmlParseException(
"Invalid 'calculation' type, not fixedRateSchedule, floatingRateCalculation or inflationRateCalculation");
}
}
// Converts an FpML 'fixedRateSchedule' to a {@code RateCalculation}.
private RateCalculation parseFixed(XmlElement legEl, XmlElement calcEl, XmlElement fixedEl, FpmlDocument document) {
// supported elements:
// 'calculationPeriodAmount/calculation/futureValueNotional'
// 'calculationPeriodAmount/calculation/fixedRateSchedule'
// 'calculationPeriodAmount/calculation/dayCountFraction'
// 'stubCalculationPeriodAmount'
// rejected elements:
// 'resetDates'
// 'stubCalculationPeriodAmount/initialStub/floatingRate'
// 'stubCalculationPeriodAmount/finalStub/floatingRate'
document.validateNotPresent(legEl, "resetDates");
FixedRateCalculation.Builder fixedRateBuilder = FixedRateCalculation.builder();
calcEl.findChild("futureValueNotional").ifPresent(fvnEl -> {
FutureValueNotional notional = FutureValueNotional.builder()
.value(document.parseDecimal(fvnEl.getChild("amount")))
.dayCountDays(fvnEl.findChild("calculationPeriodNumberOfDays")
.map(str -> (int) document.parseDecimal(str))
.orElse(null))
.valueDate(fvnEl.findChild("valueDate").map(str -> document.parseDate(str)).orElse(null))
.build();
fixedRateBuilder.futureValueNotional(notional);
});
fixedRateBuilder.rate(parseSchedule(fixedEl, document));
fixedRateBuilder.dayCount(document.parseDayCountFraction(calcEl.getChild("dayCountFraction")));
// stub
legEl.findChild("stubCalculationPeriodAmount").ifPresent(stubsEl -> {
stubsEl.findChild("initialStub").ifPresent(el -> {
fixedRateBuilder.initialStub(parseStubCalculationForFixed(el, document));
});
stubsEl.findChild("finalStub").ifPresent(el -> {
fixedRateBuilder.finalStub(parseStubCalculationForFixed(el, document));
});
});
return fixedRateBuilder.build();
}
// Converts an FpML 'FloatingRateCalculation' to a {@code RateCalculation}.
private RateCalculation parseFloat(
XmlElement legEl,
XmlElement calcEl,
XmlElement floatingEl,
PeriodicSchedule accrualSchedule,
FpmlDocument document) {
// supported elements:
// 'calculationPeriodAmount/calculation/floatingRateCalculation'
// 'calculationPeriodAmount/calculation/floatingRateCalculation/floatingRateIndex'
// 'calculationPeriodAmount/calculation/floatingRateCalculation/indexTenor?'
// 'calculationPeriodAmount/calculation/floatingRateCalculation/floatingRateMultiplierSchedule?'
// 'calculationPeriodAmount/calculation/floatingRateCalculation/spreadSchedule*'
// 'calculationPeriodAmount/calculation/floatingRateCalculation/initialRate?' (Ibor only)
// 'calculationPeriodAmount/calculation/floatingRateCalculation/averagingMethod?'
// 'calculationPeriodAmount/calculation/floatingRateCalculation/negativeInterestRateTreatment?'
// 'calculationPeriodAmount/calculation/dayCountFraction'
// 'resetDates/resetRelativeTo'
// 'resetDates/fixingDates'
// 'resetDates/rateCutOffDaysOffset' (OIS only)
// 'resetDates/resetFrequency'
// 'resetDates/resetDatesAdjustments'
// 'stubCalculationPeriodAmount/initalStub' (Ibor only, Overnight must match index)
// 'stubCalculationPeriodAmount/finalStub' (Ibor only, Overnight must match index)
// ignored elements:
// 'calculationPeriodAmount/calculation/floatingRateCalculation/finalRateRounding?'
// 'calculationPeriodAmount/calculation/discounting?'
// 'resetDates/calculationPeriodDatesReference'
// rejected elements:
// 'calculationPeriodAmount/calculation/floatingRateCalculation/spreadSchedule/type?'
// 'calculationPeriodAmount/calculation/floatingRateCalculation/rateTreatment?'
// 'calculationPeriodAmount/calculation/floatingRateCalculation/capRateSchedule?'
// 'calculationPeriodAmount/calculation/floatingRateCalculation/floorRateSchedule?'
// 'resetDates/initialFixingDate'
document.validateNotPresent(floatingEl, "rateTreatment");
document.validateNotPresent(floatingEl, "capRateSchedule");
document.validateNotPresent(floatingEl, "floorRateSchedule");
Index index = document.parseIndex(floatingEl);
if (index instanceof IborIndex) {
IborRateCalculation.Builder iborRateBuilder = IborRateCalculation.builder();
// day count
iborRateBuilder.dayCount(document.parseDayCountFraction(calcEl.getChild("dayCountFraction")));
// index
iborRateBuilder.index((IborIndex) document.parseIndex(floatingEl));
// gearing
floatingEl.findChild("floatingRateMultiplierSchedule").ifPresent(el -> {
iborRateBuilder.gearing(parseSchedule(el, document));
});
// spread
if (floatingEl.getChildren("spreadSchedule").size() > 1) {
throw new FpmlParseException("Only one 'spreadSchedule' is supported");
}
floatingEl.findChild("spreadSchedule").ifPresent(el -> {
document.validateNotPresent(el, "type");
iborRateBuilder.spread(parseSchedule(el, document));
});
// initial fixed rate
floatingEl.findChild("initialRate").ifPresent(el -> {
iborRateBuilder.firstRegularRate(document.parseDecimal(el));
});
// negative rates
floatingEl.findChild("negativeInterestRateTreatment").ifPresent(el -> {
iborRateBuilder.negativeRateMethod(parseNegativeInterestRateTreatment(el));
});
// resets
legEl.findChild("resetDates").ifPresent(resetDatesEl -> {
document.validateNotPresent(resetDatesEl, "initialFixingDate");
document.validateNotPresent(resetDatesEl, "rateCutOffDaysOffset");
resetDatesEl.findChild("resetRelativeTo").ifPresent(el -> {
iborRateBuilder.fixingRelativeTo(parseResetRelativeTo(el));
});
// fixing date offset
iborRateBuilder.fixingDateOffset(document.parseRelativeDateOffsetDays(resetDatesEl.getChild("fixingDates")));
Frequency resetFreq = document.parseFrequency(resetDatesEl.getChild("resetFrequency"));
if (!accrualSchedule.getFrequency().equals(resetFreq)) {
ResetSchedule.Builder resetScheduleBuilder = ResetSchedule.builder();
resetScheduleBuilder.resetFrequency(resetFreq);
floatingEl.findChild("averagingMethod").ifPresent(el -> {
resetScheduleBuilder.resetMethod(parseAveragingMethod(el));
});
resetScheduleBuilder.businessDayAdjustment(
document.parseBusinessDayAdjustments(resetDatesEl.getChild("resetDatesAdjustments")));
iborRateBuilder.resetPeriods(resetScheduleBuilder.build());
}
});
// stubs
legEl.findChild("stubCalculationPeriodAmount").ifPresent(stubsEl -> {
stubsEl.findChild("initialStub").ifPresent(el -> {
iborRateBuilder.initialStub(parseStubCalculation(el, document));
});
stubsEl.findChild("finalStub").ifPresent(el -> {
iborRateBuilder.finalStub(parseStubCalculation(el, document));
});
});
return iborRateBuilder.build();
} else if (index instanceof OvernightIndex) {
OvernightRateCalculation.Builder overnightRateBuilder = OvernightRateCalculation.builder();
document.validateNotPresent(floatingEl, "initialRate"); // TODO: should support this in the model
// stubs
legEl.findChild("stubCalculationPeriodAmount").ifPresent(stubsEl -> {
stubsEl.findChild("initialStub").ifPresent(el -> {
checkStubForOvernightIndex(el, document, (OvernightIndex) index);
});
stubsEl.findChild("finalStub").ifPresent(el -> {
checkStubForOvernightIndex(el, document, (OvernightIndex) index);
});
});
// day count
overnightRateBuilder.dayCount(document.parseDayCountFraction(calcEl.getChild("dayCountFraction")));
// index
overnightRateBuilder.index((OvernightIndex) document.parseIndex(floatingEl));
// accrual method
FloatingRateName idx = FloatingRateName.of(floatingEl.getChild("floatingRateIndex").getContent());
if (idx.getType() == FloatingRateType.OVERNIGHT_COMPOUNDED) {
overnightRateBuilder.accrualMethod(OvernightAccrualMethod.COMPOUNDED);
}
// gearing
floatingEl.findChild("floatingRateMultiplierSchedule").ifPresent(el -> {
overnightRateBuilder.gearing(parseSchedule(el, document));
});
// spread
if (floatingEl.getChildren("spreadSchedule").size() > 1) {
throw new FpmlParseException("Only one 'spreadSchedule' is supported");
}
floatingEl.findChild("spreadSchedule").ifPresent(el -> {
document.validateNotPresent(el, "type");
overnightRateBuilder.spread(parseSchedule(el, document));
});
// negative rates
floatingEl.findChild("negativeInterestRateTreatment").ifPresent(el -> {
overnightRateBuilder.negativeRateMethod(parseNegativeInterestRateTreatment(el));
});
// rate cut off
legEl.findChild("resetDates").ifPresent(resetDatesEl -> {
document.validateNotPresent(resetDatesEl, "initialFixingDate");
resetDatesEl.findChild("rateCutOffDaysOffset").ifPresent(el -> {
Period cutOff = document.parsePeriod(el);
if (cutOff.toTotalMonths() != 0) {
throw new FpmlParseException("Invalid 'rateCutOffDaysOffset' value '{value}', expected days-based period", cutOff);
}
overnightRateBuilder.rateCutOffDays(-cutOff.getDays());
});
});
return overnightRateBuilder.build();
} else {
throw new FpmlParseException("Invalid 'floatingRateIndex' type, not Ibor or Overnight");
}
}
// Converts an FpML 'InflationRateCalculation' to a {@code RateCalculation}.
private RateCalculation parseInflation(
XmlElement legEl,
XmlElement calcEl,
XmlElement inflationEl,
PeriodicSchedule accrualSchedule,
FpmlDocument document) {
// supported elements:
// 'calculationPeriodAmount/calculation/inflationRateCalculation'
// 'calculationPeriodAmount/calculation/inflationRateCalculation/floatingRateIndex'
// 'calculationPeriodAmount/calculation/inflationRateCalculation/indexTenor?'
// 'calculationPeriodAmount/calculation/inflationRateCalculation/floatingRateMultiplierSchedule?'
// 'calculationPeriodAmount/calculation/inflationRateCalculation/inflationLag'
// 'calculationPeriodAmount/calculation/inflationRateCalculation/interpolationMethod'
// 'calculationPeriodAmount/calculation/inflationRateCalculation/initialIndexLevel?'
// 'calculationPeriodAmount/calculation/dayCountFraction'
// ignored elements:
// 'calculationPeriodAmount/calculation/inflationRateCalculation/indexSource'
// 'calculationPeriodAmount/calculation/inflationRateCalculation/mainPublication'
// 'calculationPeriodAmount/calculation/inflationRateCalculation/fallbackBondApplicable'
// 'calculationPeriodAmount/calculation/floatingRateCalculation/initialRate?'
// 'calculationPeriodAmount/calculation/floatingRateCalculation/finalRateRounding?'
// 'calculationPeriodAmount/calculation/floatingRateCalculation/averagingMethod?'
// 'calculationPeriodAmount/calculation/floatingRateCalculation/negativeInterestRateTreatment?'
// 'resetDates'
// rejected elements:
// 'calculationPeriodAmount/calculation/floatingRateCalculation/spreadSchedule*'
// 'calculationPeriodAmount/calculation/floatingRateCalculation/rateTreatment?'
// 'calculationPeriodAmount/calculation/floatingRateCalculation/capRateSchedule?'
// 'calculationPeriodAmount/calculation/floatingRateCalculation/floorRateSchedule?'
// 'stubCalculationPeriodAmount'
document.validateNotPresent(inflationEl, "spreadSchedule");
document.validateNotPresent(inflationEl, "rateTreatment");
document.validateNotPresent(inflationEl, "capRateSchedule");
document.validateNotPresent(inflationEl, "floorRateSchedule");
document.validateNotPresent(legEl, "stubCalculationPeriodAmount"); // TODO: parse fixed stub rate
InflationRateCalculation.Builder builder = InflationRateCalculation.builder();
// index
builder.index(document.parsePriceIndex(inflationEl));
// lag
builder.lag(document.parsePeriod(inflationEl.getChild("inflationLag")));
// interpolation
String interpStr = inflationEl.getChild("interpolationMethod").getContent();
builder.indexCalculationMethod(interpStr.toLowerCase(Locale.ENGLISH).contains("linear") ?
PriceIndexCalculationMethod.INTERPOLATED :
PriceIndexCalculationMethod.MONTHLY);
// initial index
inflationEl.findChild("initialIndexLevel").ifPresent(el -> {
builder.firstIndexValue(document.parseDecimal(el));
});
// gearing
inflationEl.findChild("floatingRateMultiplierSchedule").ifPresent(el -> {
builder.gearing(parseSchedule(el, document));
});
return builder.build();
}
//-------------------------------------------------------------------------
// Converts an FpML 'StubValue' to a {@code FixedRateStubCalculation}.
private FixedRateStubCalculation parseStubCalculationForFixed(XmlElement baseEl, FpmlDocument document) {
Optional rateOptEl = baseEl.findChild("stubRate");
if (rateOptEl.isPresent()) {
return FixedRateStubCalculation.ofFixedRate(document.parseDecimal(rateOptEl.get()));
}
Optional amountOptEl = baseEl.findChild("stubAmount");
if (amountOptEl.isPresent()) {
return FixedRateStubCalculation.ofKnownAmount(document.parseCurrencyAmount(amountOptEl.get()));
}
throw new FpmlParseException("Invalid stub, fixed rate leg cannot have a floating rate stub");
}
// Converts an FpML 'StubValue' to a {@code IborRateStubCalculation}.
private IborRateStubCalculation parseStubCalculation(XmlElement baseEl, FpmlDocument document) {
Optional rateOptEl = baseEl.findChild("stubRate");
if (rateOptEl.isPresent()) {
return IborRateStubCalculation.ofFixedRate(document.parseDecimal(rateOptEl.get()));
}
Optional amountOptEl = baseEl.findChild("stubAmount");
if (amountOptEl.isPresent()) {
return IborRateStubCalculation.ofKnownAmount(document.parseCurrencyAmount(amountOptEl.get()));
}
List indicesEls = baseEl.getChildren("floatingRate");
if (indicesEls.size() == 1) {
XmlElement indexEl = indicesEls.get(0);
document.validateNotPresent(indexEl, "floatingRateMultiplierSchedule");
document.validateNotPresent(indexEl, "spreadSchedule");
document.validateNotPresent(indexEl, "rateTreatment");
document.validateNotPresent(indexEl, "capRateSchedule");
document.validateNotPresent(indexEl, "floorRateSchedule");
return IborRateStubCalculation.ofIborRate((IborIndex) document.parseIndex(indexEl));
} else if (indicesEls.size() == 2) {
XmlElement index1El = indicesEls.get(0);
document.validateNotPresent(index1El, "floatingRateMultiplierSchedule");
document.validateNotPresent(index1El, "spreadSchedule");
document.validateNotPresent(index1El, "rateTreatment");
document.validateNotPresent(index1El, "capRateSchedule");
document.validateNotPresent(index1El, "floorRateSchedule");
XmlElement index2El = indicesEls.get(1);
document.validateNotPresent(index2El, "floatingRateMultiplierSchedule");
document.validateNotPresent(index2El, "spreadSchedule");
document.validateNotPresent(index2El, "rateTreatment");
document.validateNotPresent(index2El, "capRateSchedule");
document.validateNotPresent(index2El, "floorRateSchedule");
return IborRateStubCalculation.ofIborInterpolatedRate(
(IborIndex) document.parseIndex(index1El),
(IborIndex) document.parseIndex(index2El));
}
throw new FpmlParseException("Unknown stub structure: " + baseEl);
}
// checks that the index on a stub matches the main index (this is handling bad FpML)
private void checkStubForOvernightIndex(XmlElement baseEl, FpmlDocument document, OvernightIndex index) {
document.validateNotPresent(baseEl, "stubAmount");
document.validateNotPresent(baseEl, "stubRate");
List indicesEls = baseEl.getChildren("floatingRate");
if (indicesEls.size() == 1) {
XmlElement indexEl = indicesEls.get(0);
document.validateNotPresent(indexEl, "floatingRateMultiplierSchedule");
document.validateNotPresent(indexEl, "spreadSchedule");
document.validateNotPresent(indexEl, "rateTreatment");
document.validateNotPresent(indexEl, "capRateSchedule");
document.validateNotPresent(indexEl, "floorRateSchedule");
Index parsed = document.parseIndex(indexEl);
if (parsed.equals(index)) {
return;
}
throw new FpmlParseException("OvernightIndex swap cannot have a different index in the stub: " + baseEl);
}
throw new FpmlParseException("Unknown stub structure: " + baseEl);
}
//-------------------------------------------------------------------------
// Converts an FpML 'StubPeriodTypeEnum' to a {@code StubConvention}.
private StubConvention parseStubConvention(XmlElement baseEl, FpmlDocument document) {
String str = baseEl.getContent();
if (str.equals("ShortInitial")) {
return StubConvention.SHORT_INITIAL;
} else if (str.equals("ShortFinal")) {
return StubConvention.SHORT_FINAL;
} else if (str.equals("LongInitial")) {
return StubConvention.LONG_INITIAL;
} else if (str.equals("LongFinal")) {
return StubConvention.LONG_FINAL;
} else {
throw new FpmlParseException(
"Unknown 'stubPeriodType' value '{value}', expected 'ShortInitial', 'ShortFinal', 'LongInitial' or 'LongFinal'", str);
}
}
// Converts an FpML 'Schedule' to a {@code ValueSchedule}.
private ValueSchedule parseSchedule(XmlElement scheduleEl, FpmlDocument document) {
// FpML content: ('initialValue', 'step*')
// FpML 'step' content: ('stepDate', 'stepValue')
double initialValue = document.parseDecimal(scheduleEl.getChild("initialValue"));
return parseSchedule(scheduleEl, initialValue, null, document);
}
// Converts an FpML 'Schedule' to a {@code ValueSchedule}.
private ValueSchedule parseSchedule(XmlElement scheduleEl, double initialValue, ValueStepSequence seq, FpmlDocument document) {
List stepEls = scheduleEl.getChildren("step");
ImmutableList.Builder stepBuilder = ImmutableList.builder();
for (XmlElement stepEl : stepEls) {
LocalDate stepDate = document.parseDate(stepEl.getChild("stepDate"));
double stepValue = document.parseDecimal(stepEl.getChild("stepValue"));
stepBuilder.add(ValueStep.of(stepDate, ValueAdjustment.ofReplace(stepValue)));
}
return ValueSchedule.builder().initialValue(initialValue).steps(stepBuilder.build()).stepSequence(seq).build();
}
// Converts an FpML 'NonNegativeAmountSchedule' to a {@code ValueStepSequence}.
private ValueStepSequence parseAmountSchedule(XmlElement scheduleEl, double initialValue, FpmlDocument document) {
Frequency freq = document.parseFrequency(scheduleEl.getChild("stepFrequency"));
LocalDate start = document.parseDate(scheduleEl.getChild("firstNotionalStepDate"));
LocalDate end = document.parseDate(scheduleEl.getChild("lastNotionalStepDate"));
Optional amountElOpt = scheduleEl.findChild("notionalStepAmount");
if (amountElOpt.isPresent()) {
double amount = document.parseDecimal(amountElOpt.get());
return ValueStepSequence.of(start, end, freq, ValueAdjustment.ofDeltaAmount(amount));
}
double rate = document.parseDecimal(scheduleEl.getChild("notionalStepRate"));
String relativeTo = scheduleEl.findChild("stepRelativeTo").map(el -> el.getContent()).orElse("Previous");
if (relativeTo.equals("Previous")) {
return ValueStepSequence.of(start, end, freq, ValueAdjustment.ofDeltaMultiplier(rate));
} else if (relativeTo.equals("Initial")) {
// data model does not support 'relative to initial' but can calculate amount here
double amount = initialValue * rate;
return ValueStepSequence.of(start, end, freq, ValueAdjustment.ofDeltaAmount(amount));
} else {
throw new FpmlParseException(
"Unknown 'stepRelativeTo' value '{value}', expected 'Initial' or 'Previous'", relativeTo);
}
}
//-------------------------------------------------------------------------
// Converts an FpML 'PayRelativeToEnum' to a {@code PaymentRelativeTo}.
private PaymentRelativeTo parsePayRelativeTo(XmlElement baseEl) {
String str = baseEl.getContent();
if (str.equals("CalculationPeriodStartDate")) {
return PaymentRelativeTo.PERIOD_START;
} else if (str.equals("CalculationPeriodEndDate")) {
return PaymentRelativeTo.PERIOD_END;
} else {
throw new FpmlParseException(
"Unknown 'payRelativeTo' value '{value}', expected 'CalculationPeriodStartDate' or 'CalculationPeriodEndDate'", str);
}
}
// Converts and FpML 'NegativeInterestRateTreatmentEnum' to a {@code NegativeRateMethod}.
private NegativeRateMethod parseNegativeInterestRateTreatment(XmlElement baseEl) {
String str = baseEl.getContent();
if (str.equals("NegativeInterestRateMethod")) {
return NegativeRateMethod.ALLOW_NEGATIVE;
} else if (str.equals("ZeroInterestRateMethod")) {
return NegativeRateMethod.NOT_NEGATIVE;
} else {
throw new FpmlParseException(
"Unknown 'negativeInterestRateTreatment' value '{value}', expected 'NegativeInterestRateMethod' or 'ZeroInterestRateMethod'",
str);
}
}
// Converts an FpML 'AveragingMethodEnum' to a {@code IborRateResetMethod}.
private IborRateResetMethod parseAveragingMethod(XmlElement baseEl) {
String str = baseEl.getContent();
if (str.equals("Unweighted")) {
return IborRateResetMethod.UNWEIGHTED;
} else if (str.equals("Weighted")) {
return IborRateResetMethod.WEIGHTED;
} else {
throw new FpmlParseException(
"Unknown 'resetMethod' value '{value}', expected 'Unweighted' or 'Weighted'", str);
}
}
// Converts an FpML 'ResetRelativeToEnum' to a {@code FixingRelativeTo}.
private FixingRelativeTo parseResetRelativeTo(XmlElement baseEl) {
String str = baseEl.getContent();
if (str.equals("CalculationPeriodStartDate")) {
return FixingRelativeTo.PERIOD_START;
} else if (str.equals("CalculationPeriodEndDate")) {
return FixingRelativeTo.PERIOD_END;
} else {
throw new FpmlParseException(
"Unknown 'resetRelativeTo' value '{value}', expected 'CalculationPeriodStartDate' or 'CalculationPeriodEndDate'", str);
}
}
//-------------------------------------------------------------------------
@Override
public String getName() {
return "swap";
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy