fixing and finishing greeks
This commit is contained in:
parent
a0a672cf0d
commit
46617620a4
@ -1 +1,2 @@
|
||||
pub mod portfolio;
|
||||
pub mod portfolio;
|
||||
pub mod options;
|
21
finlib/src/ffi/py/options.rs
Normal file
21
finlib/src/ffi/py/options.rs
Normal file
@ -0,0 +1,21 @@
|
||||
use pyo3::prelude::*;
|
||||
use crate::options::blackscholes::{OptionVariables, OptionGreeks};
|
||||
use crate::risk::portfolio::{Portfolio, PortfolioAsset};
|
||||
|
||||
#[pymethods]
|
||||
impl OptionVariables {
|
||||
#[new]
|
||||
pub fn init(underlying_price: f64,
|
||||
strike_price: f64,
|
||||
volatility: f64,
|
||||
risk_free_interest_rate: f64,
|
||||
dividend: f64,
|
||||
time_to_expiration: f64) -> Self {
|
||||
OptionVariables::from(underlying_price,
|
||||
strike_price,
|
||||
volatility,
|
||||
risk_free_interest_rate,
|
||||
dividend,
|
||||
time_to_expiration)
|
||||
}
|
||||
}
|
174
finlib/src/options/blackscholes/OptionSurface.rs
Normal file
174
finlib/src/options/blackscholes/OptionSurface.rs
Normal file
@ -0,0 +1,174 @@
|
||||
use core::ops::Range;
|
||||
use std::sync::{Arc, Mutex};
|
||||
#[cfg(feature = "parallel")]
|
||||
use rayon::prelude::*;
|
||||
use crate::options::blackscholes::OptionVariables;
|
||||
|
||||
pub struct OptionSurface {
|
||||
underlying_price: Range<isize>,
|
||||
underlying_price_bounds: (f64, f64),
|
||||
strike_price: Range<isize>,
|
||||
strike_price_bounds: (f64, f64),
|
||||
volatility: Range<isize>,
|
||||
volatility_bounds: (f64, f64),
|
||||
risk_free_interest_rate: Range<isize>,
|
||||
risk_free_interest_rate_bounds: (f64, f64),
|
||||
dividend: Range<isize>,
|
||||
dividend_bounds: (f64, f64),
|
||||
time_to_expiration: Range<isize>,
|
||||
time_to_expiration_bounds: (f64, f64)
|
||||
}
|
||||
|
||||
impl OptionSurface {
|
||||
pub fn from(underlying_price: Range<isize>,
|
||||
underlying_price_bounds: (f64, f64),
|
||||
strike_price: Range<isize>,
|
||||
strike_price_bounds: (f64, f64),
|
||||
volatility: Range<isize>,
|
||||
volatility_bounds: (f64, f64),
|
||||
risk_free_interest_rate: Range<isize>,
|
||||
risk_free_interest_rate_bounds: (f64, f64),
|
||||
dividend: Range<isize>,
|
||||
dividend_bounds: (f64, f64),
|
||||
time_to_expiration: Range<isize>,
|
||||
time_to_expiration_bounds: (f64, f64)) -> Self {
|
||||
Self {
|
||||
underlying_price,
|
||||
underlying_price_bounds,
|
||||
strike_price,
|
||||
strike_price_bounds,
|
||||
volatility,
|
||||
volatility_bounds,
|
||||
risk_free_interest_rate,
|
||||
risk_free_interest_rate_bounds,
|
||||
dividend,
|
||||
dividend_bounds,
|
||||
time_to_expiration,
|
||||
time_to_expiration_bounds,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk(self) -> Vec<OptionVariables> {
|
||||
|
||||
// #[cfg(feature = "parallel")]
|
||||
// {
|
||||
// let vec: Arc<Mutex<Vec<OptionVariables>>> = Arc::new(Mutex::new(vec![]));
|
||||
// self.underlying_price
|
||||
// .into_par_iter()
|
||||
// .for_each(|p| {
|
||||
// self.strike_price
|
||||
// .clone()
|
||||
// .into_par_iter()
|
||||
// .for_each(|s| {
|
||||
// self.volatility
|
||||
// .clone()
|
||||
// .into_par_iter()
|
||||
// .for_each(|v| {
|
||||
// self.risk_free_interest_rate
|
||||
// .clone()
|
||||
// .into_par_iter()
|
||||
// .for_each(|i| {
|
||||
// self.dividend
|
||||
// .clone()
|
||||
// .into_par_iter()
|
||||
// .for_each(|d| {
|
||||
// self.time_to_expiration
|
||||
// .clone()
|
||||
// .into_par_iter()
|
||||
// .for_each(|t| {
|
||||
// let mut m = vec.clone();
|
||||
// let mut guard = m.lock().unwrap();
|
||||
// guard.push(OptionVariables::from(
|
||||
// self.underlying_price_bounds.0 + (self.underlying_price_bounds.1 - self.underlying_price_bounds.0) * p as f64,
|
||||
// self.strike_price_bounds.0 + (self.strike_price_bounds.1 - self.strike_price_bounds.0) * s as f64,
|
||||
// self.volatility_bounds.0 + (self.volatility_bounds.1 - self.volatility_bounds.0) * v as f64,
|
||||
// self.risk_free_interest_rate_bounds.0 + (self.risk_free_interest_rate_bounds.1 - self.risk_free_interest_rate_bounds.0) * i as f64,
|
||||
// self.dividend_bounds.0 + (self.dividend_bounds.1 - self.dividend_bounds.0) * d as f64,
|
||||
// self.time_to_expiration_bounds.0 + (self.time_to_expiration_bounds.1 - self.time_to_expiration_bounds.0) * t as f64
|
||||
// ));
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
// });
|
||||
//
|
||||
// Arc::try_unwrap(vec).unwrap().into_inner().unwrap()
|
||||
// }
|
||||
// #[cfg(not(feature = "parallel"))]
|
||||
{
|
||||
let mut vec: Vec<OptionVariables> = Vec::with_capacity(
|
||||
self.underlying_price.len()
|
||||
* self.strike_price.len()
|
||||
* self.volatility.len()
|
||||
* self.risk_free_interest_rate.len()
|
||||
* self.dividend.len()
|
||||
* self.time_to_expiration.len()
|
||||
);
|
||||
for p in self.underlying_price {
|
||||
for s in self.strike_price.clone() {
|
||||
for v in self.volatility.clone() {
|
||||
for i in self.risk_free_interest_rate.clone() {
|
||||
for d in self.dividend.clone() {
|
||||
for t in self.time_to_expiration.clone() {
|
||||
let v = OptionVariables::from(
|
||||
self.underlying_price_bounds.0 + (self.underlying_price_bounds.1 - self.underlying_price_bounds.0) * p as f64,
|
||||
self.strike_price_bounds.0 + (self.strike_price_bounds.1 - self.strike_price_bounds.0) * s as f64,
|
||||
self.volatility_bounds.0 + (self.volatility_bounds.1 - self.volatility_bounds.0) * v as f64,
|
||||
self.risk_free_interest_rate_bounds.0 + (self.risk_free_interest_rate_bounds.1 - self.risk_free_interest_rate_bounds.0) * i as f64,
|
||||
self.dividend_bounds.0 + (self.dividend_bounds.1 - self.dividend_bounds.0) * d as f64,
|
||||
self.time_to_expiration_bounds.0 + (self.time_to_expiration_bounds.1 - self.time_to_expiration_bounds.0) * t as f64
|
||||
);
|
||||
vec.push(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vec
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::options::blackscholes::{CallOption, Option, PutOption};
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn walk_test() {
|
||||
let w = OptionSurface::from(
|
||||
(0 .. 50),
|
||||
(100., 200.),
|
||||
(0 .. 50),
|
||||
(100., 200.),
|
||||
(0 .. 5),
|
||||
(0.25, 0.50),
|
||||
(0 .. 10),
|
||||
(0.05, 0.08),
|
||||
(0 .. 1),
|
||||
(0.01, 0.02),
|
||||
(0 .. 10),
|
||||
(30./365.25, 30./365.25),
|
||||
);
|
||||
|
||||
let a = w.walk();
|
||||
|
||||
let options = a
|
||||
.par_iter()
|
||||
.map(|v| {
|
||||
let mut call = v.call();
|
||||
let mut put = v.put();
|
||||
|
||||
call.calc_greeks();
|
||||
put.calc_greeks();
|
||||
|
||||
(call, put)
|
||||
})
|
||||
.collect::<Vec<(CallOption, PutOption)>>();
|
||||
|
||||
let a1 = a.first();
|
||||
}
|
||||
}
|
@ -1,5 +1,14 @@
|
||||
use statrs::distribution::{ContinuousCDF, Normal};
|
||||
mod OptionSurface;
|
||||
|
||||
use statrs::distribution::{Continuous, ContinuousCDF, Normal};
|
||||
#[cfg(feature = "wasm")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
#[cfg(feature = "py")]
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[cfg_attr(feature = "wasm", wasm_bindgen)]
|
||||
#[cfg_attr(feature = "py", pyclass)]
|
||||
#[cfg_attr(feature = "ffi", repr(C))]
|
||||
#[derive(Debug, Copy, Clone, Default, PartialEq, PartialOrd)]
|
||||
pub struct OptionVariables {
|
||||
underlying_price: f64,
|
||||
@ -8,7 +17,8 @@ pub struct OptionVariables {
|
||||
risk_free_interest_rate: f64,
|
||||
dividend: f64,
|
||||
time_to_expiration: f64,
|
||||
d1: std::option::Option<f64>
|
||||
d1: std::option::Option<f64>,
|
||||
d2: std::option::Option<f64>
|
||||
}
|
||||
|
||||
impl OptionVariables {
|
||||
@ -26,7 +36,8 @@ impl OptionVariables {
|
||||
risk_free_interest_rate,
|
||||
dividend,
|
||||
time_to_expiration,
|
||||
d1: None
|
||||
d1: None,
|
||||
d2: None
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,6 +45,7 @@ impl OptionVariables {
|
||||
let n = Normal::new(0., 1.0).unwrap();
|
||||
let (d1, d2) = self.d1_d2();
|
||||
self.d1 = Some(d1);
|
||||
self.d2 = Some(d2);
|
||||
|
||||
let first = self.underlying_price * (-self.dividend * self.time_to_expiration).exp() * n.cdf(d1);
|
||||
|
||||
@ -47,6 +59,7 @@ impl OptionVariables {
|
||||
let n = Normal::new(0., 1.0).unwrap();
|
||||
let (d1, d2) = self.d1_d2();
|
||||
self.d1 = Some(d1);
|
||||
self.d2 = Some(d2);
|
||||
|
||||
let first = self.strike_price * (-self.risk_free_interest_rate * self.time_to_expiration).exp() * n.cdf(-d2);
|
||||
|
||||
@ -72,7 +85,7 @@ impl OptionVariables {
|
||||
(first + second) / denominator
|
||||
}
|
||||
|
||||
pub fn d2(&self, d1: f64, ) -> f64 {
|
||||
pub fn d2(&self, d1: f64) -> f64 {
|
||||
d1 - (self.volatility * f64::sqrt(self.time_to_expiration))
|
||||
}
|
||||
}
|
||||
@ -83,17 +96,23 @@ pub trait Option {
|
||||
fn vega(&self) -> f64;
|
||||
fn theta(&self) -> f64;
|
||||
fn rho(&self) -> f64;
|
||||
fn calc_greeks(&mut self);
|
||||
fn has_greeks(&self) -> bool;
|
||||
}
|
||||
|
||||
// #[cfg_attr(feature = "wasm", wasm_bindgen)]
|
||||
#[cfg_attr(feature = "py", pyclass)]
|
||||
#[cfg_attr(feature = "ffi", repr(C))]
|
||||
#[derive(Debug, Copy, Clone, Default, PartialEq, PartialOrd)]
|
||||
pub struct CallOption {
|
||||
pub price: f64,
|
||||
pub variables: OptionVariables
|
||||
pub variables: OptionVariables,
|
||||
pub greeks: std::option::Option<OptionGreeks>
|
||||
}
|
||||
|
||||
impl CallOption {
|
||||
pub fn from(price: f64, variables: OptionVariables) -> Self {
|
||||
Self { price, variables }
|
||||
Self { price, variables, greeks: None }
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,23 +132,50 @@ impl Option for CallOption {
|
||||
}
|
||||
|
||||
fn theta(&self) -> f64 {
|
||||
todo!()
|
||||
let n = Normal::new(0., 1.0).unwrap();
|
||||
let first = theta_first(&self.variables, &n);
|
||||
|
||||
let second = self.variables.risk_free_interest_rate
|
||||
* self.variables.strike_price
|
||||
* (-self.variables.risk_free_interest_rate * self.variables.time_to_expiration).exp()
|
||||
* n.cdf(self.variables.d2.unwrap());
|
||||
|
||||
let third = self.variables.dividend
|
||||
* self.variables.underlying_price
|
||||
* (-self.variables.dividend * self.variables.time_to_expiration).exp()
|
||||
* n.cdf(self.variables.d1.unwrap());
|
||||
|
||||
first - second + third
|
||||
}
|
||||
|
||||
fn rho(&self) -> f64 {
|
||||
todo!()
|
||||
let n = Normal::new(0., 1.0).unwrap();
|
||||
|
||||
self.variables.strike_price * self.variables.time_to_expiration * (-self.variables.risk_free_interest_rate * self.variables.time_to_expiration).exp() * n.cdf(self.variables.d2.unwrap())
|
||||
}
|
||||
|
||||
fn calc_greeks(&mut self) {
|
||||
self.greeks = Some(OptionGreeks::from(self));
|
||||
}
|
||||
|
||||
fn has_greeks(&self) -> bool {
|
||||
self.greeks.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg_attr(feature = "wasm", wasm_bindgen)]
|
||||
#[cfg_attr(feature = "py", pyclass)]
|
||||
#[cfg_attr(feature = "ffi", repr(C))]
|
||||
#[derive(Debug, Copy, Clone, Default, PartialEq, PartialOrd)]
|
||||
pub struct PutOption {
|
||||
pub price: f64,
|
||||
pub variables: OptionVariables
|
||||
pub variables: OptionVariables,
|
||||
pub greeks: std::option::Option<OptionGreeks>
|
||||
}
|
||||
|
||||
impl PutOption {
|
||||
pub fn from(price: f64, variables: OptionVariables) -> Self {
|
||||
Self { price, variables }
|
||||
Self { price, variables, greeks: None }
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,12 +195,42 @@ impl Option for PutOption {
|
||||
}
|
||||
|
||||
fn theta(&self) -> f64 {
|
||||
todo!()
|
||||
let n = Normal::new(0., 1.0).unwrap();
|
||||
let first = theta_first(&self.variables, &n);
|
||||
|
||||
let second = self.variables.risk_free_interest_rate
|
||||
* self.variables.strike_price
|
||||
* (-self.variables.risk_free_interest_rate * self.variables.time_to_expiration).exp()
|
||||
* n.cdf(-self.variables.d2.unwrap());
|
||||
|
||||
let third = self.variables.dividend
|
||||
* self.variables.underlying_price
|
||||
* (-self.variables.dividend * self.variables.time_to_expiration).exp()
|
||||
* n.cdf(-self.variables.d1.unwrap());
|
||||
|
||||
first + second - third
|
||||
}
|
||||
|
||||
fn rho(&self) -> f64 {
|
||||
todo!()
|
||||
let n = Normal::new(0., 1.0).unwrap();
|
||||
|
||||
- self.variables.strike_price * self.variables.time_to_expiration * (-self.variables.risk_free_interest_rate * self.variables.time_to_expiration).exp() * n.cdf(-self.variables.d2.unwrap())
|
||||
}
|
||||
|
||||
fn calc_greeks(&mut self) {
|
||||
self.greeks = Some(OptionGreeks::from(self));
|
||||
}
|
||||
|
||||
fn has_greeks(&self) -> bool {
|
||||
self.greeks.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
fn theta_first(v: &OptionVariables, n: &Normal) -> f64 {
|
||||
let numerator = v.underlying_price * v.volatility * (-v.dividend * v.time_to_expiration).exp();
|
||||
let denominator = 2. * f64::sqrt(v.time_to_expiration);
|
||||
|
||||
-(numerator / denominator) * n.pdf(v.d1.unwrap())
|
||||
}
|
||||
|
||||
pub fn gamma(v: &OptionVariables) -> f64 {
|
||||
@ -163,7 +239,7 @@ pub fn gamma(v: &OptionVariables) -> f64 {
|
||||
let numerator = (-v.dividend * v.time_to_expiration).exp();
|
||||
let denominator = v.underlying_price * v.volatility * f64::sqrt(v.time_to_expiration);
|
||||
|
||||
(numerator / denominator) * n.cdf(v.d1.unwrap())
|
||||
(numerator / denominator) * n.pdf(v.d1.unwrap())
|
||||
}
|
||||
|
||||
pub fn vega(v: &OptionVariables) -> f64 {
|
||||
@ -171,13 +247,39 @@ pub fn vega(v: &OptionVariables) -> f64 {
|
||||
|
||||
let numerator = (-v.dividend * v.time_to_expiration).exp();
|
||||
|
||||
v.underlying_price * numerator * f64::sqrt(v.time_to_expiration) * n.cdf(v.d1.unwrap()) / 100.
|
||||
v.underlying_price * numerator * f64::sqrt(v.time_to_expiration) * n.pdf(v.d1.unwrap())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "wasm", wasm_bindgen)]
|
||||
#[cfg_attr(feature = "py", pyclass)]
|
||||
#[cfg_attr(feature = "ffi", repr(C))]
|
||||
#[derive(Debug, Copy, Clone, Default, PartialEq, PartialOrd)]
|
||||
pub struct OptionGreeks {
|
||||
delta: f64,
|
||||
gamma: f64,
|
||||
vega: f64,
|
||||
theta: f64,
|
||||
rho: f64
|
||||
}
|
||||
|
||||
impl OptionGreeks {
|
||||
pub fn from(option: &impl Option) -> Self {
|
||||
Self {
|
||||
delta: option.delta(),
|
||||
gamma: option.gamma(),
|
||||
vega: option.vega(),
|
||||
theta: option.theta(),
|
||||
rho: option.rho()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// https://goodcalculators.com/black-scholes-calculator/
|
||||
|
||||
fn get_example_option() -> OptionVariables {
|
||||
OptionVariables::from(100., 100., 0.25, 0.05, 0.01, 30./365.25)
|
||||
}
|
||||
@ -250,4 +352,21 @@ mod tests {
|
||||
let diff = (rho - -4.060).abs();
|
||||
assert!(diff < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_theta_test() {
|
||||
let v = get_example_option();
|
||||
|
||||
let diff = (v.call().theta() - -19.300).abs();
|
||||
assert!(diff < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn put_theta_test() {
|
||||
let v = get_example_option();
|
||||
|
||||
let theta = v.put().theta();
|
||||
let diff = (theta - -15.319).abs();
|
||||
assert!(diff < 0.01);
|
||||
}
|
||||
}
|
@ -25,6 +25,20 @@ mod pyfinlib {
|
||||
}
|
||||
}
|
||||
|
||||
#[pymodule]
|
||||
mod options {
|
||||
use super::*;
|
||||
|
||||
#[pymodule_export]
|
||||
use finlib::options::blackscholes::OptionVariables;
|
||||
#[pymodule_export]
|
||||
use finlib::options::blackscholes::CallOption;
|
||||
#[pymodule_export]
|
||||
use finlib::options::blackscholes::PutOption;
|
||||
#[pymodule_export]
|
||||
use finlib::options::blackscholes::OptionGreeks;
|
||||
}
|
||||
|
||||
#[pymodule]
|
||||
mod risk {
|
||||
use super::*;
|
||||
|
Loading…
x
Reference in New Issue
Block a user