586 {
587
589 using AD = CommodityFutureConvention::AveragingData;
590 PST type = priceSegment.type();
591
592
593 QuantLib::ext::shared_ptr<Conventions> conventions = InstrumentConventions::instance().conventions();
594 QuantLib::ext::shared_ptr<CommodityFutureConvention> convention;
595 AD ad;
596 QuantLib::ext::shared_ptr<CommodityIndex> index;
597 QuantLib::ext::shared_ptr<FutureExpiryCalculator> uFec;
598 if (type == PST::AveragingFuture || type == PST::AveragingSpot || type == PST::AveragingOffPeakPower) {
599
600
601 convention = QuantLib::ext::dynamic_pointer_cast<CommodityFutureConvention>(
602 conventions->get(priceSegment.conventionsId()));
603 QL_REQUIRE(convention, "Convention " << priceSegment.conventionsId() <<
604 " not of expected type CommodityFutureConvention.");
605
606 ad = convention->averagingData();
607 QL_REQUIRE(!ad.empty(), "CommodityCurve: convention " << convention->id() <<
608 " should have non-empty averaging data for piecewise price curve construction.");
609
610
612
613
614 if (type == PST::AveragingFuture || type == PST::AveragingOffPeakPower) {
615
616 auto uConvention = QuantLib::ext::dynamic_pointer_cast<CommodityFutureConvention>(
617 conventions->get(ad.conventionsId()));
618 QL_REQUIRE(uConvention, "Convention " << priceSegment.conventionsId() <<
619 " not of expected type CommodityFutureConvention.");
620 uFec = QuantLib::ext::make_shared<ConventionsBasedFutureExpiry>(*uConvention);
621
622 if (ad.dailyExpiryOffset() != Null<Natural>() && ad.dailyExpiryOffset() > 0) {
623 QL_REQUIRE(uConvention->contractFrequency() == Daily, "CommodityCurve: the averaging data has" <<
624 " a positive DailyExpiryOffset (" << ad.dailyExpiryOffset() << ") but the underlying future" <<
625 " contract frequency is not daily (" << uConvention->contractFrequency() << ").");
626 }
627 }
628
629 }
630
631
632 QuantLib::ext::shared_ptr<CommodityIndex> peakIndex;
633 Natural peakHoursPerDay = 16;
634 Calendar peakCalendar;
635 if (type == PST::AveragingOffPeakPower) {
636
637
638 const string& ppId = priceSegment.peakPriceCurveId();
639 QL_REQUIRE(!ppId.empty(), "CommodityCurve: AveragingOffPeakPower segment in " <<
640 " curve configuration " << configId << " does not provide a peak price curve ID.");
641 CommodityCurveSpec ccSpec(currency, ppId);
642 DLOG(
"Looking for peak price curve with id, " << ppId <<
", and spec, " << ccSpec <<
".");
643 auto itCc = commodityCurves.find(ccSpec.name());
644 QL_REQUIRE(itCc != commodityCurves.end(), "Can't find peak price curve with id " << ppId);
645 auto peakPts = Handle<PriceTermStructure>(itCc->second->commodityPriceCurve());
646
647
649
650
651 peakCalendar =
parseCalendar(priceSegment.peakPriceCalendar());
652
653
654 if (conventions->has(ppId)) {
655 auto peakConvention = QuantLib::ext::dynamic_pointer_cast<CommodityFutureConvention>(conventions->get(ppId));
656 if (peakConvention && peakConvention->hoursPerDay() != Null<Natural>()) {
657 peakHoursPerDay = peakConvention->hoursPerDay();
658 }
659 }
660 }
661
662
663 auto quotes =
getQuotes(asof, configId, priceSegment.quotes(), loader,
true);
664
665
666 for (const auto& quote : quotes) {
667
668 const Date& expiry = quote->expiryDate();
669 switch (type) {
670 case PST::Future:
671 if (expiry == asof) {
672 TLOG(
"Quote " << quote->name() <<
" has expiry date " << io::iso_date(expiry) <<
" equal to asof" <<
673 " so not adding to instruments. Attempt to add as fixing instead.");
674 addMarketFixing(priceSegment.conventionsId(), expiry, quote->quote()->value());
675 } else if (instruments.count(expiry) == 0) {
676 instruments[expiry] = QuantLib::ext::make_shared<FuturePriceHelper>(quote->quote(), expiry);
677 } else {
678 TLOG(
"Skipping quote, " << quote->name() <<
", because its expiry date, " <<
679 io::iso_date(expiry) << ", is already in the instrument set.");
680 }
681 break;
682
683
684 case PST::AveragingFuture:
685 case PST::AveragingSpot:
686 case PST::AveragingOffPeakPower: {
687
688
689 using ADCP = AD::CalculationPeriod;
690 Date start;
691 Date end;
692 if (ad.period() == ADCP::ExpiryToExpiry) {
693
694 auto fec = QuantLib::ext::make_shared<ConventionsBasedFutureExpiry>(*convention);
695 end = fec->nextExpiry(true, expiry);
696 if (end != expiry) {
697 WLOG(
"Calculated expiry date, " << io::iso_date(end) <<
", does not equal quote's expiry date "
698 << io::iso_date(expiry) << ". Proceed with quote's expiry.");
699 }
700 start = fec->priorExpiry(false, end) + 1;
701
702 } else if (ad.period() == ADCP::PreviousMonth) {
703
704 end = Date::endOfMonth(expiry - 1 * Months);
705 start = Date(1, end.month(), end.year());
706 }
707
708 QuantLib::ext::shared_ptr<Helper>
helper;
709 if (type == PST::AveragingOffPeakPower) {
710 TLOG(
"Building average off-peak power helper from quote, " << quote->name() <<
".");
711 helper = QuantLib::ext::make_shared<AverageOffPeakPowerHelper>(quote->quote(), index, start,
712 end, uFec, peakIndex, peakCalendar, peakHoursPerDay);
713 } else {
714 TLOG(
"Building average future price helper from quote, " << quote->name() <<
".");
715 helper = QuantLib::ext::make_shared<AverageFuturePriceHelper>(quote->quote(), index, start, end, uFec,
716 ad.pricingCalendar(), ad.deliveryRollDays(), ad.futureMonthOffset(), ad.useBusinessDays(),
717 ad.dailyExpiryOffset());
718 }
719
720
721 Date pillar =
helper->pillarDate();
722 if (instruments.count(pillar) == 0) {
723 instruments[pillar] =
helper;
724 } else {
725 TLOG(
"Skipping quote, " << quote->name() <<
", because an instrument with its pillar date, " <<
726 io::iso_date(pillar) << ", is already in the instrument set.");
727 }
728 break;
729 }
730
731 default:
732 QL_FAIL("CommodityCurve: unrecognised price segment type.");
733 break;
734 }
735 }
736}
Type
Type of price segment being represented, i.e. type of instrument in the price segment.
CommodityFutureConvention::AveragingData::CalculationPeriod ADCP
QuantLib::BootstrapHelper< QuantLib::OptionletVolatilityStructure > helper