// 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 <XmlElement> amountElOpt = scheduleEl.findChild("notionalStepAmount"); if (amountElOpt.Present) { 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.Content).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(Messages.format("Unknown 'stepRelativeTo' value '{}', expected 'Initial' or 'Previous'", relativeTo)); } }
// 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(bool.Parse(el.getChild("initialExchange").Content)); notionalScheduleBuilder.intermediateExchange(bool.Parse(el.getChild("intermediateExchange").Content)); notionalScheduleBuilder.finalExchange(bool.Parse(el.getChild("finalExchange").Content)); }); // notional schedule XmlElement notionalEl = calcEl.getChild("notionalSchedule"); XmlElement stepScheduleEl = notionalEl.getChild("notionalStepSchedule"); Optional <XmlElement> 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()); }
// 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 '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").Content; builder.indexCalculationMethod(interpStr.ToLower(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 'Schedule' to a {@code ValueSchedule}. private ValueSchedule parseSchedule(XmlElement scheduleEl, double initialValue, ValueStepSequence seq, FpmlDocument document) { IList <XmlElement> stepEls = scheduleEl.getChildren("step"); ImmutableList.Builder <ValueStep> stepBuilder = ImmutableList.builder(); foreach (XmlElement stepEl in 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 'StubValue' to a {@code FixedRateStubCalculation}. private FixedRateStubCalculation parseStubCalculationForFixed(XmlElement baseEl, FpmlDocument document) { Optional <XmlElement> rateOptEl = baseEl.findChild("stubRate"); if (rateOptEl.Present) { return(FixedRateStubCalculation.ofFixedRate(document.parseDecimal(rateOptEl.get()))); } Optional <XmlElement> amountOptEl = baseEl.findChild("stubAmount"); if (amountOptEl.Present) { 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 <XmlElement> rateOptEl = baseEl.findChild("stubRate"); if (rateOptEl.Present) { return(IborRateStubCalculation.ofFixedRate(document.parseDecimal(rateOptEl.get()))); } Optional <XmlElement> amountOptEl = baseEl.findChild("stubAmount"); if (amountOptEl.Present) { return(IborRateStubCalculation.ofKnownAmount(document.parseCurrencyAmount(amountOptEl.get()))); } IList <XmlElement> indicesEls = baseEl.getChildren("floatingRate"); if (indicesEls.Count == 1) { XmlElement indexEl = indicesEls[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.Count == 2) { XmlElement index1El = indicesEls[0]; document.validateNotPresent(index1El, "floatingRateMultiplierSchedule"); document.validateNotPresent(index1El, "spreadSchedule"); document.validateNotPresent(index1El, "rateTreatment"); document.validateNotPresent(index1El, "capRateSchedule"); document.validateNotPresent(index1El, "floorRateSchedule"); XmlElement index2El = indicesEls[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); }
// 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 is 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)); }); iborRateBuilder.fixingDateOffset(document.parseRelativeDateOffsetDays(resetDatesEl.getChild("fixingDates"))); Frequency resetFreq = document.parseFrequency(resetDatesEl.getChild("resetFrequency")); if (!accrualSchedule.Frequency.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 is 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").Content); if (idx.Type == 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, expected days-based period: " + cutOff); } overnightRateBuilder.rateCutOffDays(-cutOff.Days); }); }); return(overnightRateBuilder.build()); } else { throw new FpmlParseException("Invalid 'floatingRateIndex' type, not Ibor or Overnight"); } }
//------------------------------------------------------------------------- public Trade parseTrade(FpmlDocument document, XmlElement tradeEl) { // supported elements: // 'buyerPartyReference' // 'sellerPartyReference' // 'adjustedTerminationDate' // 'paymentDate' // 'fixingDateOffset' // 'dayCountFraction' // 'notional' // 'fixedRate' // 'floatingRateIndex' // 'indexTenor+' // 'fraDiscounting' // ignored elements: // 'Product.model?' // 'buyerAccountReference?' // 'sellerAccountReference?' // 'calculationPeriodNumberOfDays' // 'additionalPayment*' TradeInfoBuilder tradeInfoBuilder = document.parseTradeInfo(tradeEl); XmlElement fraEl = tradeEl.getChild("fra"); Fra.Builder fraBuilder = Fra.builder(); // buy/sell and counterparty fraBuilder.buySell(document.parseBuyerSeller(fraEl, tradeInfoBuilder)); // start date fraBuilder.startDate(document.parseDate(fraEl.getChild("adjustedEffectiveDate"))); // end date fraBuilder.endDate(document.parseDate(fraEl.getChild("adjustedTerminationDate"))); // payment date fraBuilder.paymentDate(document.parseAdjustableDate(fraEl.getChild("paymentDate"))); // fixing offset fraBuilder.fixingDateOffset(document.parseRelativeDateOffsetDays(fraEl.getChild("fixingDateOffset"))); // dateRelativeTo required to refer to adjustedEffectiveDate, so ignored here // day count fraBuilder.dayCount(document.parseDayCountFraction(fraEl.getChild("dayCountFraction"))); // notional CurrencyAmount notional = document.parseCurrencyAmount(fraEl.getChild("notional")); fraBuilder.currency(notional.Currency); fraBuilder.notional(notional.Amount); // fixed rate fraBuilder.fixedRate(document.parseDecimal(fraEl.getChild("fixedRate"))); // index IList <Index> indexes = document.parseIndexes(fraEl); switch (indexes.Count) { case 1: fraBuilder.index((IborIndex)indexes[0]); break; case 2: fraBuilder.index((IborIndex)indexes[0]); fraBuilder.indexInterpolated((IborIndex)indexes[1]); break; default: throw new FpmlParseException("Expected one or two indexes, but found " + indexes.Count); } // discounting fraBuilder.discounting(FraDiscountingMethod.of(fraEl.getChild("fraDiscounting").Content)); return(FraTrade.builder().info(tradeInfoBuilder.build()).product(fraBuilder.build()).build()); }