From ab1d03de42e3eb53196e8c8ff3536a405bd0d6d0 Mon Sep 17 00:00:00 2001 From: finestday <179567039+finestday@users.noreply.github.com> Date: Thu, 20 Mar 2025 18:50:43 +0800 Subject: [PATCH] Added trinomial tree --- development_testing.py | 12 ++- quantsbin/derivativepricing/namesnmapper.py | 21 ++-- quantsbin/derivativepricing/pricingmodels.py | 106 +++++++++++++++++++ 3 files changed, 128 insertions(+), 11 deletions(-) diff --git a/development_testing.py b/development_testing.py index 09c6b5b..cf91733 100644 --- a/development_testing.py +++ b/development_testing.py @@ -128,6 +128,16 @@ # print(eqOption_BSM_test_pricer.risk_parameters_num()) # print(eqOption_BSM_test_pricer.risk_parameters()) +# # +# """EqOption using Trinomial""" +# eqOption_BSM_test = qbdp.EqOption(option_type='Call', strike=100, expiry_date='20180630', expiry_type='American') +# eqOption_BSM_test_pricer = eqOption_BSM_test.engine(model="Trinomial", spot0=110, pricing_date='20180531', volatility=.25, +# rf_rate=.05, div_list=div_list, yield_div=0.01, seed=12) +# +# print(eqOption_BSM_test_pricer.valuation()) +# print(eqOption_BSM_test_pricer.risk_parameters_num()) +# print(eqOption_BSM_test_pricer.risk_parameters()) + # """FutOption using Binomial""" # eqOption_BSM_test = qbdp.FutOption(option_type='Call', strike=100, expiry_date='20180630', expiry_type='American') # eqOption_BSM_test_pricer = eqOption_BSM_test.engine(model="Binomial", fwd0=110, pricing_date='20180531', volatility=.25, @@ -225,4 +235,4 @@ print(delta) delta.show() -""" \ No newline at end of file +""" diff --git a/quantsbin/derivativepricing/namesnmapper.py b/quantsbin/derivativepricing/namesnmapper.py index ede6015..af57e06 100644 --- a/quantsbin/derivativepricing/namesnmapper.py +++ b/quantsbin/derivativepricing/namesnmapper.py @@ -24,7 +24,7 @@ class PricingModel(Enum): MC_GBM = "MC_GBM" MC_GBM_LSM = "MC_GBM_LSM" BINOMIAL = "Binomial" - + TRINOMIAL = "Trinomial" class UnderlyingParameters(Enum): SPOT = "spot0" @@ -72,17 +72,17 @@ class DivType(Enum): OBJECT_MODEL = { UdlType.STOCK.value: {ExpiryType.EUROPEAN.value: [PricingModel.BLACKSCHOLESMERTON.value, PricingModel.MC_GBM.value - , PricingModel.BINOMIAL.value], - ExpiryType.AMERICAN.value: [PricingModel.MC_GBM.value, PricingModel.BINOMIAL.value]} + , PricingModel.BINOMIAL.value, PricingModel.TRINOMIAL.value], + ExpiryType.AMERICAN.value: [PricingModel.MC_GBM.value, PricingModel.BINOMIAL.value, PricingModel.TRINOMIAL.value]} , UdlType.FUTURES.value: {ExpiryType.EUROPEAN.value: [PricingModel.BLACK76.value, PricingModel.MC_GBM.value - , PricingModel.BINOMIAL.value], - ExpiryType.AMERICAN.value: [PricingModel.MC_GBM.value, PricingModel.BINOMIAL.value]} + , PricingModel.BINOMIAL.value, PricingModel.TRINOMIAL.value], + ExpiryType.AMERICAN.value: [PricingModel.MC_GBM.value, PricingModel.BINOMIAL.value, PricingModel.TRINOMIAL.value]} , UdlType.FX.value: {ExpiryType.EUROPEAN.value: [PricingModel.GK.value, PricingModel.MC_GBM.value - , PricingModel.BINOMIAL.value], - ExpiryType.AMERICAN.value: [PricingModel.MC_GBM.value, PricingModel.BINOMIAL.value]} + , PricingModel.BINOMIAL.value, PricingModel.TRINOMIAL.value], + ExpiryType.AMERICAN.value: [PricingModel.MC_GBM.value, PricingModel.BINOMIAL.value, PricingModel.TRINOMIAL.value]} , UdlType.COMMODITY.value: {ExpiryType.EUROPEAN.value: [PricingModel.GK.value, PricingModel.MC_GBM.value - , PricingModel.BINOMIAL.value], - ExpiryType.AMERICAN.value: [PricingModel.MC_GBM.value, PricingModel.BINOMIAL.value]} + , PricingModel.BINOMIAL.value, PricingModel.TRINOMIAL.value], + ExpiryType.AMERICAN.value: [PricingModel.MC_GBM.value, PricingModel.BINOMIAL.value, PricingModel.TRINOMIAL.value]} } DEFAULT_MODEL = { @@ -115,5 +115,6 @@ class DivType(Enum): PricingModel.BLACK76.value: pm.B76, PricingModel.GK.value: pm.GK, PricingModel.MC_GBM.value: pm.MonteCarloGBM, - PricingModel.BINOMIAL.value: pm.BinomialModel + PricingModel.BINOMIAL.value: pm.BinomialModel, + PricingModel.TRINOMIAL.value: pm.TrinomialModel } diff --git a/quantsbin/derivativepricing/pricingmodels.py b/quantsbin/derivativepricing/pricingmodels.py index 802b6a0..d2a9ac8 100644 --- a/quantsbin/derivativepricing/pricingmodels.py +++ b/quantsbin/derivativepricing/pricingmodels.py @@ -3,6 +3,9 @@ """ +# ExtraLine1 +# ExtraLine2 +# ExtraLine3 from abc import ABCMeta, abstractmethod from datetime import datetime as dt from math import log, sqrt @@ -476,4 +479,107 @@ def valuation(self): def risk_parameters(self): pass +class TrinomialModel(Model): + """ + This is the generalised Trinomial model used for valuation calculation for both European and American type + Args required: + instrument = Instrument parameters mapped from instrument module + spot0 = (Float) e.g. 110.0 + rf_rate = (Float < 1) e.g. 0.2 + cnv_yield = (Float < 1) e.g. 0.3 + cost_yield = (Float < 1) e.g. 0.2 + volatility = (Float < 1) e.g. 0.25 + pricing_date = (Date in string format "YYYYMMDD") e.g. 10 Dec 2018 as "20181210" + no_of_steps = (Integer). Number of steps (nodes) for the premium calculation e.g. 100 + div_list = (List). list of tuples with Ex-Dates and Dividend amounts. e.g. [('20180625',0.2),('20180727',0.6)] + + """ + + def __init__(self, instrument, spot0=None, rf_rate=0, cnv_yield=0, cost_yield=0, + volatility=None, pricing_date=None, no_of_steps=None, div_list=None, **kwargs): + self.instrument = instrument + self.spot0 = spot0 or 0.0001 + self.rf_rate = rf_rate or 0 + self.cnv_yield = cnv_yield or 0 + self.cost_yield = cost_yield or 0 + self.volatility = volatility or 0.10 + self._pricing_date = dt.strptime(pricing_date, '%Y%m%d') + self.no_of_steps = no_of_steps or 100 + self.div_list = div_list + self.cache_node = {} + + @property + def drift(self): + return self.rf_rate + self.cost_yield - self._cnv_yield + + @property + def div_processed(self): + return dividend_processor(self.div_list, self._pricing_date, self.instrument.expiry_date) + + @property + def spot_update(self): + return self.spot0 - pv_div(self.div_processed, 0, self.rf_rate) + + @property + def t_delta(self): + return self.maturity/self.no_of_steps + + @property + def up_mult(self): + return e**(self.volatility*((2 * self.t_delta) ** 0.5)) + + @property + def up_prob(self): + return (1/6 + (self.drift - 0.5 * self.volatility**2 ) * (self.t_delta/(12*self.volatility**2))**0.5 ) + + @property + def dn_prob(self): + return (1/6 - (self.drift - 0.5 * self.volatility**2 ) * (self.t_delta/(12*self.volatility**2))**0.5 ) + + @property + def md_prob(self): + return 2/3 + + @property + def step_discount_fact(self): + return e**(-1*self.rf_rate * self.t_delta) + + def node_value_store(self, pv, intrinsic_value): + if self.instrument.expiry_type == ExpiryType.AMERICAN.value: + return max(pv, intrinsic_value) + else: + return pv + + def intrinsic_value(self, _spot): + return max(self.option_flag * (_spot - self.instrument.strike), 0.0) + + def calc_spot(self, step_no, no_up): + return self.spot_update * (self.up_mult**(1*no_up - 0*step_no)) + \ + pv_div(self.div_processed, self.t_delta * step_no, self.rf_rate) + + def node_value(self, step_no, no_up): + cache_node_key = (step_no, no_up) + if cache_node_key in self.cache_node: + return self.cache_node[cache_node_key] + else: + _spot = self.calc_spot(step_no, no_up) + _intrinsic_value = self.intrinsic_value(_spot) + + if step_no >= self.no_of_steps: + return _intrinsic_value + else: + _pv = ((self.up_prob*self.node_value(step_no+1, no_up+1)) + + (self.md_prob*self.node_value(step_no+1, no_up)) + + (self.dn_prob*self.node_value(step_no+1, no_up-1)) + )*self.step_discount_fact + _node_value = self.node_value_store(_pv,_intrinsic_value) + self.cache_node[cache_node_key] = _node_value + return self.cache_node[cache_node_key] + + def valuation(self): + return self.node_value(0, 0) + + def risk_parameters(self): + pass +