Skip to content

Commit

Permalink
Merge pull request #33 from SebastienEveno/add_docstrings
Browse files Browse the repository at this point in the history
Add docstrings
  • Loading branch information
SebastienEveno authored Apr 10, 2023
2 parents b8b4cb0 + 36938a2 commit b79d0bb
Show file tree
Hide file tree
Showing 13 changed files with 530 additions and 37 deletions.
2 changes: 1 addition & 1 deletion exotx/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.6.3"
__version__ = "0.6.4"
14 changes: 14 additions & 0 deletions exotx/helpers/dates.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@


def convert_maturity_to_ql_date(maturity: Union[str, datetime, ql.Date], string_format: str = '%Y-%m-%d') -> ql.Date:
"""
Converts a maturity date in various formats to a QuantLib Date object.
This function accepts input dates as strings, Python datetime objects, or QuantLib Date objects and
returns the corresponding QuantLib Date object.
:param maturity: The maturity date to be converted, which can be a string, datetime, or QuantLib Date object.
:type maturity: Union[str, datetime, ql.Date]
:param string_format: The date format for string input, defaults to '%Y-%m-%d'.
:type string_format: str, optional
:return: The converted maturity date as a QuantLib Date object.
:rtype: ql.Date
:raises TypeError: If the input maturity type is not valid.
"""
if isinstance(maturity, ql.Date):
return maturity
elif isinstance(maturity, datetime):
Expand Down
109 changes: 97 additions & 12 deletions exotx/instruments/asian_option.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,26 @@


class AsianOption(Instrument):
"""
A class representing an Asian option, which is an option type that derives its value from the average price of
the underlying asset over a specified period.
The AsianOption class extends the Instrument class and provides functionality for constructing and pricing
Asian options using the QuantLib library.
Attributes:
strike (float): The strike price of the option.
maturity (Union[str, datetime]): The maturity date of the option as a string or datetime object.
option_type (Union[str, OptionType]): The type of the option (call or put) as a string or OptionType object.
average_type (Union[str, AverageType]): The averaging type of the option as a string or AverageType object.
average_calculation (Union[str, AverageCalculation]): The average calculation method for the option as a string or AverageCalculation object.
average_convention (Union[str, AverageConvention]): The averaging convention of the option as a string or AverageConvention object.
arithmetic_running_accumulator (float, optional): The arithmetic running accumulator for the option. Defaults to 0.0.
geometric_running_accumulator (float, optional): The geometric running accumulator for the option. Defaults to 1.0.
past_fixings (int, optional): The number of past fixings for the option. Defaults to 0.
future_fixing_dates (List[datetime], optional): A list of future fixing dates for the option. Defaults to None.
"""

def __init__(self,
strike: float, maturity: Union[str, datetime], option_type: Union[str, OptionType],
average_type: Union[str, AverageType], average_calculation: Union[str, AverageCalculation],
Expand All @@ -28,15 +48,31 @@ def __init__(self,
self.maturity = convert_maturity_to_ql_date(maturity)
self.option_type = convert_option_type_to_ql(option_type)
self.average_type = convert_average_type_to_ql(average_type)
self.average_calculation: AverageCalculation = convert_average_calculation(average_calculation)
self.average_convention: AverageConvention = convert_average_convention(average_convention)
self.average_calculation: AverageCalculation = convert_average_calculation(
average_calculation)
self.average_convention: AverageConvention = convert_average_convention(
average_convention)
self.arithmetic_running_accumulator = arithmetic_running_accumulator
self.geometric_running_accumulator = geometric_running_accumulator
self.past_fixings = past_fixings
self.future_fixing_dates = None if not future_fixing_dates else [ql.Date().from_date(future_fixing_date)
for future_fixing_date in future_fixing_dates]

def price(self, market_data, static_data, pricing_config: PricingConfiguration, seed: int = 1) -> dict:
"""
Prices the Asian option using the given market data, static data, and pricing configuration.
:param market_data: An object containing the market data needed to price the option.
:type market_data: MarketData
:param static_data: An object containing the static data needed to price the option.
:type static_data: StaticData
:param pricing_config: A configuration object for the pricing process.
:type pricing_config: PricingConfiguration
:param seed: An optional integer seed for random number generation. Defaults to 1.
:type seed: int
:return: A dictionary containing the option price and, if applicable, Greeks.
:rtype: dict
"""
reference_date: ql.Date = market_data.get_ql_reference_date()
ql.Settings.instance().evaluationDate = reference_date

Expand All @@ -49,7 +85,8 @@ def price(self, market_data, static_data, pricing_config: PricingConfiguration,
ql_payoff = ql.PlainVanillaPayoff(self.option_type, self.strike)
ql_exercise = ql.EuropeanExercise(self.maturity)
if self.average_calculation == AverageCalculation.CONTINUOUS:
ql_option = ql.ContinuousAveragingAsianOption(self.average_type, ql_payoff, ql_exercise)
ql_option = ql.ContinuousAveragingAsianOption(
self.average_type, ql_payoff, ql_exercise)
elif self.average_calculation == AverageCalculation.DISCRETE:
if self.average_type == ql.Average().Arithmetic:
ql_option = ql.DiscreteAveragingAsianOption(self.average_type,
Expand All @@ -61,12 +98,15 @@ def price(self, market_data, static_data, pricing_config: PricingConfiguration,
self.past_fixings, self.future_fixing_dates, ql_payoff,
ql_exercise)
else:
raise ValueError(f"Invalid average type \"{self.average_type}\"")
raise ValueError(
f"Invalid average type \"{self.average_type}\"")
else:
raise ValueError(f"Invalid average calculation \"{self.average_calculation}\"")
raise ValueError(
f"Invalid average calculation \"{self.average_calculation}\"")

# set the pricing engine
ql_engine = self._get_ql_pricing_engine(market_data, static_data, pricing_config, seed)
ql_engine = self._get_ql_pricing_engine(
market_data, static_data, pricing_config, seed)
ql_option.setPricingEngine(ql_engine)

# price
Expand All @@ -91,20 +131,23 @@ def _get_ql_pricing_engine(self, market_data, static_data, pricing_config: Prici
elif self.average_convention == AverageConvention.STRIKE:
return ql.AnalyticDiscreteGeometricAverageStrikeAsianEngine(process)
else:
raise ValueError(f"Invalid average convention \"{self.average_convention}\"")
raise ValueError(
f"Invalid average convention \"{self.average_convention}\"")
elif pricing_config.numerical_method == NumericalMethod.MC:
# TODO: filter on pricing_config.pricing_model, here we assume black-scholes only
bs_model = BlackScholesModel(market_data, static_data)
process = bs_model.setup()
random_number_generator = str(pricing_config.random_number_generator)
random_number_generator = str(
pricing_config.random_number_generator)
if self.average_convention == AverageConvention.PRICE:
return ql.MCDiscreteGeometricAPEngine(process, random_number_generator)
elif self.average_convention == AverageConvention.STRIKE:
return ValueError(
f"No corresponding engine for asian option for numerical method {pricing_config.numerical_method}, "
f"average type {self.average_type}, average calculation {self.average_calculation}, and average convention {self.average_convention}")
else:
raise ValueError(f"Invalid average convention \"{self.average_convention}\"")
raise ValueError(
f"Invalid average convention \"{self.average_convention}\"")
else:
raise ValueError(
f"No engine for asian option with numerical method {pricing_config.numerical_method}"
Expand All @@ -113,13 +156,15 @@ def _get_ql_pricing_engine(self, market_data, static_data, pricing_config: Prici
if pricing_config.numerical_method == NumericalMethod.MC:
bs_model = BlackScholesModel(market_data, static_data)
process = bs_model.setup()
random_number_generator = str(pricing_config.random_number_generator)
random_number_generator = str(
pricing_config.random_number_generator)
if self.average_convention == AverageConvention.PRICE:
return ql.MCDiscreteArithmeticAPEngine(process, random_number_generator)
elif self.average_convention == AverageConvention.STRIKE:
return ql.MCDiscreteArithmeticASEngine(process, random_number_generator)
else:
raise ValueError(f"Invalid average convention \"{self.average_convention}\"")
raise ValueError(
f"Invalid average convention \"{self.average_convention}\"")
else:
raise ValueError(f"Invalid average type {self.average_type}")
elif self.average_calculation == AverageCalculation.CONTINUOUS:
Expand All @@ -136,7 +181,8 @@ def _get_ql_pricing_engine(self, market_data, static_data, pricing_config: Prici
else:
raise ValueError("No engine for asian option")
else:
raise ValueError(f"Invalid average calculation \"{self.average_calculation}\"")
raise ValueError(
f"Invalid average calculation \"{self.average_calculation}\"")

# region serialization/deserialization
def to_json(self):
Expand All @@ -153,6 +199,34 @@ def from_json(cls, json_data):

# region Schema
class AsianOptionSchema(Schema):
"""
AsianOptionSchema is a Marshmallow schema class for deserializing and validating JSON data into an AsianOption object.
This schema defines the required fields for an AsianOption object and validates their types and values. It also provides
a post_load method to create an AsianOption object after deserialization and validation.
Fields:
- strike (float): The option's strike price.
- maturity (date): The option's maturity date in the format "YYYY-MM-DD".
- option_type (OptionType): The option type, either "call" or "put".
- average_type (AverageType): The averaging type, either "arithmetic" or "geometric".
- average_calculation (AverageCalculation): The averaging calculation, either "continuous" or "discrete".
- average_convention (AverageConvention): The average convention, either "price" or "strike".
Example usage:
>>> asian_option_data = {
... "strike": 100.0,
... "maturity": "2023-05-10",
... "option_type": "call",
... "average_type": "arithmetic",
... "average_calculation": "continuous",
... "average_convention": "price"
... }
>>> schema = AsianOptionSchema()
>>> result = schema.load(asian_option_data)
>>> asian_option = result.data
"""
strike = fields.Float()
maturity = fields.Date(format="%Y-%m-%d")
option_type = OptionTypeField()
Expand All @@ -162,5 +236,16 @@ class AsianOptionSchema(Schema):

@post_load
def make_asian_option(self, data, **kwargs) -> AsianOption:
"""
Creates an AsianOption object after deserialization and validation of JSON data.
This method is called after the JSON data has been deserialized and validated against the schema. It constructs
an AsianOption object using the deserialized data.
:param data: The deserialized and validated JSON data.
:type data: dict
:return: The created AsianOption object.
:rtype: AsianOption
"""
return AsianOption(**data)
# endregion
Loading

0 comments on commit b79d0bb

Please sign in to comment.