Skip to main content

MCQuantLib is a QuantLib derivative to perform Monte Carlo pricing of options.

Project description

MCQuantLib

Introduction

MCQuantLib is a derivative of Quantlib, a famous quantitative library of financial engineering. Unlike QuantLib, however, MCQuantLib focuses on Monte Carlo simulation in option pricing. It provides different kinds of Payoffs, Structures, Product models and Calendar Tools.

Why Another Monte Carlo Pricing Library

The first reason is that all handles and quotes in QuantLib make it very difficult to maintain a robust option pricing project. MCQuantLib is designed to simplify the whole procedure of pricing, enhance stability and provides user-friendly API.

The second reason is that calendar state is usually global in QuantLib, which will creates huge problems when you have couples of options for revaluation. Updating even one date may be time-consuming and may not render to correct results. MCQuantLib avoids using global date and calendar, suitable for pricing and re-valuating portfolio of options.

The third reason is that QuantLib focuses on continuously observed options, while MCQuantLib focuses on discretely observed options. This is a great difference because price of options may be different even if they have the same name in QuantLib and MCQuantLib.

The fourth reason is that MCQuantLib is much faster than Monte Carlo Engines provided by QuantLib. MCQuantLib is based on numpy and can use multiple CPU cores to simulate paths. Almost every function in MCQuantLib is fine-tuned and optimized for speed. For vanilla option, MCQuantLib can simulate 10^8 paths in 56s, or 10^6 paths in 3.4s, or 10^5 paths in 3s.

Install

Use pip install MCQuantLib to install.

Usage

Import

You should always import stochastic process module and monte carlo engine module before any pricing:

from MCQuantLib import Engine, BlackScholes, Heston
batchSize = 100
numIteration = 900
r = 0.03
q = 0
v = 0.25
dayCounter = 252
mc = Engine(batchSize, numIteration)
bs = BlackScholes(r, q, v, dayCounter)

Option Name

MCQuantLib supports coding by two kinds of style, one is called as Academy Style and another is called as QuantLib Style. The difference between them is the Academy Style coding uses array and pure number to represent dates, while QuantLib Style uses calendar and pandas.Timestamp to mark dates.

To use Academy Style, you should import the class with Option suffix, such as VanillaCallOption and UpOutOption. To use QuantLib Style, you should import the class without Option suffix, such as VanillaCall and UpOut.

Vanilla Call Option

To price a vanilla Call Option, use:

from MCQuantLib import VanillaCallOption
option = VanillaCallOption(
    spot=100,
    observationDay=np.linspace(1, 252, 252),
    strike=100
)
option.calculateValue(mc, bs, requestGreek=True)

If you prefer a QuantLib Style and want to use calendar object:

import pandas as pd
import QuantLib as ql
from MCQuantLib import VanillaCall, Calendar
calendar = Calendar(ql.Japan())
start = pd.Timestamp(2024, 7, 9)
observationDate = calendar.makeScheduleByPeriod(start, '1d', 253, True)[1:]
option = VanillaCall(start, observationDate, strike=100, calendar=calendar)
option.value(start, 100, mc, bs)

Vanilla Put Option

To price a vanilla Put Option, use:

from MCQuantLib import VanillaPutOption
option = VanillaPutOption(
    spot=100,
    observationDay=np.linspace(1, 252, 252),
    strike=100
)
option.calculateValue(mc, bs, requestGreek=True)

If you prefer a QuantLib Style and want to use calendar object:

import pandas as pd
import QuantLib as ql
from MCQuantLib import VanillaPut, Calendar
calendar = Calendar(ql.Japan())
start = pd.Timestamp(2024, 7, 9)
observationDate = calendar.makeScheduleByPeriod(start, '1d', 253, True)[1:]
option = VanillaPut(start, observationDate, strike=100, calendar=calendar)
option.value(start, 100, mc, bs)

Barrier Option

Barrier Option in MCQuantLib is different with those in QuantLib, even they may have the same name. All Barrier Options in QuantLib are continuously observed, while barrier options in MCQuantLib are all discretely observed. For knock-out options, discretely observed option usually has higher price than continuously observed ones. For knock-in options, discretely observed option usually has lower price than continuously observed ones.

Up-Out Option

To price a vanilla Up-Out Barrier Option, use:

from MCQuantLib import UpOutOption, PlainVanillaPayoff
option = UpOutOption(
    spot=100,
    barrier=120,
    rebate=0,
    observationDay=np.linspace(1, 252, 252),
    payoff=PlainVanillaPayoff(optionType=1, strike=100)
)
option.calculateValue(mc, bs, requestGreek=True)

For time-varying barrier and rebate, pass in an array to barrier:

from MCQuantLib import UpOutOption, PlainVanillaPayoff
optionTimeVaryingBarrier = UpOutOption(
    spot=100,
    barrier=np.linspace(110, 120, 252),
    rebate=np.linspace(0, 3, 252),
    observationDay=np.linspace(1, 252, 252),
    payoff=PlainVanillaPayoff(optionType=1, strike=100)
)
optionTimeVaryingBarrier.calculateValue(mc, bs)

If you prefer a QuantLib Style and want to use calendar object:

import pandas as pd
import QuantLib as ql
from MCQuantLib import UpOut, Calendar, PlainVanillaPayoff
calendar = Calendar(ql.Japan())
start = pd.Timestamp(2024, 7, 9)
knockOutObservationDate = calendar.makeScheduleByPeriod(start, '1M', 13, True)[1:]
option = UpOut(start, 120, 10, knockOutObservationDate, payoff=PlainVanillaPayoff(strike=100, optionType=1),calendar=calendar)
option.value(start, 100, mc, bs)

Up-In Option

To price a vanilla Up-In Barrier Option, use:

from MCQuantLib import UpInOption, PlainVanillaPayoff
option = UpInOption(
    spot=100,
    barrier=120,
    rebate=0,
    observationDay=np.linspace(1, 252, 252),
    payoff=PlainVanillaPayoff(optionType=1,strike=100)
)
option.calculateValue(mc, bs, requestGreek=True)

If you prefer a QuantLib Style and want to use calendar object:

import pandas as pd
import QuantLib as ql
from MCQuantLib import UpIn, Calendar, PlainVanillaPayoff
calendar = Calendar(ql.Japan())
start = pd.Timestamp(2024, 7, 9)
knockOutObservationDate = calendar.makeScheduleByPeriod(start, '1M', 13, True)[1:]
option = UpIn(start, 120, 10, knockOutObservationDate, payoff=-PlainVanillaPayoff(strike=100, optionType=-1),calendar=calendar)
option.value(start, 100, mc, bs)

Down-Out Option

To price a vanilla Down-Out Barrier Option, use:

from MCQuantLib import DownOutOption, PlainVanillaPayoff
option = DownOutOption(
    spot=100,
    barrier=80,
    rebate=0,
    observationDay=np.linspace(1, 252, 252),
    payoff=PlainVanillaPayoff(optionType=1, strike=100)
)
option.calculateValue(mc, bs, requestGreek=True)

If you prefer a QuantLib Style and want to use calendar object:

import pandas as pd
import QuantLib as ql
from MCQuantLib import DownOut, Calendar, PlainVanillaPayoff
calendar = Calendar(ql.Japan())
start = pd.Timestamp(2024, 7, 9)
knockOutObservationDate = calendar.makeScheduleByPeriod(start, '1M', 13, True)[1:]
option = DownOut(start, 87, 10, knockOutObservationDate, payoff=PlainVanillaPayoff(strike=100, optionType=-1),calendar=calendar)
option.value(start, 100, mc, bs)

Down-In Option

To price a vanilla Down-In Barrier Option, use:

from MCQuantLib import DownInOption, PlainVanillaPayoff
option = DownInOption(
    spot=100,
    barrier=80,
    rebate=0,
    observationDay=np.linspace(1, 252, 252),
    payoff=PlainVanillaPayoff(optionType=1, strike=100)
)
option.calculateValue(mc, bs, requestGreek=True)

If you prefer a QuantLib Style and want to use calendar object:

import pandas as pd
import QuantLib as ql
from MCQuantLib import DownIn, Calendar, PlainVanillaPayoff
calendar = Calendar(ql.Japan())
start = pd.Timestamp(2024, 7, 9)
knockOutObservationDate = calendar.makeScheduleByPeriod(start, '1M', 13, True)[1:]
option = DownIn(start, 87, 10, knockOutObservationDate, payoff=-PlainVanillaPayoff(strike=100, optionType=-1),calendar=calendar)
option.value(start, 100, mc, bs)

Double-Out Option

To price a vanilla Double-Out Barrier Option, use:

from MCQuantLib import DoubleOutOption, PlainVanillaPayoff
option = DoubleOutOption(
    spot=100,
    barrierUp=120,
    barrierDown=80,
    observationDayUp=np.linspace(1, 252, 252),
    observationDayDown=np.linspace(1, 252, 252),
    payoff=PlainVanillaPayoff(optionType=1, strike=100),
    rebateUp=1,
    rebateDown=2
)
option.calculateValue(mc, bs, requestGreek=True)

If you prefer a QuantLib Style and want to use calendar object:

import pandas as pd
import QuantLib as ql
from MCQuantLib import DoubleOut, Calendar, PlainVanillaPayoff
calendar = Calendar(ql.Japan())
start = pd.Timestamp(2024, 7, 9)
knockOutObservationDateUp = calendar.makeScheduleByPeriod(start, '1M', 13, True)[1:]
knockOutObservationDateDown = calendar.makeScheduleByPeriod(start, '2M', 7, True)[1:]
option = DoubleOut(
    spot=100,
    barrierUp=120,
    barrierDown=80,
    observationDayUp=knockOutObservationDateUp,
    observationDayDown=knockOutObservationDateDown,
    payoff=PlainVanillaPayoff(strike=100, optionType=1),
    rebateUp=3,
    rebateDown=2
)
option.value(start, 100, mc, bs)

Double-In Option

To price a vanilla Double-In Barrier Option, use:

from MCQuantLib import DoubleInOption, PlainVanillaPayoff
option = DoubleInOption(
    spot=100,
    barrierUp=120,
    barrierDown=80,
    observationDayUp=np.linspace(1, 252, 21),
    observationDayDown=np.linspace(1, 252, 252),
    rebate=2,
    payoff=PlainVanillaPayoff(optionType=1, strike=100)
)
option.calculateValue(mc, bs, requestGreek=True)

If you prefer a QuantLib Style and want to use calendar object:

import pandas as pd
import QuantLib as ql
from MCQuantLib import DoubleIn, Calendar, PlainVanillaPayoff
calendar = Calendar(ql.Japan())
start = pd.Timestamp(2024, 7, 9)
knockInObservationDateUp = calendar.makeScheduleByPeriod(start, '1M', 13, True)[1:]
knockInObservationDateDown = calendar.makeScheduleByPeriod(start, '2M', 7, True)[1:]
option = DoubleIn(
    spot=100,
    barrierUp=120,
    barrierDown=80,
    observationDayUp=knockInObservationDateUp,
    observationDayDown=knockInObservationDateDown,
    payoff=PlainVanillaPayoff(strike=100, optionType=1),
    rebateUp=3,
    rebateDown=2
)
option.value(start, 100, mc, bs)

SnowBall Option

To price a Snow Ball Option, use:

from MCQuantLib import SnowBallOption
option = SnowBallOption(
    spot=100,
    upperBarrierOut=105,
    lowerBarrierIn=80,
    observationDayIn=np.linspace(1, 252, 252),
    observationDayOut=np.linspace(1, 252, 12),
    rebateOut=np.linspace(1, 15, 12),
    fullCoupon=15
)
option.calculateValue(mc, bs, requestGreek=True)

If you prefer a QuantLib Style and want to use calendar object:

import pandas as pd
import QuantLib as ql
from MCQuantLib import SnowBall, PlainVanillaPayoff, Calendar
# instantiate a Calendar object so you can use it to generate periodic pd.Timestamp array
calendar = Calendar(ql.Japan())
# this should be a trading day
start = pd.Timestamp(2024, 7, 9)
assert calendar.isTrading(start)
# monthly trading dates excluding the start
monthlyDates = calendar.makeScheduleByPeriod(start, "1m", 13)[1:]
# a short put payoff
shortPut = - PlainVanillaPayoff(optionType=-1, strike=100)
# instantiate the structured product
option = SnowBall(
    startDate=start, initialPrice=100, knockOutBarrier=105,
    knockOutObservationDate=monthlyDates, knockInBarrier=80, knockInObservationDate="daily",
    knockInPayoff=shortPut, knockOutCouponRate=0.15,
    maturityCouponRate=0.15, calendar=calendar
)
# value the contract given day and spot price
option.value(pd.Timestamp(2024, 8, 8), 102, False, mc, bs)

Caller

Caller is used to decide how to parallel your Monte Carlo simulation. It is an attribute function of Engine object. The default caller is roughly equivalent to:

def caller(calc, seedSequence, /, **kwargs):
    calc = joblib.delayed(calc)
    kwargs["n_jobs"] = cpu_counts() if "n_jobs" not in kwargs else kwargs["n_jobs"]
    with joblib.Parallel(**kwargs) as parallel:
        res = parallel(calc(seed) for seed in seedSequence)
    return res

To implement Monte Carlo with your own caller, for example one with no parallel computation, define the caller as follows:

def selfDefinedCaller(calc, seedSequence):
    return [calc(s) for s in seedSequence]

You may also set the default caller so that you do not need to specify caller every time calculate is called.

mc.caller = selfDefinedCaller

To revert to the joblib caller, delete the caller or set it to None:

del mc.caller
mc.caller == None  # True

Process

Besides Black-Scholes process, MCQuantLib also provides Heston process to simulate market dynamic. To use Heston model to price your option, you should first create a Heston process object, and pass it as parameter of value or calculateValue function. For example:

from MCQuantLib import Heston
hst = Heston(0.017, 0, -0.07196, 0.05093, 13.3601, 1.0394, 0.05073, 252)
# ... There is a lot of other codes here
option.value(start, 100, mc, hst)
# or you can:
option.calculateValue(mc, hst, requestGreek=True)

Self-Defined Option

To price a self-defined option, the only thing you need to do is to inherit from class InstrumentMC and overwrite _setSpot and pvLogPath. You should also make sure your self-defined class has attribute named as _simulatedTimeArray. We will introduce these attributes as follows:

Inherit

You should create new class like:

import numpy as np
from numbers import Number
from MCQuantLib import InstrumentMC
class SelfDefinedOption(InstrumentMC):
    def __init__(self, spot: Number, observationDay: np.ndarray, optionParameter: Any):
        self._spot = spot
        self.observationDay = observationDay
        self._simulatedTimeArray = np.append([0], observationDay)
        self.optionParameter = optionParameter

Class InstrumentMC does not has __init__ function. So you have to write your own one. It is important to make sure you have spot as a parameter of __init__.

Attribute: _simulatedTimeArray

This parameter tells MCQuantLib how to handle date. The random generator will generate random numbers according to this array. You should have at least one parameter named as observationDay, typed as np.ndarray, because it is the base of your _simulatedTimeArray. By default, you should write it in __init__ as:

def __init__(self, spot: Number, observationDay: np.ndarray, optionParameter: Any):
    # ...
    self._simulatedTimeArray = np.append([0], observationDay)

Attribute Function: _setSpot

This function tells MCQuantLib how to initialize prices, such as spot price, barrier price and other price. By default, you should write it like:

def _setSpot(self, value: Number) -> None:
    self._spot = value

Attribute Function: pvLogPath

This is the most important function when pricing your self-defined option. It accepts logPath array and discountFactor array, and returns a scalar as price of this option. For vanilla option, this function looks like:

def pvLogPath(self, logPath: np.ndarray, discountFactor: np.ndarray) -> Number:
    discountFactorTerminal = discountFactor[-1]
    payoffTerminal = self.payoff(np.exp(logPath[:, -1]) * self.spot)
    return np.sum(payoffTerminal * discountFactorTerminal) / len(logPath)

Make sure you understand the mean discounted value of payoff in every path should be the price of your option. Based on this fact, you should design your own pvLogPath.

Example

Let's show an example to price a self-defined option. This option will give you payoff as max(pricePath) - min(pricePath). If price of the stock goes as high as 135 at some time before expiration day, and goes as low as 87 at another time. This option will give you 135 - 87 at expiration day. Let's call it as MaxMinOption and price it:

import numpy as np
from numbers import Number
from MCQuantLib import InstrumentMC
class MaxMinOption(InstrumentMC):
    def __init__(self, spot: Number, observationDay: np.ndarray):
        self._spot = spot
        self.observationDay = observationDay
        self._simulatedTimeArray = np.append([0], observationDay)

    def _setSpot(self, value: Number) -> None:
        self._spot = value

    def pvLogPath(self, logPath: np.ndarray, discountFactor: np.ndarray) -> Number:
        discountFactorTerminal = discountFactor[-1]
        price = np.exp(logPath) * self.spot
        maxPrice = np.max(price, axis=1)
        minPrice = np.min(price, axis=1)
        payoffTerminal = maxPrice - minPrice
        return np.sum(payoffTerminal * discountFactorTerminal) / len(logPath)

Then import necessary module before price it:

from MCQuantLib import Engine, BlackScholes
batchSize = 100
numIteration = 900
r = 0.03
q = 0
v = 0.25
dayCounter = 252
mc = Engine(batchSize, numIteration)
bs = BlackScholes(r, q, v, dayCounter)

The last thing is creating an object and price it:

maxMin = MaxMinOption(100, np.array(range(1, 253)))
maxMin.calculateValue(mc, bs, requestGreek=True)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mcquantlib-0.1.23.tar.gz (15.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mcquantlib-0.1.23-py3-none-any.whl (6.7 kB view details)

Uploaded Python 3

File details

Details for the file mcquantlib-0.1.23.tar.gz.

File metadata

  • Download URL: mcquantlib-0.1.23.tar.gz
  • Upload date:
  • Size: 15.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.4

File hashes

Hashes for mcquantlib-0.1.23.tar.gz
Algorithm Hash digest
SHA256 a27555ab8ddb4f32c41ba205503f8f7df189aa9d743a58d8076bf3717870708c
MD5 08f5d2687c839bd70988a11f494b2ee6
BLAKE2b-256 b52441d22445a4c1ee943482d254c2b4772367f536c80f8599a69cb15cc10250

See more details on using hashes here.

File details

Details for the file mcquantlib-0.1.23-py3-none-any.whl.

File metadata

  • Download URL: mcquantlib-0.1.23-py3-none-any.whl
  • Upload date:
  • Size: 6.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.4

File hashes

Hashes for mcquantlib-0.1.23-py3-none-any.whl
Algorithm Hash digest
SHA256 e4843935d7264486b3d5c17fb60ce3555c970792d30a75c7c6c09e9ee2de3c5d
MD5 98f8eb421a5f04c4b7cfe27527d7f31c
BLAKE2b-256 62e15ac2bd960772a5c9f30fd7b2e8e4edc36bec4d0900f578167f2965f02339

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page