Giter Club home page Giter Club logo

Comments (7)

thrasibule avatar thrasibule commented on June 7, 2024

fair_upfront throws unless you build a cds with upfront which you can't do right now with pyql (See https://github.com/thrasibule/QuantLib/blob/master/ql/instruments/creditdefaultswap.cpp#L179). Now we should raise an exception in python instead of aborting.
You can check my branch upfront_lite which has this fix and also a class for building UpfrontCds.
I'll submit a pull request to make sure the example run as it is.

from pyql.

tangent360 avatar tangent360 commented on June 7, 2024

Thank you for this, it's very helpful. Apologies in advance because this would be much more straightforward if I had a good working knowledge of quantlib.

After looking through the quantlib code I did notice that fairSpread() "does not take any upfront into account, even if one was given", which rules out my intended use for specifying an upfront in the first place - i.e. as way to back out the fair spread given only the upfront.
I believe now the correct way to achieve this goal would be to use the UpfrontCdsHelper and not to try and back into the correct hazzards by changing attributes of the instrument and recomputing?

Is there a common industry equivalent use-case for specifying the upfront as implemented by quantlib?
If I'm thinking about this correctly, to get the market observed upfront on a SNAC CDS you can just set the coupon to 100 or 500 as appropriate on the CreditDefaultSwap instrument and compute the NPV - that should be the day one upfront payment? (this is all assuming you have built the correct underlying hazard rate curve)
Unless you have a very delayed upfront where the FV of the upfront amount is different than paying it today (or T+3) then I'm struggling to see where one would use it.

In any event, I was playing around with the C++ CDS example in quantlib and noticed that if you specify a protection startDate and an upfrontDate of today, then subsequent calls to fairUpfront() return the 'fair upfront not available' error that you referenced in your last post. If you change the upfrontDate = T+1 or later then fairUpfront() computes the correct upfront, which obviously ignores the provided upfront so I don't really understand the logic behind this behavior. If I move the accrual date, protection start date and upfront date all forward to T+1 and leave the evaluation date on T then it also works so I'm guessing this is associated with the includeTodaysCashFlows option in Settings but have not has a chance to investigate yet.

When I try the same from pyql using your branch, and creating the CDS with:

cds_with_upfront = CreditDefaultSwap.from_upfront( SELLER, nominal, upfront_rate, quoted_spreads, cds_schedule, Following, Actual365Fixed(), True, todays_date, todays_date )

and then call:

cds_with_upfront.fair_upfront

I get an error message which is much more informative than the prior crashes:

RuntimeError: fair upfront not available

this behaviour matches the C++ implementation, however when I advance the upfrontDate to todays_date+1, it crashes Python like before.
Similarly, adding 1 to both protectionStartDate and upfrontDate would give me an error that the "protection can not start after accrual" from C++ but from pyql it crashes.
The third option of advancing all 3 dates (accrual, protection start and upfront date) by one day seems to work fine - it matches the C++ numbers, which pretty much tie out with Bloomberg CDSW.

I'm not worried about a day or so of PV-ing difference ... this version will work perfectly fine for me, but I wanted to let you know the behaviour I was seeing in case you were ultimately planning to merge this back into the master branch. Many thanks for the help.

from pyql.

dpinte avatar dpinte commented on June 7, 2024

@tangent360 thanks for your detailed notes. The best for debugging such issue is to provide a minimal script that reproduces the bug. Is this something you could share?

I suspect we do have a c++ exception raised that we don't manage properly in pyql and that the fix should be quick and easy to implement (adding some except+ in the cython layer).

from pyql.

thrasibule avatar thrasibule commented on June 7, 2024

You're right, if you want to get the upfront equivalent, you can just look at the npv of the cds.
I've updated my branch upfront_lite to catch a few more exceptions , so it should bomb a lot less now, let me know if you can trigger other segfaults. It would be helpful if you have some example code written. We can turn it into tests, and I can propose it for merging if the code is helpful to you.
The QuantLib CDS code is pretty old, and doesn't handle current market conventions well at the moment. There is a pull request outstanding which improves the situation a lot (see lballabio/QuantLib#112), but not sure if or when it will get accepted. Maybe you could express interest on github or on the mailing list, that could get things going.

from pyql.

tangent360 avatar tangent360 commented on June 7, 2024

Thank you both for the responses.
I've pasted an example script below that should generate the errors I'm seeing.
I've also pasted my output when I run it so that you can see what I'm getting.
@thrasibule I'd be more than happy to try and get things going on the quantlib CDS updates but I fear the only value I can provide is motivation! What's the best way to see what's missing? Just read through all the proposed code changes or is there a doc somewhere outlining the shortcomings vs the current industry standards?

My output:

 
 ============================================================================
 This attempt will behave the same as the C++ version.
 It raises a runtime error because the accrual date, protection start date
 and upfront date are the same as the evaluation date.
 The error message should be: RuntimeError: fair upfront not available
 ============================================================================


Caught Runtime Error: fair upfront not available

 ============================================================================
 This attempt will behave the same as the C++ version.
 The accrual date, protection start date and upfront date have all been advanced
 to T+1 where the evaluation date is T.
 The computed upfront is 'practically correct', but we had to shift all the dates to make it work.
 ============================================================================


   Upfront:      0.11184785949854062

 ============================================================================
 This attempt will NOT behave the same as the C++ version.
 The upfront date has been advanced to T+1 ... it crashes python while trying
 to create the CreditDefaultSwap.
 To match the C++ version it should be returning the 'correct' calculated
 upfront amount, but it won't exactly match the upfront above because of the
 other date changes
 ============================================================================


The Schedule has been correctly created ... now attempting to create the CreditDefaultSwap...

**** PYTHON CRASHES ***

My test script:

from __future__ import print_function

from quantlib.instruments.credit_default_swap import CreditDefaultSwap, SELLER
from quantlib.pricingengines.credit import MidPointCdsEngine
from quantlib.settings import Settings
from quantlib.time.api import (
    Date, May, Sep, Dec, Actual360, Following, TARGET, Period, Months,
    Quarterly, TwentiethIMM, Years, Schedule, Unadjusted
)
from quantlib.termstructures.credit.api import (
        SpreadCdsHelper, PiecewiseDefaultCurve, ProbabilityTrait, Interpolator )
from quantlib.termstructures.yields.api import FlatForward

if __name__ == '__main__':

    #*********************
    #***  MARKET DATA  ***
    #*********************
    calendar = TARGET()

    todays_date = Date(20, Sep, 2016)

    # must be a business day
    todays_date = calendar.adjust(todays_date)

    Settings.instance().evaluation_date = todays_date

    # dummy curve
    ts_curve = FlatForward(
        reference_date=todays_date, forward=-0.005, daycounter=Actual360()
    )

    # In Lehmans Brothers "guide to exotic credit derivatives"
    # p. 32 there's a simple case, zero flat curve with a flat CDS
    # curve with constant market spreads of 150 bp and RR = 50%
    # corresponds to a flat 3% hazard rate. The implied 1-year
    # survival probability is 97.04% and the 2-years is 94.18%

    # market
    recovery_rate = 0.4
    quoted_spreads = [0.08, 0.08, 0.08, 0.08 ]

    tenors = [Period(i, Months) for i in [1, 4, 8, 5*12]]
    maturities = [
        calendar.adjust(todays_date + tenors[i], Following) for i in range(4)
    ]
    instruments = []
    for i in range(4):
        helper = SpreadCdsHelper(
            quoted_spreads[i], tenors[i], 0, calendar, Quarterly,
            Following, TwentiethIMM, Actual360(), recovery_rate, ts_curve
        )

        instruments.append(helper)

    # Bootstrap hazard rates
    hazard_rate_structure = PiecewiseDefaultCurve.from_reference_date(
            ProbabilityTrait.HazardRate, Interpolator.BackwardFlat, todays_date, instruments, Actual360()
    )

    #vector<pair<Date, Real> > hr_curve_data = hazardRateStructure->nodes();

    #cout << "Calibrated hazard rate values: " << endl ;
    #for (Size i=0; i<hr_curve_data.size(); i++) {
    #    cout << "hazard rate on " << hr_curve_data[i].first << " is "
    #         << hr_curve_data[i].second << endl;
    #}
    #cout << endl;


    target = todays_date + Period(1, Years)
    # reprice instruments
    nominal = 1000000.0;
    #Handle<DefaultProbabilityTermStructure> probability(hazardRateStructure);
    engine = MidPointCdsEngine(hazard_rate_structure, recovery_rate, ts_curve)

    print(  "\n",
            "============================================================================\n",
            "This attempt will behave the same as the C++ version.\n",
            "It raises a runtime error because the accrual date, protection start date \n",
            "and upfront date are the same as the evaluation date.\n",
            "The error message should be: RuntimeError: fair upfront not available\n",
            "============================================================================\n\n"
            )


    cds_schedule = Schedule.from_effective_termination(
        todays_date, maturities[3], Period(Quarterly), calendar,
        termination_date_convention=Unadjusted,
        date_generation_rule=TwentiethIMM
    )

    cds_upfront = CreditDefaultSwap.from_upfront(
        SELLER, nominal, 0.0, 0.05, cds_schedule, Following,
        Actual360(), True, todays_date, todays_date
    )

    cds_upfront.set_pricing_engine(engine);

    try:
        calculated_upfront = cds_upfront.fair_upfront
        print("   Upfront:     ", calculated_upfront )
    except RuntimeError as err:
        print("Caught Runtime Error:", err  )


    print("\n",
          "============================================================================\n",
          "This attempt will behave the same as the C++ version.\n",
          "The accrual date, protection start date and upfront date have all been advanced\n",
          "to T+1 where the evaluation date is T.\n",
          "The computed upfront is 'practically correct', but we had to shift all the dates to make it work.\n",
          "============================================================================\n\n"
          )

    cds_schedule = Schedule.from_effective_termination(
        todays_date + 1, maturities[3], Period(Quarterly), calendar,
        termination_date_convention=Unadjusted,
        date_generation_rule=TwentiethIMM
    )

    cds_upfront = CreditDefaultSwap.from_upfront(
        SELLER, nominal, 0.0, 0.05, cds_schedule, Following,
        Actual360(), True, todays_date + 1, todays_date + 1
    )

    cds_upfront.set_pricing_engine(engine);

    try:
        calculated_upfront = cds_upfront.fair_upfront
        print("   Upfront:     ", calculated_upfront)
    except RuntimeError as err:
        print("Caught Runtime Error:", err)


    print("\n",
          "============================================================================\n",
          "This attempt will NOT behave the same as the C++ version.\n",
          "The upfront date has been advanced to T+1 ... it crashes python while trying\n",
          "to create the CreditDefaultSwap.\n",
          "To match the C++ version it should be returning the 'correct' calculated\n",
          "upfront amount, but it won't exactly match the upfront above because of the\n",
          "other date changes\n",
          "============================================================================\n\n"
          )

    cds_schedule = Schedule.from_effective_termination(
        todays_date, maturities[3], Period(Quarterly), calendar,
        termination_date_convention=Unadjusted,
        date_generation_rule=TwentiethIMM
    )

    print( "The Schedule has been correctly created ... now attempting to create the CreditDefaultSwap...")

    try:
        # This next line will cause Python to crash, the catch blocks don't get a chance to execute.
        cds_upfront = CreditDefaultSwap.from_upfront(
            SELLER, nominal, 0.0, 0.05, cds_schedule, Following,
            Actual360(), True, todays_date, todays_date+1
        )
    except:
        print("Unexpected error:", sys.exec_info()[0])

    #This line will never run.
    print("Now attempting to set Engine ... If you're seeing this output your experience is different to mine.")
    cds_upfront.set_pricing_engine(engine);

    try:
        calculated_upfront = cds_upfront.fair_upfront
        print("   Upfront:     ", calculated_upfront )
    except RuntimeError as err:
        print("Caught Runtime Error:", err  )

from pyql.

thrasibule avatar thrasibule commented on June 7, 2024

I've tried your script. It runs fine for me with my latest branch if I replace exec_info by exc_info (there is a typo there), and it catches the runtime exception. However it shows the same upfront as before, since obviously the cds is not updated. Are you positive you can build a cds with these parameters in C++? Can you share that C++ code as well if that's the case?

from pyql.

tangent360 avatar tangent360 commented on June 7, 2024

I was missing a second boolean value in the argument list to CreditDefaultSwap.from_upfront so I was advancing the protection start date when I thought I was advancing the upfront date. The error coming back was 'protection cannot start before accrual date' but since it was crashing Python, before @thrasibule's latest update, I wasn't aware of the argument problem. Separately, I was reusing the calculated_upfront variable for all test cases and that's why the tests appeared to be producing the same value ... it was the same because it was never being overwritten inside the failing try block. Now when I advance all dates vs just the upfront date, the computed upfront amounts have a small difference and match the C++ values. This is also the expected model behaviour. This particular test case, of advancing all dates, is not really relevant anymore since just advancing the upfront_date works from pyql. There are only two test cases worth considering:

  • upfront_date = Settings.instance().evaluation_date which throws an error from quantlib and I don't understand the thinking but can speculate - in any event it's a quantlib isssue and not pyql.
  • upfront_date = evaluation_date+1 we get the correct computed upfront.

Many thanks for all the help on this.

Here is my new output and test script:

Output:

 ============================================================================
 This test case raises a runtime error because the accrual date, protection
 start date and upfront date are the same as the evaluation date.
 The error message should be: RuntimeError: fair upfront not available.
 ============================================================================


Caught Runtime Error: fair upfront not available
 ============================================================================
 If we move the upfront date to T+1 and leave the evaluation date, accrual date
 and protection start date on T then we can get the fair_upfront even though
 the provided upfront has no bearing on the calculation of fair_upfront.
 This is the behavior we see in the C++ implementation of quantlib.
 ============================================================================


   Upfront:      0.11189181724989519

Script:

from __future__ import print_function

from quantlib.instruments.credit_default_swap import CreditDefaultSwap, SELLER
from quantlib.pricingengines.credit import MidPointCdsEngine
from quantlib.settings import Settings
from quantlib.time.api import (
    Date, May, Sep, Dec, Actual360, Following, TARGET, Period, Months,
    Quarterly, TwentiethIMM, Years, Schedule, Unadjusted
)
from quantlib.termstructures.credit.api import (
        SpreadCdsHelper, PiecewiseDefaultCurve, ProbabilityTrait, Interpolator )
from quantlib.termstructures.yields.api import FlatForward
import sys

if __name__ == '__main__':

    #*********************
    #***  MARKET DATA  ***
    #*********************
    calendar = TARGET()

    todays_date = Date(20, Sep, 2016)

    # must be a business day
    todays_date = calendar.adjust(todays_date)

    Settings.instance().evaluation_date = todays_date

    # dummy curve
    ts_curve = FlatForward(
        reference_date=todays_date, forward=-0.005, daycounter=Actual360()
    )

    # In Lehmans Brothers "guide to exotic credit derivatives"
    # p. 32 there's a simple case, zero flat curve with a flat CDS
    # curve with constant market spreads of 150 bp and RR = 50%
    # corresponds to a flat 3% hazard rate. The implied 1-year
    # survival probability is 97.04% and the 2-years is 94.18%

    # market
    recovery_rate = 0.4
    quoted_spreads = [0.08, 0.08, 0.08, 0.08 ]

    tenors = [Period(i, Months) for i in [1, 4, 8, 5*12]]
    maturities = [
        calendar.adjust(todays_date + tenors[i], Following) for i in range(4)
    ]
    instruments = []
    for i in range(4):
        helper = SpreadCdsHelper(
            quoted_spreads[i], tenors[i], 0, calendar, Quarterly,
            Following, TwentiethIMM, Actual360(), recovery_rate, ts_curve
        )

        instruments.append(helper)

    # Bootstrap hazard rates
    hazard_rate_structure = PiecewiseDefaultCurve.from_reference_date(
            ProbabilityTrait.HazardRate, Interpolator.BackwardFlat, todays_date, instruments, Actual360()
    )

    #vector<pair<Date, Real> > hr_curve_data = hazardRateStructure->nodes();

    #cout << "Calibrated hazard rate values: " << endl ;
    #for (Size i=0; i<hr_curve_data.size(); i++) {
    #    cout << "hazard rate on " << hr_curve_data[i].first << " is "
    #         << hr_curve_data[i].second << endl;
    #}
    #cout << endl;


    target = todays_date + Period(1, Years)
    # reprice instruments
    nominal = 1000000.0;
    #Handle<DefaultProbabilityTermStructure> probability(hazardRateStructure);
    engine = MidPointCdsEngine(hazard_rate_structure, recovery_rate, ts_curve)

    print(  "",
            "============================================================================\n",
            "This test case raises a runtime error because the accrual date, protection\n",
            "start date and upfront date are the same as the evaluation date.\n",
            "The error message should be: RuntimeError: fair upfront not available.\n",
            "============================================================================\n\n"
            )


    cds_schedule = Schedule.from_effective_termination(
        todays_date, maturities[3], Period(Quarterly), calendar,
        termination_date_convention=Unadjusted,
        date_generation_rule=TwentiethIMM
    )

    cds_upfront = CreditDefaultSwap.from_upfront(
        SELLER, nominal, 0.0, 0.05, cds_schedule, Following,
        Actual360(), True, True, todays_date, todays_date
    )

    cds_upfront.set_pricing_engine(engine);

    try:
        calculated_upfront = cds_upfront.fair_upfront
        print("   Upfront:     ", calculated_upfront )
    except RuntimeError as err:
        print("Caught Runtime Error:", err  )
    calculated_upfront = None

    print("",
          "============================================================================\n",
          "If we move the upfront date to T+1 and leave the evaluation date, accrual date\n",
          "and protection start date on T then we can get the fair_upfront even though\n",
          "the provided upfront has no bearing on the calculation of fair_upfront.\n",
          "This is the same behavior we see in the C++ implementation of quantlib.\n",
          "============================================================================\n\n"
          )

    cds_schedule = Schedule.from_effective_termination(
        todays_date, maturities[3], Period(Quarterly), calendar,
        termination_date_convention=Unadjusted,
        date_generation_rule=TwentiethIMM
    )

    try:
        cds_upfront = CreditDefaultSwap.from_upfront(
            SELLER, nominal, 0.0, 0.05, cds_schedule, Following,
            Actual360(), True, True, todays_date, todays_date+1
        )
    except RuntimeError as err:
        print("RuntimeError error:", err)

    cds_upfront.set_pricing_engine(engine);

    try:
        calculated_upfront = cds_upfront.fair_upfront
        print("   Upfront:     ", calculated_upfront )
    except RuntimeError as err:
        print("Caught Runtime Error:", err  )
    calculated_upfront = None

from pyql.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.