rupee 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.autotest +2 -0
- data/.gitignore +8 -0
- data/.rspec +1 -0
- data/COPYING +19 -0
- data/Gemfile +4 -0
- data/README.md +85 -0
- data/Rakefile +5 -0
- data/ext/rupee/bond.c +97 -0
- data/ext/rupee/conv.c +18 -0
- data/ext/rupee/distribution.c +63 -0
- data/ext/rupee/extconf.rb +3 -0
- data/ext/rupee/option.c +178 -0
- data/ext/rupee/rupee.c +13 -0
- data/ext/rupee/rupee.h +27 -0
- data/lib/rupee.rb +20 -0
- data/lib/rupee/benchmark.rb +39 -0
- data/lib/rupee/quote.rb +48 -0
- data/lib/rupee/quote/source.rb +53 -0
- data/lib/rupee/version.rb +4 -0
- data/rupee.gemspec +27 -0
- data/spec/c/bond_spec.rb +39 -0
- data/spec/c/distribution_spec.rb +7 -0
- data/spec/c/option_spec.rb +45 -0
- data/spec/import/quote_spec.rb +23 -0
- data/spec/spec_helper.rb +2 -0
- data/tasks/benchmark.rake +25 -0
- metadata +111 -0
data/.autotest
ADDED
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/COPYING
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2011 Bryan McKelvey
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
5
|
+
the Software without restriction, including without limitation the rights to
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
7
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
8
|
+
so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
+
SOFTWARE.
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
Rupee - financial tools for Ruby
|
2
|
+
================================
|
3
|
+
|
4
|
+
<table>
|
5
|
+
<tr>
|
6
|
+
<td>Homepage</td>
|
7
|
+
<td><a href="http://www.brymck.com">www.brymck.com</a></td>
|
8
|
+
</tr>
|
9
|
+
<tr>
|
10
|
+
<td>Author</td>
|
11
|
+
<td>Bryan McKelvey</td>
|
12
|
+
</tr>
|
13
|
+
<tr>
|
14
|
+
<td>Copyright</td>
|
15
|
+
<td>(c) 2011 Bryan McKelvey</td>
|
16
|
+
</tr>
|
17
|
+
<tr>
|
18
|
+
<td>License</td>
|
19
|
+
<td>MIT</td>
|
20
|
+
</tr>
|
21
|
+
</table>
|
22
|
+
|
23
|
+
[](https://flattr.com/submit/auto?user_id=brymck&url=https://github.com/brymck/rupee&title=rupee&language=en_GB&tags=github&category=software)
|
24
|
+
|
25
|
+
/|\
|
26
|
+
/ | \
|
27
|
+
/ | \
|
28
|
+
/_ / \ _\ RU PE E
|
29
|
+
| | | | _ _ _
|
30
|
+
| | | | | | | | |__o ____
|
31
|
+
| | | | | | | _ | __| |____|
|
32
|
+
| _| |_ | | | |/ / | |___
|
33
|
+
\ \ / / /_/|___/ \____|
|
34
|
+
\ | /
|
35
|
+
\ | /
|
36
|
+
\|/ brymck
|
37
|
+
|
38
|
+
Installing
|
39
|
+
----------
|
40
|
+
|
41
|
+
Note that you must have Ruby 1.8.7+ installed and the ability to compile native
|
42
|
+
extensions (standard on most platforms and available on Windows via
|
43
|
+
[DevKit](http://rubyinstaller.org/downloads/)).
|
44
|
+
|
45
|
+
gem install rupee
|
46
|
+
|
47
|
+
You can also do things the hard way if you want to keep track of the repo:
|
48
|
+
|
49
|
+
git clone git://github.com/brymck/rupee.git
|
50
|
+
cd rupee
|
51
|
+
bundle update
|
52
|
+
rake install
|
53
|
+
|
54
|
+
After all that hard work, you can take a test drive by running something like
|
55
|
+
this in the Ruby console (i.e. `irb` in a command prompt):
|
56
|
+
|
57
|
+
require "rupee"
|
58
|
+
Rupee::Option.black_scholes "c", 60, 65, 0.25, 0.08, 0, 0.3
|
59
|
+
|
60
|
+
which should return `2.1334`.
|
61
|
+
|
62
|
+
You should also be able to get the latest stock info for, for example, Wells
|
63
|
+
Fargo using the following (note that you only need to `require` the `quote`
|
64
|
+
module):
|
65
|
+
|
66
|
+
require "rupee/quote"
|
67
|
+
Rupee::Quote.bloomberg "WFC", :price, :change, :pct_change
|
68
|
+
|
69
|
+
Got it? Good. This will surely help you collect some rupees in real life.
|
70
|
+
|
71
|
+
Performance
|
72
|
+
-----------
|
73
|
+
|
74
|
+
This is just a simple benchmark I ran on my own laptop, where I value a simple
|
75
|
+
call option with Black-Scholes one million times. You can test the same on
|
76
|
+
yours with rake, but in any case it makes the point that for the mathematical
|
77
|
+
side of finance a native extension has substantial benefits:
|
78
|
+
|
79
|
+
rake benchmark:black_scholes
|
80
|
+
|
81
|
+
Results:
|
82
|
+
|
83
|
+
user system total real
|
84
|
+
rupee: 1.388000 0.000000 1.388000 ( 1.440000)
|
85
|
+
pure ruby: 18.580000 0.000000 18.580000 ( 18.817000)
|
data/Rakefile
ADDED
data/ext/rupee/bond.c
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
#include "rupee.h"
|
2
|
+
|
3
|
+
double
|
4
|
+
bond_price(cf_times, cfs, r, len)
|
5
|
+
double *cf_times, *cfs, r;
|
6
|
+
int len;
|
7
|
+
{
|
8
|
+
double p, *cft;
|
9
|
+
int i;
|
10
|
+
|
11
|
+
p = 0;
|
12
|
+
|
13
|
+
for (i = 0; i < len; i++)
|
14
|
+
p += exp(-r * cf_times[i]) * cfs[i];
|
15
|
+
|
16
|
+
return p;
|
17
|
+
};
|
18
|
+
|
19
|
+
static VALUE
|
20
|
+
price(self, rcf_times, rcfs, rr)
|
21
|
+
VALUE self, rcf_times, rcfs, rr;
|
22
|
+
{
|
23
|
+
int len = RARRAY_LEN(rcfs);
|
24
|
+
double r, cf_times[len], cfs[len];
|
25
|
+
|
26
|
+
r = NUM2DBL(rr);
|
27
|
+
rtofa(cf_times, rcf_times, len);
|
28
|
+
rtofa(cfs, rcfs, len);
|
29
|
+
|
30
|
+
return rb_float_new(bond_price(cf_times, cfs, r, len));
|
31
|
+
}
|
32
|
+
|
33
|
+
static VALUE
|
34
|
+
convexity(self, rcf_times, rcfs, rr)
|
35
|
+
VALUE self, rcf_times, rcfs, rr;
|
36
|
+
{
|
37
|
+
int len = RARRAY_LEN(rcfs);
|
38
|
+
double r, C, B, cf_times[len], cfs[len];
|
39
|
+
int i;
|
40
|
+
|
41
|
+
rtofa(cf_times, rcf_times, len);
|
42
|
+
rtofa(cfs, rcfs, len);
|
43
|
+
r = NUM2DBL(rr);
|
44
|
+
C = 0;
|
45
|
+
|
46
|
+
for (i = 0; i < len; i++)
|
47
|
+
C += cfs[i] * pow(cf_times[i], 2) * exp(-r * cf_times[i]);
|
48
|
+
|
49
|
+
B = bond_price(cf_times, cfs, r, len);
|
50
|
+
|
51
|
+
return rb_float_new(C / B);
|
52
|
+
};
|
53
|
+
|
54
|
+
static VALUE
|
55
|
+
duration(self, rcf_times, rcfs, rr)
|
56
|
+
VALUE self, rcf_times, rcfs, rr;
|
57
|
+
{
|
58
|
+
double r, S, D1;
|
59
|
+
int i, cfs_len;
|
60
|
+
|
61
|
+
VALUE *cf_times = RARRAY_PTR(rcf_times);
|
62
|
+
VALUE *cfs = RARRAY_PTR(rcfs);
|
63
|
+
cfs_len = RARRAY_LEN(rcfs);
|
64
|
+
r = NUM2DBL(rr);
|
65
|
+
S = 0;
|
66
|
+
D1 = 0;
|
67
|
+
|
68
|
+
for (i = 0; i < cfs_len; i++) {
|
69
|
+
double cfti, cfi, dcfi;
|
70
|
+
|
71
|
+
cfti = NUM2DBL(cf_times[i]);
|
72
|
+
cfi = NUM2DBL(cfs[i]);
|
73
|
+
dcfi = cfi * exp(-r * cfti);
|
74
|
+
|
75
|
+
S += dcfi;
|
76
|
+
D1 += cfti * dcfi;
|
77
|
+
}
|
78
|
+
|
79
|
+
return rb_float_new(D1 / S);
|
80
|
+
}
|
81
|
+
|
82
|
+
void
|
83
|
+
init_bond()
|
84
|
+
{
|
85
|
+
VALUE klass, singleton;
|
86
|
+
|
87
|
+
#if 0
|
88
|
+
value module = rb_define_module("rupee");
|
89
|
+
#endif
|
90
|
+
|
91
|
+
klass = rb_define_class_under(module, "Bond", rb_cObject);
|
92
|
+
singleton = rb_singleton_class(klass);
|
93
|
+
|
94
|
+
rb_define_singleton_method(klass, "convexity", convexity, 3);
|
95
|
+
rb_define_singleton_method(klass, "duration", duration, 3);
|
96
|
+
rb_define_singleton_method(klass, "price", price, 3);
|
97
|
+
}
|
data/ext/rupee/conv.c
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
#include "rupee.h"
|
2
|
+
|
3
|
+
#define PI 3.1415926536
|
4
|
+
|
5
|
+
double
|
6
|
+
cnd(z)
|
7
|
+
double z;
|
8
|
+
{
|
9
|
+
double L, K, dCND;
|
10
|
+
static const double b = 0.2316419;
|
11
|
+
static const double a1 = 0.31938153;
|
12
|
+
static const double a2 = 0.356563782;
|
13
|
+
static const double a3 = 1.781477937;
|
14
|
+
static const double a4 = 1.821255978;
|
15
|
+
static const double a5 = 1.330274429;
|
16
|
+
|
17
|
+
L = fabs(z);
|
18
|
+
K = 1.0 / (1.0 + b * L);
|
19
|
+
|
20
|
+
dCND = 1.0 - 1.0 / sqrt(2.0 * PI) *
|
21
|
+
exp(-L * L / 2.0) *
|
22
|
+
(a1 * K -
|
23
|
+
a2 * K * K +
|
24
|
+
a3 * K * K * K -
|
25
|
+
a4 * K * K * K * K +
|
26
|
+
a5 * K * K * K * K * K);
|
27
|
+
|
28
|
+
if (z < 0.0)
|
29
|
+
return 1.0 - dCND;
|
30
|
+
else
|
31
|
+
return dCND;
|
32
|
+
}
|
33
|
+
|
34
|
+
/* call-seq: cnd(z)
|
35
|
+
*
|
36
|
+
* Returns the standard normal cumulative distribution (has a mean of zero and
|
37
|
+
* a standard deviation of one).
|
38
|
+
*
|
39
|
+
* ==== Arguments
|
40
|
+
*
|
41
|
+
* * +z+ - The value for which you want the distribution
|
42
|
+
*/
|
43
|
+
static VALUE
|
44
|
+
rupee_cnd(self, rz)
|
45
|
+
VALUE self, rz;
|
46
|
+
{
|
47
|
+
return rb_float_new(cnd(NUM2DBL(rz)));
|
48
|
+
}
|
49
|
+
|
50
|
+
void
|
51
|
+
init_distribution()
|
52
|
+
{
|
53
|
+
VALUE klass, singleton;
|
54
|
+
|
55
|
+
#if 0
|
56
|
+
value module = rb_define_module("rupee");
|
57
|
+
#endif
|
58
|
+
|
59
|
+
klass = rb_define_class_under(module, "Distribution", rb_cObject);
|
60
|
+
singleton = rb_singleton_class(klass);
|
61
|
+
|
62
|
+
rb_define_singleton_method(klass, "cnd", rupee_cnd, 1);
|
63
|
+
}
|
data/ext/rupee/option.c
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
#include "rupee.h"
|
2
|
+
|
3
|
+
/* Whether a particular string refers to a call option (false for put) */
|
4
|
+
static bool
|
5
|
+
is_call(call_put_flag)
|
6
|
+
const char *call_put_flag;
|
7
|
+
{
|
8
|
+
/* Returns true for everything unless it starts with a 'p' */
|
9
|
+
return (call_put_flag[0] != 'p');
|
10
|
+
}
|
11
|
+
|
12
|
+
static double
|
13
|
+
bs(call_put_flag, S, X, T, r, q, v)
|
14
|
+
const char *call_put_flag;
|
15
|
+
double S, X, T, r, q, v;
|
16
|
+
{
|
17
|
+
double d1, d2;
|
18
|
+
|
19
|
+
d1 = (log(S / X) + (r - q + v * v / 2) * T) / (v * sqrt(T));
|
20
|
+
d2 = d1 - v * sqrt(T);
|
21
|
+
|
22
|
+
if (is_call(call_put_flag))
|
23
|
+
return S * exp(-q * T) * cnd(d1) - X * exp(-r * T) * cnd(d2);
|
24
|
+
else
|
25
|
+
return X * exp(-r * T) * cnd(-d2) - S * exp(-q * T) * cnd(-d1);
|
26
|
+
}
|
27
|
+
|
28
|
+
/* call-seq:
|
29
|
+
* black_scholes(call_put_flag, forward, strike_price, time_to_expiry,
|
30
|
+
* risk_free_rate, volatility)
|
31
|
+
*
|
32
|
+
* The Black-Scholes European call/put valuation
|
33
|
+
*
|
34
|
+
* ==== Arguments
|
35
|
+
*
|
36
|
+
* * +call_put_flag+ - Whether the instrument is a call (c) or a put (p)
|
37
|
+
* * +forward+ - The current forward value
|
38
|
+
* * +strike_price+ - The option's strike price
|
39
|
+
* * +time_to_expiry+ - The time to maturity in years
|
40
|
+
* * +risk_free_rate+ - The risk-free rate through expiry
|
41
|
+
* * +dividend_yield+ - The annual dividend yield
|
42
|
+
* * +volatility+ - The implied volatility at expiry
|
43
|
+
*/
|
44
|
+
static VALUE
|
45
|
+
rupee_black_scholes(self, rcall_put_flag, rF, rX, rT, rr, rq, rv)
|
46
|
+
VALUE self, rcall_put_flag, rF, rX, rT, rr, rq, rv;
|
47
|
+
{
|
48
|
+
const char *call_put_flag;
|
49
|
+
double F, X, T, r, q, v;
|
50
|
+
|
51
|
+
call_put_flag = StringValuePtr(rcall_put_flag);
|
52
|
+
F = NUM2DBL(rF);
|
53
|
+
X = NUM2DBL(rX);
|
54
|
+
T = NUM2DBL(rT);
|
55
|
+
r = NUM2DBL(rr);
|
56
|
+
q = NUM2DBL(rq);
|
57
|
+
v = NUM2DBL(rv);
|
58
|
+
|
59
|
+
return rb_float_new(bs(call_put_flag, F, X, T, r, q, v));
|
60
|
+
}
|
61
|
+
|
62
|
+
double
|
63
|
+
gbs(call_put_flag, S, X, T, r, b, v)
|
64
|
+
const char *call_put_flag;
|
65
|
+
double S, X, T, r, b, v;
|
66
|
+
{
|
67
|
+
double d1, d2;
|
68
|
+
|
69
|
+
d1 = (log(S / X) + (b + v * v / 2) * T) / (v * sqrt(T));
|
70
|
+
d2 = d1 - v * sqrt(T);
|
71
|
+
|
72
|
+
if (is_call(call_put_flag))
|
73
|
+
return S * exp((b - r) * T) * cnd(d1) - X * exp(-r * T) * cnd(d2);
|
74
|
+
else
|
75
|
+
return X * exp(-r * T) * cnd(-d2) - S * exp((b - r) * T) * cnd(-d1);
|
76
|
+
}
|
77
|
+
|
78
|
+
/* call-seq:
|
79
|
+
* generalized_black_scholes(call_put_flag, forward, strike_price,
|
80
|
+
* time_to_expiry, risk_free_rate, volatility)
|
81
|
+
*
|
82
|
+
* The generalized Black-Scholes European call/put valuation
|
83
|
+
*
|
84
|
+
* ==== Arguments
|
85
|
+
*
|
86
|
+
* * +call_put_flag+ - Whether the instrument is a call (c) or a put (p)
|
87
|
+
* * +forward+ - The current forward value
|
88
|
+
* * +strike_price+ - The option's strike price
|
89
|
+
* * +time_to_expiry+ - The time to maturity in years
|
90
|
+
* * +risk_free_rate+ - The risk-free rate through expiry
|
91
|
+
* * +cost_of_carry+ - The annualized cost of carry
|
92
|
+
* * +volatility+ - The implied volatility at expiry
|
93
|
+
*/
|
94
|
+
static VALUE
|
95
|
+
rupee_generalized_black_scholes(self, rcall_put_flag, rF, rX, rT, rr, rb, rv)
|
96
|
+
VALUE self, rcall_put_flag, rF, rX, rT, rr, rb, rv;
|
97
|
+
{
|
98
|
+
const char *call_put_flag;
|
99
|
+
double F, X, T, r, b, v;
|
100
|
+
|
101
|
+
call_put_flag = StringValuePtr(rcall_put_flag);
|
102
|
+
F = NUM2DBL(rF);
|
103
|
+
X = NUM2DBL(rX);
|
104
|
+
T = NUM2DBL(rT);
|
105
|
+
r = NUM2DBL(rr);
|
106
|
+
b = NUM2DBL(rb);
|
107
|
+
v = NUM2DBL(rv);
|
108
|
+
|
109
|
+
return rb_float_new(gbs(call_put_flag, F, X, T, r, b, v));
|
110
|
+
}
|
111
|
+
|
112
|
+
static double
|
113
|
+
black76(call_put_flag, F, X, T, r, v)
|
114
|
+
const char *call_put_flag;
|
115
|
+
double F, X, T, r, v;
|
116
|
+
{
|
117
|
+
double d1, d2;
|
118
|
+
|
119
|
+
d1 = (log(F / X) + (v * v / 2.0) * T) / (v * sqrt(T));
|
120
|
+
d2 = d1 - v * sqrt(T);
|
121
|
+
|
122
|
+
if (is_call(call_put_flag))
|
123
|
+
return exp(-r * T) * (F * cnd(d1) - X * cnd(d2));
|
124
|
+
else
|
125
|
+
return exp(-r * T) * (X * cnd(-d2) - F * cnd(-d1));
|
126
|
+
}
|
127
|
+
|
128
|
+
/* call-seq:
|
129
|
+
* black76(call_put_flag, forward, strike_price, time_to_expiry,
|
130
|
+
* risk_free_rate, volatility)
|
131
|
+
*
|
132
|
+
* The Black-76 valuation for options on futures and forwards
|
133
|
+
*
|
134
|
+
* ==== Arguments
|
135
|
+
*
|
136
|
+
* * +call_put_flag+ - Whether the instrument is a call (c) or a put (p)
|
137
|
+
* * +forward+ - The current forward value
|
138
|
+
* * +strike_price+ - The option's strike price
|
139
|
+
* * +time_to_expiry+ - The time to maturity in years
|
140
|
+
* * +risk_free_rate+ - The risk-free rate through expiry
|
141
|
+
* * +volatility+ - The implied volatility at expiry
|
142
|
+
*/
|
143
|
+
static VALUE
|
144
|
+
rupee_black76(self, rcall_put_flag, rF, rX, rT, rr, rv)
|
145
|
+
VALUE self, rcall_put_flag, rF, rX, rT, rr, rv;
|
146
|
+
{
|
147
|
+
const char *call_put_flag;
|
148
|
+
double F, X, T, r, v;
|
149
|
+
|
150
|
+
call_put_flag = StringValuePtr(rcall_put_flag);
|
151
|
+
F = NUM2DBL(rF);
|
152
|
+
X = NUM2DBL(rX);
|
153
|
+
T = NUM2DBL(rT);
|
154
|
+
r = NUM2DBL(rr);
|
155
|
+
v = NUM2DBL(rv);
|
156
|
+
|
157
|
+
return rb_float_new(black76(call_put_flag, F, X, T, r, v));
|
158
|
+
}
|
159
|
+
|
160
|
+
void
|
161
|
+
init_option()
|
162
|
+
{
|
163
|
+
VALUE klass, singleton;
|
164
|
+
|
165
|
+
#if 0
|
166
|
+
VALUE module = rb_define_module("Rupee");
|
167
|
+
#endif
|
168
|
+
|
169
|
+
klass = rb_define_class_under(module, "Option", rb_cObject);
|
170
|
+
singleton = rb_singleton_class(klass);
|
171
|
+
|
172
|
+
rb_define_singleton_method(klass, "black_scholes", rupee_black_scholes, 7);
|
173
|
+
rb_define_alias(singleton, "bs", "black_scholes");
|
174
|
+
rb_define_singleton_method(klass, "generalized_black_scholes",
|
175
|
+
rupee_generalized_black_scholes, 7);
|
176
|
+
rb_define_alias(singleton, "gbs", "generalized_black_scholes");
|
177
|
+
rb_define_singleton_method(klass, "black76", rupee_black76, 6);
|
178
|
+
}
|
data/ext/rupee/rupee.c
ADDED
data/ext/rupee/rupee.h
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#ifndef RUPEE
|
2
|
+
#define RUPEE
|
3
|
+
|
4
|
+
#include <ruby.h>
|
5
|
+
#include <math.h>
|
6
|
+
#include <string.h>
|
7
|
+
#include <stdbool.h>
|
8
|
+
|
9
|
+
extern VALUE module;
|
10
|
+
|
11
|
+
/* Conversion */
|
12
|
+
double *rtofa(double *dest, VALUE src, int len);
|
13
|
+
|
14
|
+
/* Statistics */
|
15
|
+
double cnd(double);
|
16
|
+
void init_distribution();
|
17
|
+
|
18
|
+
/* Options */
|
19
|
+
double gbs(const char *call_put_flag, double S, double X, double T, double r,
|
20
|
+
double b, double v);
|
21
|
+
void init_option();
|
22
|
+
|
23
|
+
/* Bonds */
|
24
|
+
double bond_price(double *cf_times, double *cfs, double r, int len);
|
25
|
+
void init_bond();
|
26
|
+
|
27
|
+
#endif
|
data/lib/rupee.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require "rupee/rupee" # keep this as the first require
|
2
|
+
require "rupee/version"
|
3
|
+
|
4
|
+
# Rupee aims to provide user-friendly tools for use in financial applications.
|
5
|
+
# The gem is in its development stages, but it currently offers:
|
6
|
+
#
|
7
|
+
# * Live, streaming web import capabilities (Excel 2002+), including custom
|
8
|
+
# functions for easily importing security prices from Bloomberg.com, Google
|
9
|
+
# Finance and Yahoo! Finance
|
10
|
+
# * Basic options pricing, including Black-Scholes, the options Greeks and a
|
11
|
+
# few more complex options models
|
12
|
+
#
|
13
|
+
# Author:: Bryan McKelvey (mailto:bryan.mckelvey@gmail.com)
|
14
|
+
# Copyright:: Copyright (c) 2011 Bryan McKelvey
|
15
|
+
# License:: MIT
|
16
|
+
|
17
|
+
# This module contains all modules and classes associated with Rupee
|
18
|
+
module Rupee
|
19
|
+
autoload :Quote, "rupee/quote"
|
20
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Rupee
|
2
|
+
# Just for use in benchmarking the performance of Rupee code
|
3
|
+
class Benchmark
|
4
|
+
class << self
|
5
|
+
# Black-Scholes option price valuation
|
6
|
+
# by Michael Neumann (with some revisions)
|
7
|
+
# http://www.espenhaug.com/black_scholes.html
|
8
|
+
def black_scholes(callPutFlag, s, x, t, r, v)
|
9
|
+
d1 = (Math.log(s / x) + (r + v * v / 2.0) * t) / (v * Math.sqrt(t))
|
10
|
+
d2 = d1 - v * Math.sqrt(t)
|
11
|
+
if callPutFlag == 'c'
|
12
|
+
s * cnd(d1) - x * Math.exp(-r * t) * cnd(d2)
|
13
|
+
else
|
14
|
+
x * Math.exp(-r * t) * cnd(-d2) - s * cnd(-d1)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
A1 = 0.31938153
|
21
|
+
A2 = -0.356563782
|
22
|
+
A3 = 1.781477937
|
23
|
+
A4 = -1.821255978
|
24
|
+
A5 = 1.330274429
|
25
|
+
|
26
|
+
# Cumulative normal distribution
|
27
|
+
# by Michael Neumann (with some revisions)
|
28
|
+
# http://www.espenhaug.com/black_scholes.html
|
29
|
+
def cnd(x)
|
30
|
+
l = x.abs
|
31
|
+
k = 1.0 / (1.0 + 0.2316419 * l)
|
32
|
+
w = 1.0 - 1.0 / Math.sqrt(2 * Math::PI) * Math.exp(-l * l / 2.0) *
|
33
|
+
(A1 * k + A2 * k * k + A3 * (k ** 3) + A4 * (k ** 4) + A5 * (k ** 5))
|
34
|
+
w = 1.0 - w if x < 0
|
35
|
+
return w
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/rupee/quote.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require "rupee/quote/source"
|
2
|
+
autoload :Net, "net/http"
|
3
|
+
autoload :URI, "uri"
|
4
|
+
|
5
|
+
module Rupee
|
6
|
+
# The quote and data import functionality in Rupee
|
7
|
+
class Quote
|
8
|
+
class << self
|
9
|
+
# Retrieves the current price of a security
|
10
|
+
def get(url, *params)
|
11
|
+
results = {}
|
12
|
+
params = [:price] if params.empty?
|
13
|
+
url = URI.parse(url)
|
14
|
+
html = Net::HTTP.start(url.host, url.port) do |http|
|
15
|
+
http.get url.request_uri
|
16
|
+
end.body
|
17
|
+
|
18
|
+
params.each do |p|
|
19
|
+
results[p] = @sources[0].params[p].match(html)[1]
|
20
|
+
end
|
21
|
+
|
22
|
+
results
|
23
|
+
end
|
24
|
+
|
25
|
+
# Retrieves the current price of a security from Bloomberg
|
26
|
+
def bloomberg(ticker, *params)
|
27
|
+
get BLOOMBERG_URL % ticker, *params
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
# The URL for Bloomberg's quotes service
|
33
|
+
BLOOMBERG_URL = "http://www.bloomberg.com/apps/quote?ticker=%s"
|
34
|
+
|
35
|
+
# Returns an intepretation of an abbreviated source name
|
36
|
+
def shorten_source(source)
|
37
|
+
case source.downcase.to_sym
|
38
|
+
when :"", :bloomberg, :bberg, :bb, :b
|
39
|
+
:bloomberg
|
40
|
+
when :google, :goog, :g
|
41
|
+
:google
|
42
|
+
when :yahoo!, :yahoo, :yhoo, :y!, :y
|
43
|
+
:yahoo
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Rupee
|
2
|
+
class Quote
|
3
|
+
# A class to hold quote sources
|
4
|
+
class Source
|
5
|
+
# The name of the source
|
6
|
+
attr :name
|
7
|
+
|
8
|
+
# The parameters available
|
9
|
+
attr :params
|
10
|
+
|
11
|
+
def initialize(name, aliases = [], params = {})
|
12
|
+
@name = name
|
13
|
+
@aliases = aliases
|
14
|
+
@params = params
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
# A holder for the sources available
|
20
|
+
attr :sources
|
21
|
+
|
22
|
+
# The default source to use when making generic requests
|
23
|
+
attr :default_source
|
24
|
+
|
25
|
+
# Build the default sources that come with Rupee
|
26
|
+
def build_sources
|
27
|
+
@sources ||= []
|
28
|
+
|
29
|
+
# Bloomberg
|
30
|
+
@sources << Source.new(:bloomberg, [:bberg, :bb, :b],
|
31
|
+
:price => /(?:PRICE|VALUE): <span class="amount">([0-9.,NA-]{1,})/,
|
32
|
+
:change => /Change<\/td>\n<td class="value[^>]+>([0-9.,NA-]{1,})/,
|
33
|
+
:pct_change => /Change<\/td>\n<td class="value[^>]+>[0-9.,NA-]{1,} \(([0-9NA.,-]{1,})\%/,
|
34
|
+
:date => /"date">(.*?)</,
|
35
|
+
:time => /"time">(.*?)</,
|
36
|
+
:bid => /Bid<\/td>\n<td class="value[^>]+>([0-9.,NA-]{1,})/,
|
37
|
+
:ask => /Ask<\/td>\n<td class="value[^>]+>([0-9.,NA-]{1,})/,
|
38
|
+
:open => /Open<\/td>\n<td class="value[^>]+>([0-9.,NA-]{1,})/,
|
39
|
+
:high => /High<\/td>\n<td class="value[^>]+>([0-9.,NA-]{1,})/,
|
40
|
+
:low => /Low<\/td>\n<td class="value[^>]+>([0-9.,NA-]{1,})/,
|
41
|
+
:volume => /Volume<\/td>\n<td class="value[^>]+>([0-9.,NA-]{1,})/,
|
42
|
+
:mkt_cap => /Market Cap[^<]+<\/td>\n<td class="value">([0-9.,NA-]{1,})/,
|
43
|
+
:p_e => /Price\/Earnings[^<]+<\/td>\n<td class="value">([0-9.,NA-]{1,})/)
|
44
|
+
@sources << Source.new(:yahoo)
|
45
|
+
@sources << Source.new(:google)
|
46
|
+
@default_source = :bloomberg
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Initialize sources
|
51
|
+
Quote.build_sources
|
52
|
+
end
|
53
|
+
end
|
data/rupee.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "rupee/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "rupee"
|
7
|
+
s.version = Rupee::VERSION
|
8
|
+
s.authors = ["Bryan McKelvey"]
|
9
|
+
s.email = ["bryan.mckelvey@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/brymck/rupee"
|
11
|
+
s.summary = "Financial tools for Ruby"
|
12
|
+
s.description = "rupee aims to provide user-friendly tools for use in financial gems and applications."
|
13
|
+
|
14
|
+
s.rubyforge_project = "rupee"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- spec/**/*_spec.rb`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.extensions = "ext/rupee/extconf.rb"
|
20
|
+
s.require_paths = ["lib", "ext"]
|
21
|
+
|
22
|
+
# specify any dependencies here; for example:
|
23
|
+
s.add_development_dependency "bundler", "~> 1.0"
|
24
|
+
s.add_development_dependency "rspec", "~> 2.0"
|
25
|
+
s.add_development_dependency "autotest", "~> 4.0"
|
26
|
+
# s.add_runtime_dependency "rest-client"
|
27
|
+
end
|
data/spec/c/bond_spec.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../spec_helper"
|
2
|
+
|
3
|
+
# Discrete discounting
|
4
|
+
# bonds price = 102.531
|
5
|
+
# bond yield to maturity = 0.09
|
6
|
+
# bond duration = 2.73895
|
7
|
+
# bond duration modified = 2.5128
|
8
|
+
# bond convexity =8.93248
|
9
|
+
# new bond price = 100
|
10
|
+
#Continous discounting
|
11
|
+
# bonds price = 101.464
|
12
|
+
# bond yield to maturity = 0.09
|
13
|
+
# bond duration = 2.73753
|
14
|
+
# bond convexity =7.86779
|
15
|
+
# new bond price = 104.282
|
16
|
+
|
17
|
+
TOLERANCE = 0.001
|
18
|
+
|
19
|
+
describe Rupee::Bond do
|
20
|
+
before :each do
|
21
|
+
@times = [1, 2, 3]
|
22
|
+
@cflows = [10, 10, 110]
|
23
|
+
@r = 0.09
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "with continuous discounting" do
|
27
|
+
it "should produce an accurate price" do
|
28
|
+
Rupee::Bond.price(@times, @cflows, @r).should be_within(TOLERANCE).of 101.464
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should produce an accurate duration" do
|
32
|
+
Rupee::Bond.duration(@times, @cflows, @r).should be_within(TOLERANCE).of 2.73753
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should produce an accurate convexity" do
|
36
|
+
Rupee::Bond.convexity(@times, @cflows, @r).should be_within(TOLERANCE).of 7.86779
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../spec_helper"
|
2
|
+
|
3
|
+
describe Rupee::Option do
|
4
|
+
describe "European option valuation" do
|
5
|
+
describe "using the Black-76 model" do
|
6
|
+
describe "on a call option of price $60, strike $65, time to expiry 0.25, risk-free rate 8%, and volatility 30%" do
|
7
|
+
it "should return $1.7202 for a call" do
|
8
|
+
Rupee::Option.black76("c", 60, 65, 0.25, 0.08, 0.3).round(4).should == 1.7202
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should return $6.6212 for a put" do
|
12
|
+
Rupee::Option.black76("p", 60, 65, 0.25, 0.08, 0.3).round(4).should == 6.6212
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "using the generalized Black-Scholes model" do
|
18
|
+
describe "on a call option of price $60, strike $65, time to expiry 0.25, risk-free rate 8%, and volatility 30%" do
|
19
|
+
it "should return $1.7202 for a call" do
|
20
|
+
Rupee::Option.generalized_black_scholes("c", 60, 65, 0.25, 0.08, 0, 0.3).round(4).should == 1.7202
|
21
|
+
Rupee::Option.gbs("c", 60, 65, 0.25, 0.08, 0, 0.3).round(4).should == 1.7202
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should return $6.6212 for a put" do
|
25
|
+
Rupee::Option.generalized_black_scholes("p", 60, 65, 0.25, 0.08, 0, 0.3).round(4).should == 6.6212
|
26
|
+
Rupee::Option.gbs("p", 60, 65, 0.25, 0.08, 0, 0.3).round(4).should == 6.6212
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "using the Black-Scholes model" do
|
32
|
+
describe "on a call option of price $60, strike $65, time to expiry 0.25, risk-free rate 8%, and volatility 30%" do
|
33
|
+
it "should return $1.7202 for a call" do
|
34
|
+
Rupee::Option.black_scholes("c", 60, 65, 0.25, 0.08, 0, 0.3).round(4).should == 2.1334
|
35
|
+
Rupee::Option.bs("c", 60, 65, 0.25, 0.08, 0, 0.3).round(4).should == 2.1334
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should return $6.6212 for a put" do
|
39
|
+
Rupee::Option.black_scholes("p", 60, 65, 0.25, 0.08, 0, 0.3).round(4).should == 5.8463
|
40
|
+
Rupee::Option.bs("p", 60, 65, 0.25, 0.08, 0, 0.3).round(4).should == 5.8463
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../spec_helper"
|
2
|
+
|
3
|
+
describe Rupee::Quote do
|
4
|
+
it "should automatically have a Bloomberg source" do
|
5
|
+
Rupee::Quote.sources[0].name.should == :bloomberg
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "when pulling quotes" do
|
9
|
+
describe "without any parameters specified" do
|
10
|
+
before :each do
|
11
|
+
@wfc = Rupee::Quote.bloomberg("WFC")
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should default to pulling the price" do
|
15
|
+
@wfc.should include :price
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should return a price" do
|
19
|
+
@wfc[:price].to_f.should be_a_kind_of Float
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
autoload :Benchmark, "benchmark"
|
2
|
+
|
3
|
+
namespace :benchmark do
|
4
|
+
task :default => :black_scholes
|
5
|
+
|
6
|
+
desc "Run Black-Scholes one million times"
|
7
|
+
task :black_scholes do
|
8
|
+
require "rupee"
|
9
|
+
require "rupee/benchmark"
|
10
|
+
|
11
|
+
n = 1_000_000
|
12
|
+
Benchmark.bm(11) do |x|
|
13
|
+
x.report "rupee:" do
|
14
|
+
n.times do
|
15
|
+
Rupee::Option.black_scholes "c", 60, 65, 0.25, 0.08, 0, 0.3
|
16
|
+
end
|
17
|
+
end
|
18
|
+
x.report("pure ruby:") do
|
19
|
+
n.times do
|
20
|
+
Rupee::Benchmark.black_scholes "c", 60, 65, 0.25, 0.08, 0.3
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rupee
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Bryan McKelvey
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-09-28 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: &84328270 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *84328270
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &84326450 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '2.0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *84326450
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: autotest
|
38
|
+
requirement: &84324740 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '4.0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *84324740
|
47
|
+
description: rupee aims to provide user-friendly tools for use in financial gems and
|
48
|
+
applications.
|
49
|
+
email:
|
50
|
+
- bryan.mckelvey@gmail.com
|
51
|
+
executables: []
|
52
|
+
extensions:
|
53
|
+
- ext/rupee/extconf.rb
|
54
|
+
extra_rdoc_files: []
|
55
|
+
files:
|
56
|
+
- .autotest
|
57
|
+
- .gitignore
|
58
|
+
- .rspec
|
59
|
+
- COPYING
|
60
|
+
- Gemfile
|
61
|
+
- README.md
|
62
|
+
- Rakefile
|
63
|
+
- ext/rupee/bond.c
|
64
|
+
- ext/rupee/conv.c
|
65
|
+
- ext/rupee/distribution.c
|
66
|
+
- ext/rupee/extconf.rb
|
67
|
+
- ext/rupee/option.c
|
68
|
+
- ext/rupee/rupee.c
|
69
|
+
- ext/rupee/rupee.h
|
70
|
+
- lib/rupee.rb
|
71
|
+
- lib/rupee/benchmark.rb
|
72
|
+
- lib/rupee/quote.rb
|
73
|
+
- lib/rupee/quote/source.rb
|
74
|
+
- lib/rupee/version.rb
|
75
|
+
- rupee.gemspec
|
76
|
+
- spec/c/bond_spec.rb
|
77
|
+
- spec/c/distribution_spec.rb
|
78
|
+
- spec/c/option_spec.rb
|
79
|
+
- spec/import/quote_spec.rb
|
80
|
+
- spec/spec_helper.rb
|
81
|
+
- tasks/benchmark.rake
|
82
|
+
homepage: https://github.com/brymck/rupee
|
83
|
+
licenses: []
|
84
|
+
post_install_message:
|
85
|
+
rdoc_options: []
|
86
|
+
require_paths:
|
87
|
+
- lib
|
88
|
+
- ext
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ! '>='
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ! '>='
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
requirements: []
|
102
|
+
rubyforge_project: rupee
|
103
|
+
rubygems_version: 1.8.10
|
104
|
+
signing_key:
|
105
|
+
specification_version: 3
|
106
|
+
summary: Financial tools for Ruby
|
107
|
+
test_files:
|
108
|
+
- spec/c/bond_spec.rb
|
109
|
+
- spec/c/distribution_spec.rb
|
110
|
+
- spec/c/option_spec.rb
|
111
|
+
- spec/import/quote_spec.rb
|