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

Finite Difference Pricer with nalgebra #212

Closed
wants to merge 16 commits into from

Conversation

yfnaji
Copy link
Contributor

@yfnaji yfnaji commented Apr 5, 2024

Important Notes

This Pull Request is regarding issue #98 for the creation of a finite difference pricer.

I have made 2 PRs for this particular issue. Only one of them needs to be merged if the decision has been made to merge.

nalgebra vs heap allocated vectors

This PR utilises the nalgebra package to represent vectors and matrices, and handle their calculations. The other PR uses heap allocated vectors instead.

The way in which the objects and methods should be used are exactly the same for both PRs.

There are differences in speed performance between the two implementations.

An example with the following parameters:

initial_price: 3.22,
strike_price: 12.87,
risk_free_rate: 0.02,
volatility: 0.2,
evaluation_date: None,
expiration_date: today() + Duration::days(365),
time_steps: 1000,
price_steps: 100,
type_flag: TypeFlag::Put,
exercise_flag: ExerciseFlag::American

We get the following runtimes when we run one instance of both implementations:

Method vec branch nalgebra (latest commit)
Explicit 18.032395ms 447.828518ms
Implicit 475.63228ms 548.617466ms
Crank-Nicolson 496.54801ms 1.034193406s

We can immediately see that the explicit method is about 17 22 24 times faster using heap allocated vectors than using the nalgebra package (this PR) and runtime for the Crank-Nicolson method is roughly halved (This is at least the case for the machine I am running the code on).

Contents of this PR

This PR implements the following features:

  • A struct, FiniteDifferencePricer in the module instruments::options::finite_difference_pricer, where the attributes are the parameters of an option, namely:

    • initial_price: f64
    • strike_price: f64
    • risk_free_rate: f64
    • volatility: f64
    • evaluation_date: Option<Date>,
    • expiration_date: Date,
    • time_steps: u32,
    • price_steps: u32,
    • type_flag: TypeFlag,
    • exercise_flag: ExerciseFlag

    where TypeFlag and ExerciseFlag are imported from instruments::options::option

  • A constructor for the above struct with the new() method which takes in the above defined attributes as arguments

  • Three public methods implemented in the FiniteDifferencePricer for the three widely known finite difference methods, namely explicit(), implicit() and crank_nicolson() which returns the option price rounded to 2 decimal places

Demonstration by example

We can start off by defining the finite difference object by calling the new() constructor

use RustQuant::instruments::options::{
    option::{TypeFlag, ExerciseFlag},
    finite_difference_pricer::FiniteDifferencePricer,
};

let finite_difference_obj = FiniteDifferencePricer::new(
    15.55,                            // initial_price
    10.2,                             // strike_price
    0.1,                              // risk_free_rate
    0.025,                            // volatility
    None,                             // evaluation_date
    today() + Duration::days(1),      // expiration_date
    1000,                             // time_steps
    100,                              // price_steps
    TypeFlag::Call,                   // type_flag
    ExerciseFlag::American            // exercise_flag
);

Now that the object has been defined, we can run any of the 3 methods to provide a numerical approximation of the option price:

println!("Explicit method: {}", finite_difference_obj.explicit());
println!("Implicit method: {}", finite_difference_obj.implicit());
println!("Crank-Nicolson: {}", finite_difference_obj.crank_nicolson());

Running the above code yields the following results:

Explicit method: 5.35
Implicit method: 5.35
Crank-Nicolson: 5.35

Notes

  1. I have opted to solve this problem using a struct to define the option parameters and from there we can run each finite difference method from the object. Alternatively, I could amend the PR so that it can be purely functional.

  2. The code utilises the Dirichlet condition for the boundaries of the stock price extremities:

If we define the following:

$$V^{m}_{n} - \text{The option price at time step } n \text{ and price step } m \text{ (with maximum } M\text{)}$$

$$T - \text{Maturity time}$$

$$K - \text{Strike price}$$

$$S_{M} - \text{Maximum price of the underlying asset}$$

$$\Delta t - \text{Time step size}$$

Then Dirichlet boundary conditions for the call options are:

$$V^{0}_{n} = 0$$

$$V^{M}_{n} = S_M - Ke^{-r(T-n\Delta t)}$$

and for the put options:

$$V^{0}_{n} =Ke^{-r(T-n\Delta t)}$$

$$V^{M}_{n} = 0$$

  1. We can also implement the option use the Neumann conditions, perhaps in another pull request in the future.

@avhz
Copy link
Owner

avhz commented Apr 25, 2024

I will close this for now, however I would be interested to see if this can be optimised further.
If I get the time, I will try myself.

@avhz avhz closed this Apr 25, 2024
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

Successfully merging this pull request may close these issues.

2 participants