Permalink
Browse files

Got Black-Scholes up and running

  • Loading branch information...
1 parent 3417d45 commit 95a067ce4ba97de148f2b892ff851c51b1b88272 @brymck committed Sep 26, 2011
Showing with 157 additions and 31 deletions.
  1. +102 −7 ext/rupee/options.c
  2. +2 −1 ext/rupee/rupee.c
  3. +1 −0 ext/rupee/rupee.h
  4. +5 −4 ext/rupee/statistics.c
  5. +1 −1 ext/rupee/statistics.h
  6. +1 −1 lib/rupee/version.rb
  7. +2 −2 rupee.gemspec
  8. +0 −15 spec/c/european_spec.rb
  9. +43 −0 spec/c/options_spec.rb
View
@@ -7,26 +7,115 @@
*
* * +call_put_flag+ - The string to check
*/
-static bool is_call(const char * call_put_flag)
+static bool is_call(const char *call_put_flag)
{
/* Returns to for everything unless it starts with a 'p' */
return (call_put_flag[0] != 'p');
}
-static double _black76(const char *call_put_flag, double F, double X, double T, double r, double v)
+static double bs(const char *call_put_flag, double S, double X, double T, double r, double q, double v)
+{
+ double d1, d2;
+
+ d1 = (log(S / X) + (r - q + v * v / 2) * T) / (v * sqrt(T));
+ d2 = d1 - v * sqrt(T);
+
+ if (is_call(call_put_flag))
+ return S * exp(-q * T) * cnd(d1) - X * exp(-r * T) * cnd(d2);
+ else
+ return X * exp(-r * T) * cnd(-d2) - S * exp(-q * T) * cnd(-d1);
+}
+
+/* call-seq:
+ * Rupee.black_scholes(call_put_flag, forward, strike_price, time_to_expiry, risk_free_rate, volatility)
+ *
+ * The Black-Scholes European call/put valuation
+ *
+ * ==== Arguments
+ *
+ * * +call_put_flag+ - Whether the instrument is a call (c) or a put (p)
+ * * +forward+ - The current forward value
+ * * +strike_price+ - The option's strike price
+ * * +time_to_expiry+ - The time to maturity in years
+ * * +risk_free_rate+ - The risk-free rate through expiry
+ * * +dividend_yield+ - The annual dividend yield
+ * * +volatility+ - The implied volatility at expiry
+ */
+static VALUE rupee_black_scholes(VALUE self, VALUE rcall_put_flag, VALUE rF, VALUE rX, VALUE rT, VALUE rr, VALUE rq, VALUE rv)
+{
+ const char *call_put_flag;
+ double F, X, T, r, q, v;
+
+ call_put_flag = StringValuePtr(rcall_put_flag);
+ F = NUM2DBL(rF);
+ X = NUM2DBL(rX);
+ T = NUM2DBL(rT);
+ r = NUM2DBL(rr);
+ q = NUM2DBL(rq);
+ v = NUM2DBL(rv);
+
+ return rb_float_new(bs(call_put_flag, F, X, T, r, q, v));
+}
+
+double gbs(const char *call_put_flag, double S, double X, double T, double r, double b, double v)
+{
+ double d1, d2;
+
+ d1 = (log(S / X) + (b + v * v / 2) * T) / (v * sqrt(T));
+ d2 = d1 - v * sqrt(T);
+
+ if (is_call(call_put_flag))
+ return S * exp((b - r) * T) * cnd(d1) - X * exp(-r * T) * cnd(d2);
+ else
+ return X * exp(-r * T) * cnd(-d2) - S * exp((b - r) * T) * cnd(-d1);
+}
+
+/* call-seq:
+ * Rupee.generalized_black_scholes(call_put_flag, forward, strike_price, time_to_expiry, risk_free_rate, volatility)
+ *
+ * The generalized Black-Scholes European call/put valuation
+ *
+ * ==== Arguments
+ *
+ * * +call_put_flag+ - Whether the instrument is a call (c) or a put (p)
+ * * +forward+ - The current forward value
+ * * +strike_price+ - The option's strike price
+ * * +time_to_expiry+ - The time to maturity in years
+ * * +risk_free_rate+ - The risk-free rate through expiry
+ * * +cost_of_carry+ - The annualized cost of carry
+ * * +volatility+ - The implied volatility at expiry
+ */
+static VALUE rupee_generalized_black_scholes(VALUE self, VALUE rcall_put_flag, VALUE rF, VALUE rX, VALUE rT, VALUE rr, VALUE rb, VALUE rv)
+{
+ const char *call_put_flag;
+ double F, X, T, r, b, v;
+
+ call_put_flag = StringValuePtr(rcall_put_flag);
+ F = NUM2DBL(rF);
+ X = NUM2DBL(rX);
+ T = NUM2DBL(rT);
+ r = NUM2DBL(rr);
+ b = NUM2DBL(rb);
+ v = NUM2DBL(rv);
+
+ return rb_float_new(gbs(call_put_flag, F, X, T, r, b, v));
+}
+
+static double black76(const char *call_put_flag, double F, double X, double T, double r, double v)
{
double d1, d2;
d1 = (log(F / X) + (v * v / 2.0) * T) / (v * sqrt(T));
d2 = d1 - v * sqrt(T);
if (is_call(call_put_flag))
- return exp(-r * T) * (F * _cnd(d1) - X * _cnd(d2));
+ return exp(-r * T) * (F * cnd(d1) - X * cnd(d2));
else
- return exp(-r * T) * (X * _cnd(-d2) - F * _cnd(-d1));
+ return exp(-r * T) * (X * cnd(-d2) - F * cnd(-d1));
}
-/* call-seq: Rupee.black76(call_put_flag, forward, strike_price, time_to_expiry, risk_free_rate, volatility)
+/* call-seq:
+ * Rupee.black76(call_put_flag, forward, strike_price, time_to_expiry, risk_free_rate, volatility)
*
* The Black-76 valuation for options on futures and forwards
*
@@ -41,24 +130,30 @@ static double _black76(const char *call_put_flag, double F, double X, double T,
*/
static VALUE rupee_black76(VALUE self, VALUE rcall_put_flag, VALUE rF, VALUE rX, VALUE rT, VALUE rr, VALUE rv)
{
- const char * call_put_flag = StringValuePtr(rcall_put_flag);
+ const char *call_put_flag;
double F, X, T, r, v;
+ call_put_flag = StringValuePtr(rcall_put_flag);
F = NUM2DBL(rF);
X = NUM2DBL(rX);
T = NUM2DBL(rT);
r = NUM2DBL(rr);
v = NUM2DBL(rv);
- return rb_float_new(_black76(call_put_flag, F, X, T, r, v));
+ return rb_float_new(black76(call_put_flag, F, X, T, r, v));
}
void init_options()
{
/* Fool RDoc into thinking you're defining a class */
#if 0
VALUE cRupee = rb_define_class("Rupee", rb_cObject);
+ VALUE sRupee = rb_singleton_class(cRupee);
#endif
+ rb_define_singleton_method(cRupee, "black_scholes", rupee_black_scholes, 7);
+ rb_define_alias(sRupee, "bs", "black_scholes");
+ rb_define_singleton_method(cRupee, "generalized_black_scholes", rupee_generalized_black_scholes, 7);
+ rb_define_alias(sRupee, "gbs", "generalized_black_scholes");
rb_define_singleton_method(cRupee, "black76", rupee_black76, 6);
}
View
@@ -1,11 +1,12 @@
#include "rupee.h"
-VALUE cRupee;
+VALUE cRupee, sRupee;
/* Ruby calls this to load the extension */
void Init_rupee(void)
{
cRupee = rb_define_class("Rupee", rb_cObject);
+ sRupee = rb_singleton_class(cRupee);
init_statistics();
init_options();
View
@@ -7,6 +7,7 @@
#include <stdbool.h>
extern VALUE cRupee;
+extern VALUE sRupee;
#include "statistics.h"
#include "options.h"
View
@@ -2,8 +2,7 @@
#define PI 3.1415926536
-/* For private use here */
-double _cnd(double z)
+double cnd(double z)
{
double L, K, dCND;
static const double b = 0.2316419;
@@ -30,7 +29,8 @@ double _cnd(double z)
return dCND;
}
-/* call-seq: Rupee.cnd(z)
+/* call-seq:
+ * Rupee.cnd(z)
*
* Returns the standard normal cumulative distribution (has a mean of zero and
* a standard deviation of one).
@@ -41,14 +41,15 @@ double _cnd(double z)
*/
static VALUE rupee_cnd(VALUE self, VALUE rz)
{
- return rb_float_new(_cnd(NUM2DBL(rz)));
+ return rb_float_new(cnd(NUM2DBL(rz)));
}
void init_statistics()
{
/* Fool RDoc into thinking you're defining a class */
#if 0
VALUE cRupee = rb_define_class("Rupee", rb_cObject);
+ VALUE sRupee = rb_singleton_class(cRupee);
#endif
rb_define_singleton_method(cRupee, "cnd", rupee_cnd, 1);
View
@@ -3,7 +3,7 @@
#include "rupee.h"
-double _cnd(double);
+double cnd(double);
void init_statistics();
#endif
View
@@ -1,4 +1,4 @@
class Rupee
# The current version
- VERSION = "0.0.3"
+ VERSION = "0.0.4"
end
View
@@ -7,14 +7,14 @@ Gem::Specification.new do |s|
s.version = Rupee::VERSION
s.authors = ["Bryan McKelvey"]
s.email = ["bryan.mckelvey@gmail.com"]
- s.homepage = "http://brymck.herokuapp.com"
+ s.homepage = "https://github.com/brymck/rupee"
s.summary = "Financial tools for Ruby"
s.description = "rupee aims to provide user-friendly tools for use in financial gems and applications."
s.rubyforge_project = "rupee"
s.files = `git ls-files`.split("\n")
- s.test_files = `git ls-files -- spec/*`.split("\n")
+ s.test_files = `git ls-files -- spec/**/*_spec.rb`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.extensions = "ext/rupee/extconf.rb"
s.require_paths = ["lib", "ext"]
View
@@ -1,15 +0,0 @@
-require File.dirname(__FILE__) + "/../spec_helper"
-
-describe "European option valuation" do
- describe "using the Black-76 model" do
- describe "on a call option of price $60, strike $65, time to expiry 0.25, risk-free rate 8%, and volatility 30%" do
- it "should return $1.7202 for a call" do
- Rupee.black76("c", 60, 65, 0.25, 0.08, 0.3).round(4).should == 1.7202
- end
-
- it "should return $6.6212 for a put" do
- Rupee.black76("p", 60, 65, 0.25, 0.08, 0.3).round(4).should == 6.6212
- end
- end
- end
-end
View
@@ -0,0 +1,43 @@
+require File.dirname(__FILE__) + "/../spec_helper"
+
+describe "European option valuation" do
+ describe "using the Black-76 model" do
+ describe "on a call option of price $60, strike $65, time to expiry 0.25, risk-free rate 8%, and volatility 30%" do
+ it "should return $1.7202 for a call" do
+ Rupee.black76("c", 60, 65, 0.25, 0.08, 0.3).round(4).should == 1.7202
+ end
+
+ it "should return $6.6212 for a put" do
+ Rupee.black76("p", 60, 65, 0.25, 0.08, 0.3).round(4).should == 6.6212
+ end
+ end
+ end
+
+ describe "using the generalized Black-Scholes model" do
+ describe "on a call option of price $60, strike $65, time to expiry 0.25, risk-free rate 8%, and volatility 30%" do
+ it "should return $1.7202 for a call" do
+ Rupee.generalized_black_scholes("c", 60, 65, 0.25, 0.08, 0, 0.3).round(4).should == 1.7202
+ Rupee.gbs("c", 60, 65, 0.25, 0.08, 0, 0.3).round(4).should == 1.7202
+ end
+
+ it "should return $6.6212 for a put" do
+ Rupee.generalized_black_scholes("p", 60, 65, 0.25, 0.08, 0, 0.3).round(4).should == 6.6212
+ Rupee.gbs("p", 60, 65, 0.25, 0.08, 0, 0.3).round(4).should == 6.6212
+ end
+ end
+ end
+
+ describe "using the Black-Scholes model" do
+ describe "on a call option of price $60, strike $65, time to expiry 0.25, risk-free rate 8%, and volatility 30%" do
+ it "should return $1.7202 for a call" do
+ Rupee.black_scholes("c", 60, 65, 0.25, 0.08, 0, 0.3).round(4).should == 2.1334
+ Rupee.bs("c", 60, 65, 0.25, 0.08, 0, 0.3).round(4).should == 2.1334
+ end
+
+ it "should return $6.6212 for a put" do
+ Rupee.black_scholes("p", 60, 65, 0.25, 0.08, 0, 0.3).round(4).should == 5.8463
+ Rupee.bs("p", 60, 65, 0.25, 0.08, 0, 0.3).round(4).should == 5.8463
+ end
+ end
+ end
+end

0 comments on commit 95a067c

Please sign in to comment.