|
From: Ashish B. <ash...@gm...> - 2022-05-18 14:16:02
|
We tried again and got the same error.
Could it be due to the old version (1.7) we are using? I see in the release
notes of Apr-2021 that the overload for past fixing was added:
- Added an overloaded constructor for Asian options that takes all past
fixings and thus allows to reprice them correctly when the evaluation date
changes (thanks to Jack Gillett).
Ashish
On Wed, 18 May 2022 at 19:14, Jack G <jac...@gm...> wrote:
> Hi Ashish,
>
> I have a better answer for you with a screenshot awaiting moderation as
> it's quite large, but in the meantime, I was able to run your code without
> problems using the python at the bottom of this email (although I didn't
> check the numbers).
>
> If there is something you're doing specifically to generate the error,
> could you please attach a minimal reproduction of it?
>
> Best,
> Jack
>
>
>
>
> import QuantLib as ql
> from copy import deepcopy
> from datetime import datetime
> from datetime import timedelta
>
>
> # DEFAULT_PERIODS = [ql.Period(f"{i}d") for i in range(0,31)]
> DEFAULT_PERIODS = [ql.Period("6M"), ql.Period("12M"), ql.Period("18M"),
> ql.Period("24M")]
> DEFAULT_tGRID = 100
> DEFAULT_xGRID = 100
> DEFAULT_aGRID = 50
> DEFAULT_aRA = 1 # arithmeticRunningAccumulator
> DEFAULT_PASTFIXINGS = 1
>
>
> class AsianOptionPrice:
> def __init__(self, **x):
> for k, v in x.items():
> setattr(self, k, v)
> self.tGrid = x.get("tGrid", DEFAULT_tGRID)
> self.xGrid = x.get("xGrid", DEFAULT_xGRID)
> self.aGrid = x.get("aGrid", DEFAULT_aGRID)
> self.periods = x.get("periods", DEFAULT_PERIODS)
> self.pastFixings = x.get("pastFixings", DEFAULT_PASTFIXINGS)
> self.aRA = x.get("arithmeticRunningAccumulator", DEFAULT_aRA)
> self.keyList = [k for k, v in x.items()]
> self.dist = 10**(-2)
> self.date_format = "%d-%m-%Y"
> self.value_date = ql.DateParser.parseFormatted(
> self.valueDate, '%d-%m-%Y')
> self.expiry_date = ql.DateParser.parseFormatted(
> self.expiryDate, '%d-%m-%Y')
> ql.Settings.instance().evaluationDate = self.value_date
> self.exDay = ql.DateParser.parseFormatted(self.expiryDate,
> '%d-%m-%Y')
> # Volatility input is required for running the computing implied
> volatility function. This volatility is NOT used in the implied volatility
> calculation.
> self.vol_for_iv_calc = 0.10
> self.IV = None
>
> def generate_fixture_dates(self,start_date,end_date,period):
> schedule = ql.MakeSchedule(start_date, end_date, period)
> fixture_dates = [_date for _date in schedule if _date.weekday()
> not in [1,7]]
> return fixture_dates
> def asian(self):
> """
> arithmeticRunningAccumulator(aRA)
> """
> arithmeticRunningAccumulator = self.aRA
> pastFixings = self.pastFixings
> print(pastFixings)
> option_type = self.optionType.lower()
> if option_type not in ["call", "put"]:
> raise Exception(
> f"Option Type is Neither Call nor Put, Option type is
> {option_type}")
> option_type = ql.Option.Call if option_type == "call" else
> ql.Option.Put
> strike = self.strikePrice
> today = self.value_date
> print(today)
> fixingStartDate
> =ql.DateParser.parseFormatted(self.fixingStartDate, '%d-%m-%Y')
> print("#"*40,fixingStartDate)
> periods = self.periods
> asianFutureFixingDates =
> self.generate_fixture_dates(fixingStartDate,self.expiry_date,ql.Period("1d"))
> print("#"*40,asianFutureFixingDates)
> asianExpiryDate = self.expiry_date
> vanillaPayoff = ql.PlainVanillaPayoff(option_type, strike)
> europeanExercise = ql.EuropeanExercise(asianExpiryDate)
> arithmeticAverage = ql.Average().Arithmetic
> discreteArithmeticAsianOption = ql.DiscreteAveragingAsianOption(
> arithmeticAverage, arithmeticRunningAccumulator, pastFixings,
> asianFutureFixingDates, vanillaPayoff, europeanExercise)
> return discreteArithmeticAsianOption
>
> def create_arg_list(self, Option=None, compute_implied_vol=None,
> implied_vol_am_put=None):
> '''
> The arguments with disturbances added. Will be used for
> calculation of Greeks
> using numerical differentiation.
> Creating a dictionary of arguments that will be used for
> option price and greeks calculation.
> '''
> self.riskFreeRate = self.riskFreeRate/100
> originalDict_ = {k: getattr(self, k) for k in self.keyList}
> ListDict = []
> for i in range(4):
> ListDict.append(deepcopy(originalDict_))
> ListDict[1]['riskFreeRate'] = self.riskFreeRate + self.dist
> ListDict[2]['volatility'] = self.volatility + self.dist
> ListDict[3]['expiryDate'] = (datetime.strptime(
> self.expiryDate, self.date_format) -
> timedelta(days=1)).strftime(self.date_format)
> print(ListDict)
> return(ListDict)
>
> def bs_process(self, create_process_for_iv=None, use_IV=None):
> """
> Creating the Black-Scholes process for option valuation.
> """
> riskFreeTS = ql.YieldTermStructureHandle(
> ql.FlatForward(self.value_date, self.riskFreeRate,
> ql.Actual365Fixed()))
> dividendTS = ql.YieldTermStructureHandle(
> ql.FlatForward(self.value_date, 0, ql.Actual365Fixed()))
> volatility = ql.BlackVolTermStructureHandle(ql.BlackConstantVol(
> self.value_date, ql.NullCalendar(), self.volatility,
> ql.Actual365Fixed()))
> initialValue = ql.QuoteHandle(ql.SimpleQuote(self.underlyingPrice))
> process = ql.BlackScholesMertonProcess(
> initialValue, dividendTS, riskFreeTS, volatility)
> return(process)
>
> def evaluate(self, Option=None, compute_implied_vol=None):
> """
> Call the relevant option based on the input.
> """
> self.riskFreeRate = self.riskFreeRate/100
> res = None
> option = AsianOptionPrice.asian(self)
> if compute_implied_vol == True:
> # create Black Scholes process using IV.
> process = AsianOptionPrice.bs_process(
> self, create_process_for_iv=False, use_IV=True)
> engine = ql.FdBlackScholesAsianEngine(
> process, tGrid=self.tGrid, xGrid=self.xGrid,
> aGrid=self.aGrid)
> option.setPricingEngine(engine)
> res = {'value': option.NPV(), 'impliedVolatility': self.IV}
> else:
> # Create Black Scholes process using HV.
> process = AsianOptionPrice.bs_process(
> self, create_process_for_iv=False, use_IV=False)
> engine = ql.FdBlackScholesAsianEngine(
> process, tGrid=self.tGrid, xGrid=self.xGrid,
> aGrid=self.aGrid)
> option.setPricingEngine(engine)
> res = {'value': option.NPV()}
> return res
>
> def evaluate_asian_npv(self):
> Option = AsianOptionPrice.asian(self)
> process = AsianOptionPrice.bs_process(
> self, create_process_for_iv=False, use_IV=False)
> engine = ql.FdBlackScholesAsianEngine(
> process, tGrid=self.tGrid, xGrid=self.xGrid, aGrid=self.aGrid)
> Option.setPricingEngine(engine)
> res = Option.NPV()
> return(res)
>
> def calculate_greeks(self, Option=None, compute_implied_vol=None):
> today = self.value_date
> Option = AsianOptionPrice.asian(self)
> if compute_implied_vol == True:
> process = AsianOptionPrice.bs_process(
> self, create_process_for_iv=False, use_IV=True)
> else:
> process = AsianOptionPrice.bs_process(
> self, create_process_for_iv=False, use_IV=False)
> engine = ql.FdBlackScholesAsianEngine(
> process, tGrid=self.tGrid, xGrid=self.xGrid, aGrid=self.aGrid)
> Option.setPricingEngine(engine)
> option_greeks_ = {"delta": Option.delta(), "gamma": Option.gamma(
> )}
> ArgmntList = AsianOptionPrice.create_arg_list(
> self, Option, compute_implied_vol=True,
> implied_vol_am_put=self.volatility)
> DisturbedPrices = []
> arg_keys = ['Price', 'PRup', 'PVup', 'PTup']
> for i in range(4):
> DisturbedPrices.append(eval(
> "AsianOptionPrice(**ArgmntList[i]).evaluate_asian_npv()"))
> print(DisturbedPrices)
> PricesDistrbd = dict(zip(arg_keys, DisturbedPrices))
> Rho = (PricesDistrbd['PRup'] -
> PricesDistrbd['Price'])/(self.dist*100)
> vega = (PricesDistrbd['PVup'] -
> PricesDistrbd['Price'])/(self.dist*100)
> thetaDay = (PricesDistrbd['PTup'] - PricesDistrbd['Price'])
> option_greeks_.update({'rho': Rho, 'vega': vega, 'theta':
> thetaDay})
> return option_greeks_
>
>
> settings = {
> "computeHistoricalVolatiltiy": False,
> "tenantId": "380",
> "gridPoints": 149,
> "computeImpliedVolatiltiy": False,
> "runId": 3735,
> "timeSteps": 150,
> "optionTrades": [
> {
> "underlyingPrice": 98.69,
> "refNo": "OPT-1135-GVA",
> "underlyingPriceUnit": "US cents/LB",
> "valueDate": "17-03-2022",
> "fixingStartDate":"01-05-2022",
> "pastFixings":0,
> "volatility": 0.6015,
> "openQuantity": 1,
> "strikePriceUnit": "US cents/LB",
> "optionType": "Call",
> "expiryDate": "31-05-2022",
> "exType": "asian",
> "riskFreeRate": 0.8080374999999999,
> "premium": 1,
> "portfolioId": 18209,
> "marketPremium": 11.42,
> "longShort": "Long",
> "optionInstrument": "EURO USD 1.105 Call",
> "asset": "EURO USD 1.105 Call",
> "portfolioName": "Option Trades",
> "scenarioId": 8356,
> "scenarioName": "Option Valuation - Greeks",
> "strikePrice": 95,
> "maturityInYears": 0.205479452
> }
> ],
> "userId": "5263",
> "americanOptionsEngine": "CrankNicolson"
> }
>
> opt = AsianOptionPrice(**settings["optionTrades"][0])
> opt.evaluate_asian_npv()
>
> On Wed, May 18, 2022 at 9:25 PM Ashish Bansal <ash...@gm...>
> wrote:
>
>> Hi Jack,
>>
>> Please find attached the Asian option python code and the option trade
>> payload.
>>
>> Thanks for your help,
>> Regards,
>> Ashish
>>
>> On Wed, 18 May 2022 at 12:12, Jack G <jac...@gm...> wrote:
>>
>>> Hi Ashish,
>>>
>>> I've just lifted the examples from your references directly for a
>>> discrete arithmetic Asian using the FD Asian engine and it is working for
>>> me - can you share the code you are using that breaks please?
>>>
>>> Best,
>>> Jack
>>>
>>> On Wed, 18 May 2022, 13:55 Ashish Bansal, <ash...@gm...>
>>> wrote:
>>>
>>>> Hi all,
>>>>
>>>> Could somebody help with the asian options? We are using
>>>> FdBlackScholesAsianengine with DiscreteAveragingAsianOption. Online
>>>> references are given below. In DiscreteAveragingAsianOption, we need to
>>>> pass the past fixing which is suggested to be 0 for new trade. Whereas when
>>>> we are passing it as 0, we are getting the following error:
>>>> "QuantLib Error - Running average requires at least one past fixing"
>>>> ref:
>>>>
>>>> https://quantlib-python-docs.readthedocs.io/en/latest/pricing_engines.html#fdblackscholesasianengine
>>>>
>>>>
>>>> https://quantlib-python-docs.readthedocs.io/en/latest/instruments/options.html?highlight=DiscreteAveragingAsianOption#ql.DiscreteAveragingAsianOption
>>>>
>>>> Queries:
>>>> 1. What is to be passed here for new trade if not 0?
>>>> 2. For trade which is mid of averaging and has few past fixings, do we
>>>> need to pass the count of past fixing or the actual rates of the pastfixing
>>>> as an array?
>>>>
>>>> Any help will be appreciated.
>>>>
>>>> Thanks in advance
>>>> Ashish
>>>> _______________________________________________
>>>> QuantLib-users mailing list
>>>> Qua...@li...
>>>> https://lists.sourceforge.net/lists/listinfo/quantlib-users
>>>>
>>>
|