Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeError: in method 'EndCriteria_call_', argument 3 of type 'Size &' #1956

Open
andrzimin72 opened this issue Apr 19, 2024 · 9 comments
Open

Comments

@andrzimin72
Copy link

andrzimin72 commented Apr 19, 2024

I cordially salute the participants of the QuantLib conference. As for me one of the most important tools, in particular in calibration procedures, is an optimizer of a function. For example, my typical problem is: find a model parameter set such that some cost function is minimized. The available optimizers in QuantLib are Levenberg Marquardt methods. To setup up an optimizer, I need to define the end criteria which lead to a successful optimization. They are summarized in the EndCriteria class whose constructor os EndCriteria (StationaryStateIterations). I wrote a small piece of code to сonsider the Vasicek model for standard Brownian motion. With a little bit of bad advice, I find myself facing a much bigger challenge.

model.calibrate(swaptions, optimization_method, end_criteria(10000, 100, 1e-6, 1e-8, 1e-8, 1.0e-8, 1.0e-8, 1.0e-8))
File "pandas1/lib/python3.10/site-packages/QuantLib/QuantLib.py", line 7508, in call__return QuantLib.EndCriteria___call(self, iteration, statState, positiveOptimization, fold, normgold, fnew, normgnewx, ecType)
TypeError: in method 'EndCriteria___call
_', argument 3 of type 'Size &'

I am new in this field, could you please guide me about this error. And how should I write this argument ‘statState’ of type 'Size &'?

Copy link

boring-cyborg bot commented Apr 19, 2024

Thanks for posting! It might take a while before we look at your issue, so don't worry if there seems to be no feedback. We'll get to it.

@lballabio
Copy link
Owner

I'm kind of guessing since I don't know how you initialized the various variables, but

model.calibrate(swaptions, optimization_method, end_criteria)

might work. The operator() you're calling is for internal use of the model. I'm not sure it should be exposed to Python.

@andrzimin72
Copy link
Author

andrzimin72 commented Apr 20, 2024

Thank you very much for your good advices. May be you a right. But I have read very interesting article about this topic: “The Levenberg-Marquardt Method and its Implementation in Python” submitted by Marius Kaltenbach (Konstanzer Online-Publikations-System (KOPS) URL: http://nbn-resolving.de/urn:nbn:de:bsz:352-2-1ofyba49ud2jr5). So far I have no complaints about this argument ‘statState’.
I think the input parameters of this argument are:
-maximum iterations: restrict the maximum number of solver iterations;
-minimum stationary state iterations: give a minimum number of iterations at stationary point (for both, function value stationarity and root stationarity). I suppose that not all of the end criteria are needed in each optimizer. I haven’t found any argument 3 of type 'Size &' which checks for the optimization. Furthermore, the argument which leads to a stopping of the algorithm is returned. May be it should be checked that this criteria is not of type EndCriteria::None.
But I don't know how to calculate argument 3 of type 'Size &' in method 'EndCriteria___call__'. And I don't know how to change it.

@lballabio
Copy link
Owner

You don't have to call the method 'EndCriteria__call__'. That method is internal and shouldn't be exposed to Python.

You have already passed the input parameters to the EndCriteria constructor and built end_criteria. You can pass it to the calibrate method by saying model.calibrate(swaptions, optimization_method, end_criteria).

@andrzimin72
Copy link
Author

Thank you very much for your good advices. I corrected the error in the script, so it shouldn't happen again. Apparently there is another problem with the code.
model.calibrate(swaptions, optimization_method, end_criteria)
File "/pandas1/lib/python3.10/site-packages/QuantLib/QuantLib.py", line 16672, in calibrate
return _QuantLib.CalibratedModel_calibrate(self, *args)
RuntimeError: wrong argument type
I suppose in general there is now be another possibilities to find out where exactly in the code it goes wrong. I have read the instructions https://www.implementingquantlib.com/2013/08/chapter-5-part-4-of-5-models-and.html,
https://rkapl123.github.io/QLAnnotatedSource/d7/db6/class_quant_lib_1_1_calibrated_model.html.
May be I am again a bit confused here.

@lballabio
Copy link
Owner

Hard to say what's wrong without seeing the rest of the code. Can you post a script that we can run to reproduce the problem?

@andrzimin72
Copy link
Author

But you're a busy man. Therefore, if I’m already in a difficult situation, I should not waste your time on unnecessary thoughts or waiting for a miracle, which simply can not be. For a long time I assumed that this error was due purely to my own personal bumbling, and not a problem with the method. Anyway I'm trying to answer the question, to articulate my ask.
vasicek_calib.zip

@lballabio
Copy link
Owner

No problem, it took all of 5 minutes once I had code to run. The code below runs the calibration. You were nearly there. The needed changes were:

  • BlackCallableFixedRateBondEngine can't work with swaptions. The JamshidianSwaptionEngine you tried and then commented out works.
  • removed CalibrationData(0, 1, 0.1) (a swaption can't have 0 time to exercise) and replaced with another item because we need at least 4 of them; I picked time to exercise = 2.
  • don't call EndCriteria__call__.
import numpy as np
import QuantLib as ql
from collections import namedtuple
import math


def create_swaption_helpers(data_, index_, term_structure_, engine_):
    swaptions_ = []
    fixed_leg_tenor = ql.Period(1, ql.Years)
    fixed_leg_daycounter = ql.Actual360()
    floating_leg_daycounter = ql.Actual360()
    for d in data_:
        vol_handle = ql.QuoteHandle(ql.SimpleQuote(d.volatility))
        helper = ql.SwaptionHelper(ql.Period(d.start, ql.Months),
                                   ql.Period(d.length, ql.Months),
                                   vol_handle,
                                   index_,
                                   fixed_leg_tenor,
                                   fixed_leg_daycounter,
                                   floating_leg_daycounter,
                                   term_structure_,
                                   ql.BlackCalibrationHelper.RelativePriceError,
                                   ql.nullDouble(),
                                   1.,
                                   ql.Normal,
                                   0.
                                   )
        helper.setPricingEngine(engine_)
        swaptions_.append(helper)
    return swaptions_


def calibration_report(swaptions_, data_):
    print("-" * 82)
    print("%15s %15s %15s %15s %15s" % \
          ("Model Price", "Market Price", "Implied Vol", "Market Vol", "Rel Error"))
    print("-" * 82)
    cum_err = 0.0
    for i, s in enumerate(swaptions_):
        model_price = s.modelValue()
        market_vol = data_[i].volatility
        black_price = s.blackPrice(market_vol)
        rel_error = model_price / black_price - 1.0
        implied_vol = s.impliedVolatility(model_price,
                                          1e-6, 500, 0.0, 0.50)
        rel_error2 = implied_vol / market_vol - 1.0
        cum_err += rel_error2 * rel_error2

        print("%15.5f %15.5f %15.5f %15.5f %15.5f" % \
              (model_price, black_price, implied_vol, market_vol, rel_error))
    print("-" * 82)
    print("Cumulative Error : %15.5f" % math.sqrt(cum_err))


today = ql.Date().todaysDate()
# ql.Settings.instance().evaluationDate = today
# crv = ql.ZeroCurve([today, settlement], [0.05, 0.05], ql.Actual365Fixed())
crv = ql.FlatForward(today, 0.05, ql.Actual365Fixed())
yts = ql.YieldTermStructureHandle(crv)
vol = ql.QuoteHandle(ql.SimpleQuote(0.1))
model = ql.Vasicek(r0=0.05, a=0.2, b=0.05, sigma=0.1)
# engine = ql.BlackCallableFixedRateBondEngine(vol, yts)
engine = ql.JamshidianSwaptionEngine(model, yts)
index = ql.Euribor1Y(yts)

CalibrationData = namedtuple("CalibrationData", 
                             "start, length, volatility")

data = [
    # CalibrationData(0, 1, 0.1),
    CalibrationData(1, 1, 0.1),
    CalibrationData(2, 1, 0.1),
    CalibrationData(3, 1, 0.1),
    CalibrationData(4, 1, 0.1)
]
swaptions = create_swaption_helpers(data, index, yts, engine)

optimization_method = ql.LevenbergMarquardt(1.0e-8, 1.0e-8, 1.0e-8)
end_criteria = ql.EndCriteria(10000, 100, 1e-6, 1e-8, 1e-8)
model.calibrate(swaptions, optimization_method, end_criteria)

calibration_report(swaptions, data)
a, b, sigma, lam = model.params()
print('%6.5f' % a, '%6.5f' % b, '%6.5f' % sigma, '%6.5f' % lam)

@andrzimin72
Copy link
Author

It's amazing incredible! Excellent attention, you have solved the problem. I’m grateful for your help and support. Also, I am grateful for the good fortune to learn at you, to communicate with the great minds, I'm not afraid of this word, experts in programming and machine learning. May be interest to the independent decision of a problem is the stimulus, driving force of process of knowledge. I hope this information proves helpful to someone in the future. Regards!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants