text: /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
  
  /*
   Copyright (C) 2006, 2007 Chiara Fornarola
   Copyright (C) 2007, 2009, 2011 Ferdinando Ametrano
   Copyright (C) 2007, 2009 StatPro Italia srl
  
   This file is part of QuantLib, a free-software/open-source library
   for financial quantitative analysts and developers - http://quantlib.org/
  
   QuantLib is free software: you can redistribute it and/or modify it
   under the terms of the QuantLib license.  You should have received a
   copy of the license along with this program; if not, please email
   <quantlib-dev@lists.sf.net>. The license is also available online at
   <http://quantlib.org/license.shtml>.
  
   This program is distributed in the hope that it will be useful, but WITHOUT
   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
   FOR A PARTICULAR PURPOSE.  See the license for more details.
  */
  
  #include <ql/instruments/assetswap.hpp>
  
  #include <ql/cashflows/cashflowvectors.hpp>
  #include <ql/cashflows/fixedratecoupon.hpp>
  #include <ql/cashflows/iborcoupon.hpp>
  #include <ql/cashflows/simplecashflow.hpp>
  #include <ql/cashflows/couponpricer.hpp>
  #include <ql/pricingengines/swap/discountingswapengine.hpp>
  
  using boost::shared_ptr;
  using boost::dynamic_pointer_cast;
  using std::vector;
  
  namespace QuantLib {
  
      AssetSwap::AssetSwap(bool parSwap,
                           const boost::shared_ptr<Bond>& bond,
                           Real bondCleanPrice,
                           Real nonParRepayment,
                           Real gearing,
                           const boost::shared_ptr<IborIndex>& iborIndex,
                           Spread spread,
                           const DayCounter& floatingDayCounter,
                           Date dealMaturity,
                           bool payBondCoupon)
      : Swap(2), bond_(bond), bondCleanPrice_(bondCleanPrice),
        nonParRepayment_(nonParRepayment), spread_(spread), parSwap_(parSwap)
      {
          Schedule tempSch(bond_->settlementDate(),
                           bond_->maturityDate(),
                           iborIndex->tenor(),
                           iborIndex->fixingCalendar(),
                           iborIndex->businessDayConvention(),
                           iborIndex->businessDayConvention(),
                           DateGeneration::Backward,
                           false); // endOfMonth
          if (dealMaturity==Date())
              dealMaturity = bond_->maturityDate();
          QL_REQUIRE(dealMaturity <= tempSch.dates().back(),
                     "deal maturity " << dealMaturity <<
                     " cannot be later than (adjusted) bond maturity " <<
                     tempSch.dates().back());
          QL_REQUIRE(dealMaturity > tempSch.dates()[0],
                     "deal maturity " << dealMaturity <<
                     " must be later than swap start date " <<
                     tempSch.dates()[0]);
  
          // the following might become an input parameter
          BusinessDayConvention paymentAdjustment = Following;
  
          Date finalDate = tempSch.calendar().adjust(
              dealMaturity, paymentAdjustment);
          Schedule schedule = tempSch.until(finalDate);
  
          // bondCleanPrice must be the (forward) clean price
          // at the floating schedule start date
          upfrontDate_ = schedule.startDate();
          Real dirtyPrice = bondCleanPrice_ +
                            bond_->accruedAmount(upfrontDate_);
  
          Real notional = bond_->notional(upfrontDate_);
          /* In the market asset swap, the bond is purchased in return for
             payment of the full price. The notional of the floating leg is
             then scaled by the full price. */
          if (!parSwap_)
              notional *= dirtyPrice/100.0;
  
          if (floatingDayCounter==DayCounter())
              legs_[1] = IborLeg(schedule, iborIndex)
                  .withNotionals(notional)
                  .withPaymentAdjustment(paymentAdjustment)
                  .withGearings(gearing)
                  .withSpreads(spread);
          else
              legs_[1] = IborLeg(schedule, iborIndex)
                  .withNotionals(notional)
                  .withPaymentDayCounter(floatingDayCounter)
                  .withPaymentAdjustment(paymentAdjustment)
                  .withGearings(gearing)
                  .withSpreads(spread);
  
          Leg::const_iterator i;
          for (i=legs_[1].begin(); i<legs_[1].end(); ++i)
              registerWith(*i);
  
          const Leg& bondLeg = bond_->cashflows();
          // skip bond redemption
          for (i = bondLeg.begin(); i<bondLeg.end()-1 && (*i)->date()<=dealMaturity; ++i) {
              // whatever might be the choice for the discounting engine
              // bond flows on upfrontDate_ must be discarded
              bool upfrontDateBondFlows = false;
              if (!(*i)->hasOccurred(upfrontDate_, upfrontDateBondFlows))
                  legs_[0].push_back(*i);
          }
          // if the first skipped cashflow is not the redemption
          // and it is a coupon then add the accrued coupon
          if (i<bondLeg.end()-1) {
              shared_ptr<Coupon> c = boost::dynamic_pointer_cast<Coupon>(*i);
              if (c) {
                  shared_ptr<CashFlow> accruedCoupon(new
                      SimpleCashFlow(c->accruedAmount(dealMaturity), finalDate));
                  legs_[0].push_back(accruedCoupon);
              }
          }
          // add the nonParRepayment_
          shared_ptr<CashFlow> nonParRepaymentFlow(new
              SimpleCashFlow(nonParRepayment_, finalDate));
          legs_[0].push_back(nonParRepaymentFlow);
  
          QL_REQUIRE(!legs_[0].empty(),
                     "empty bond leg to start with");
  
          // special flows
          if (parSwap_) {
              // upfront on the floating leg
              Real upfront = (dirtyPrice-100.0)/100.0*notional;
              shared_ptr<CashFlow> upfrontCashFlow(new
                  SimpleCashFlow(upfront, upfrontDate_));
              legs_[1].insert(legs_[1].begin(), upfrontCashFlow);
              // backpayment on the floating leg
              // (accounts for non-par redemption, if any)
              Real backPayment = notional;
              shared_ptr<CashFlow> backPaymentCashFlow(new
                  SimpleCashFlow(backPayment, finalDate));
              legs_[1].push_back(backPaymentCashFlow);
          } else {
              // final notional exchange
              shared_ptr<CashFlow> finalCashFlow (new
                  SimpleCashFlow(notional, finalDate));
              legs_[1].push_back(finalCashFlow);
          }
  
          QL_REQUIRE(!legs_[0].empty(), "empty bond leg");
          for (i=legs_[0].begin(); i<legs_[0].end(); ++i)
              registerWith(*i);
  
          if (payBondCoupon) {
              payer_[0]=-1.0;
              payer_[1]=+1.0;
          } else {
              payer_[0]=+1.0;
              payer_[1]=-1.0;
          }
      }
  
      AssetSwap::AssetSwap(bool payBondCoupon,
                           const shared_ptr<Bond>& bond,
                           Real bondCleanPrice,
                           const shared_ptr<IborIndex>& iborIndex,
                           Spread spread,
                           const Schedule& floatSchedule,
                           const DayCounter& floatingDayCounter,
                           bool parSwap)
      : Swap(2), bond_(bond), bondCleanPrice_(bondCleanPrice),
        nonParRepayment_(100), spread_(spread), parSwap_(parSwap)
      {
          Schedule schedule = floatSchedule;
          if (floatSchedule.empty())
              schedule = Schedule(bond_->settlementDate(),
                                  bond_->maturityDate(),
                                  iborIndex->tenor(),
                                  iborIndex->fixingCalendar(),
                                  iborIndex->businessDayConvention(),
                                  iborIndex->businessDayConvention(),
                                  DateGeneration::Backward,
                                  false); // endOfMonth
  
          // the following might become an input parameter
          BusinessDayConvention paymentAdjustment = Following;
  
          Date finalDate = schedule.calendar().adjust(
              schedule.endDate(), paymentAdjustment);
          Date adjBondMaturityDate = schedule.calendar().adjust(
              bond_->maturityDate(), paymentAdjustment);
  
          QL_REQUIRE(finalDate==adjBondMaturityDate,
                     "adjusted schedule end date (" <<
                     finalDate <<
                     ") must be equal to adjusted bond maturity date (" <<
                     adjBondMaturityDate << ")");
  
          // bondCleanPrice must be the (forward) clean price
          // at the floating schedule start date
          upfrontDate_ = schedule.startDate();
          Real dirtyPrice = bondCleanPrice_ +
                            bond_->accruedAmount(upfrontDate_);
  
          Real notional = bond_->notional(upfrontDate_);
          /* In the market asset swap, the bond is purchased in return for
             payment of the full price. The notional of the floating leg is
             then scaled by the full price. */
          if (!parSwap_)
              notional *= dirtyPrice/100.0;
  
          if (floatingDayCounter==DayCounter())
              legs_[1] = IborLeg(schedule, iborIndex)
                  .withNotionals(notional)
                  .withPaymentAdjustment(paymentAdjustment)
                  .withSpreads(spread);
          else
              legs_[1] = IborLeg(schedule, iborIndex)
                  .withNotionals(notional)
                  .withPaymentDayCounter(floatingDayCounter)
                  .withPaymentAdjustment(paymentAdjustment)
                  .withSpreads(spread);
  
          for (Leg::const_iterator i=legs_[1].begin(); i<legs_[1].end(); ++i)
              registerWith(*i);
  
          const Leg& bondLeg = bond_->cashflows();
          for (Leg::const_iterator i=bondLeg.begin(); i<bondLeg.end(); ++i) {
              // whatever might be the choice for the discounting engine
              // bond flows on upfrontDate_ must be discarded
              bool upfrontDateBondFlows = false;
              if (!(*i)->hasOccurred(upfrontDate_, upfrontDateBondFlows))
                  legs_[0].push_back(*i);
          }
  
          QL_REQUIRE(!legs_[0].empty(),
                     "empty bond leg to start with");
  
          // special flows
          if (parSwap_) {
              // upfront on the floating leg
              Real upfront = (dirtyPrice-100.0)/100.0*notional;
              shared_ptr<CashFlow> upfrontCashFlow(new
                  SimpleCashFlow(upfront, upfrontDate_));
              legs_[1].insert(legs_[1].begin(), upfrontCashFlow);
              // backpayment on the floating leg
              // (accounts for non-par redemption, if any)
              Real backPayment = notional;
              shared_ptr<CashFlow> backPaymentCashFlow(new
                  SimpleCashFlow(backPayment, finalDate));
              legs_[1].push_back(backPaymentCashFlow);
          } else {
              // final notional exchange
              shared_ptr<CashFlow> finalCashFlow(new
                  SimpleCashFlow(notional, finalDate));
              legs_[1].push_back(finalCashFlow);
          }
  
          QL_REQUIRE(!legs_[0].empty(), "empty bond leg");
          for (Leg::const_iterator i=legs_[0].begin(); i<legs_[0].end(); ++i)
              registerWith(*i);
  
          if (payBondCoupon) {
              payer_[0]=-1.0;
              payer_[1]=+1.0;
          } else {
              payer_[0]=+1.0;
              payer_[1]=-1.0;
          }
      }
  
      void AssetSwap::setupArguments(PricingEngine::arguments* args) const {
  
          Swap::setupArguments(args);
  
          AssetSwap::arguments* arguments =
              dynamic_cast<AssetSwap::arguments*>(args);
  
          if (!arguments)  // it's a swap engine...
              return;
  
          const Leg& fixedCoupons = bondLeg();
  
          arguments->fixedResetDates = arguments->fixedPayDates =
              vector<Date>(fixedCoupons.size());
          arguments->fixedCoupons = vector<Real>(fixedCoupons.size());
  
          for (Size i=0; i<fixedCoupons.size(); ++i) {
              shared_ptr<FixedRateCoupon> coupon =
                  dynamic_pointer_cast<FixedRateCoupon>(fixedCoupons[i]);
  
              arguments->fixedPayDates[i] = coupon->date();
              arguments->fixedResetDates[i] = coupon->accrualStartDate();
              arguments->fixedCoupons[i] = coupon->amount();
          }
  
          const Leg& floatingCoupons = floatingLeg();
  
          arguments->floatingResetDates = arguments->floatingPayDates =
              arguments->floatingFixingDates =
              vector<Date>(floatingCoupons.size());
          arguments->floatingAccrualTimes =
              vector<Time>(floatingCoupons.size());
          arguments->floatingSpreads =
              vector<Spread>(floatingCoupons.size());
  
          for (Size i=0; i<floatingCoupons.size(); ++i) {
              shared_ptr<FloatingRateCoupon> coupon =
                  dynamic_pointer_cast<FloatingRateCoupon>(floatingCoupons[i]);
  
              arguments->floatingResetDates[i] = coupon->accrualStartDate();
              arguments->floatingPayDates[i] = coupon->date();
              arguments->floatingFixingDates[i] = coupon->fixingDate();
              arguments->floatingAccrualTimes[i] = coupon->accrualPeriod();
              arguments->floatingSpreads[i] = coupon->spread();
          }
      }
  
      Spread AssetSwap::fairSpread() const {
          static const Spread basisPoint = 1.0e-4;
          calculate();
          if (fairSpread_ != Null<Spread>()) {
              return fairSpread_;
          } else if (legBPS_.size() > 1 && legBPS_[1] != Null<Spread>()) {
              fairSpread_ = spread_ - NPV_/legBPS_[1]*basisPoint;
              return fairSpread_;
          } else {
              QL_FAIL("fair spread not available");
          }
      }
  
      Real AssetSwap::floatingLegBPS() const {
          calculate();
          QL_REQUIRE(legBPS_.size() > 1 && legBPS_[1] != Null<Real>(),
                     "floating-leg BPS not available");
          return legBPS_[1];
      }
  
      Real AssetSwap::floatingLegNPV() const {
          calculate();
          QL_REQUIRE(legNPV_.size() > 1 && legNPV_[1] != Null<Real>(),
                     "floating-leg NPV not available");
          return legNPV_[1];
      }
  
      Real AssetSwap::fairCleanPrice() const {
          calculate();
          if (fairCleanPrice_ != Null<Real>()) {
              return fairCleanPrice_;
          } else {
              QL_REQUIRE(startDiscounts_[1]!=Null<DiscountFactor>(),
                         "fair clean price not available for seasoned deal");
              Real notional = bond_->notional(upfrontDate_);
              if (parSwap_) {
                  fairCleanPrice_ = bondCleanPrice_ - payer_[1] *
                      NPV_*npvDateDiscount_/startDiscounts_[1]/(notional/100.0);
              } else {
                  Real accruedAmount = bond_->accruedAmount(upfrontDate_);
                  Real dirtyPrice = bondCleanPrice_ + accruedAmount;
                  Real fairDirtyPrice = - legNPV_[0]/legNPV_[1] * dirtyPrice;
                  fairCleanPrice_ = fairDirtyPrice - accruedAmount;
              }
  
              return fairCleanPrice_;
          }
      }
  
      Real AssetSwap::fairNonParRepayment() const {
          calculate();
          if (fairNonParRepayment_ != Null<Real>()) {
              return fairNonParRepayment_;
          } else {
              QL_REQUIRE(endDiscounts_[1]!=Null<DiscountFactor>(),
                         "fair non par repayment not available for expired leg");
              Real notional = bond_->notional(upfrontDate_);
              fairNonParRepayment_ = nonParRepayment_ - payer_[0] * 
                  NPV_*npvDateDiscount_/endDiscounts_[1]/(notional/100.0);
              return fairNonParRepayment_;
          }
      }
  
      void AssetSwap::setupExpired() const {
          Swap::setupExpired();
          fairSpread_ = Null<Spread>();
          fairCleanPrice_ = Null<Real>();
          fairNonParRepayment_ = Null<Real>();
      }
  
      void AssetSwap::fetchResults(const PricingEngine::results* r) const {
          Swap::fetchResults(r);
          const AssetSwap::results* results =
              dynamic_cast<const AssetSwap::results*>(r);
          if (results) {
              fairSpread_ = results->fairSpread;
              fairCleanPrice_= results->fairCleanPrice;
              fairNonParRepayment_= results->fairNonParRepayment;
          } else {
              fairSpread_ = Null<Spread>();
              fairCleanPrice_ = Null<Real>();
              fairNonParRepayment_ = Null<Real>();
          }
      }
  
      void AssetSwap::arguments::validate() const {
          QL_REQUIRE(fixedResetDates.size() == fixedPayDates.size(),
                     "number of fixed start dates different from "
                     "number of fixed payment dates");
          QL_REQUIRE(fixedPayDates.size() == fixedCoupons.size(),
                     "number of fixed payment dates different from "
                     "number of fixed coupon amounts");
          QL_REQUIRE(floatingResetDates.size() == floatingPayDates.size(),
                     "number of floating start dates different from "
                     "number of floating payment dates");
          QL_REQUIRE(floatingFixingDates.size() == floatingPayDates.size(),
                     "number of floating fixing dates different from "
                     "number of floating payment dates");
          QL_REQUIRE(floatingAccrualTimes.size() == floatingPayDates.size(),
                     "number of floating accrual times different from "
                     "number of floating payment dates");
          QL_REQUIRE(floatingSpreads.size() == floatingPayDates.size(),
                     "number of floating spreads different from "
                     "number of floating payment dates");
      }
  
      void AssetSwap::results::reset() {
          Swap::results::reset();
          fairSpread = Null<Spread>();
          fairCleanPrice = Null<Real>();
          fairNonParRepayment = Null<Real>();
      }
  
  }
  