rupee 0.1.0 → 0.1.1
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/README.md +31 -7
- data/ext/rupee/bond.c +257 -50
- data/ext/rupee/future.c +38 -0
- data/ext/rupee/option.c +3 -2
- data/ext/rupee/rupee.c +3 -1
- data/ext/rupee/rupee.h +11 -2
- data/ext/rupee/util.c +36 -0
- data/lib/rupee.rb +3 -0
- data/lib/rupee/option.rb +31 -0
- data/lib/rupee/security.rb +36 -0
- data/lib/rupee/version.rb +1 -1
- data/spec/c/bond_spec.rb +27 -10
- data/spec/c/future_spec.rb +13 -0
- data/tasks/benchmark.rake +34 -5
- metadata +13 -8
- data/ext/rupee/conv.c +0 -18
data/README.md
CHANGED
@@ -56,8 +56,16 @@ this in the Ruby console (i.e. `irb` in a command prompt):
|
|
56
56
|
|
57
57
|
require "rupee"
|
58
58
|
Rupee::Option.black_scholes "c", 60, 65, 0.25, 0.08, 0, 0.3
|
59
|
+
Rupee::Call.new(
|
60
|
+
:underlying => 60,
|
61
|
+
:strike => 65,
|
62
|
+
:time => 0.25,
|
63
|
+
:rate => 0.08,
|
64
|
+
:div_yield => 0.00,
|
65
|
+
:volatility => 0.3
|
66
|
+
).black_scholes
|
59
67
|
|
60
|
-
which should return `2.1334`.
|
68
|
+
both of which should return `2.1334`.
|
61
69
|
|
62
70
|
You should also be able to get the latest stock info for, for example, Wells
|
63
71
|
Fargo using the following (note that you only need to `require` the `quote`
|
@@ -72,14 +80,30 @@ Performance
|
|
72
80
|
-----------
|
73
81
|
|
74
82
|
This is just a simple benchmark I ran on my own laptop, where I value a simple
|
75
|
-
call option with Black-Scholes
|
76
|
-
|
77
|
-
|
83
|
+
call option with Black-Scholes 100,000 times. You can test the same on yours
|
84
|
+
with rake, but in any case it makes the point that for the mathematical side
|
85
|
+
of finance a native extension has substantial benefits:
|
78
86
|
|
79
87
|
rake benchmark:black_scholes
|
80
88
|
|
81
89
|
Results:
|
82
90
|
|
83
|
-
|
84
|
-
|
85
|
-
|
91
|
+
user system total real
|
92
|
+
Rupee (class): 0.190000 0.000000 0.190000 ( 0.194001)
|
93
|
+
Rupee (one object): 0.180000 0.000000 0.180000 ( 0.183091)
|
94
|
+
Rupee (new object): 2.210000 0.000000 2.210000 ( 2.213351)
|
95
|
+
Pure Ruby: 2.320000 0.000000 2.320000 ( 2.324259)
|
96
|
+
|
97
|
+
In words, for math-intensive operations, using a C implementation is clearly
|
98
|
+
faster than the same thing in Ruby.
|
99
|
+
|
100
|
+
Also, if you're doing a valuation on a one-off set of examples (e.g. in a Monte
|
101
|
+
Carlo simulation), you probably don't want to create an object every time.
|
102
|
+
Something like `Rupee::Option.black_scholes ...` should work just fine.
|
103
|
+
Creating a `Rupee::Option` object takes roughly the same amount of time as
|
104
|
+
running `Rupee::Option.black_scholes` a dozen times.
|
105
|
+
|
106
|
+
However, if you're creating and reusing an object, I strongly recommend
|
107
|
+
preserving the object orientation of Ruby: the penalty for using a new instance
|
108
|
+
rather than calling the class method directly is almost entirely in the object
|
109
|
+
initialization itself.
|
data/ext/rupee/bond.c
CHANGED
@@ -1,82 +1,274 @@
|
|
1
1
|
#include "rupee.h"
|
2
2
|
|
3
|
+
#define ACCURACY 0.00001
|
4
|
+
#define MAX_ITERATIONS 200
|
5
|
+
|
3
6
|
double
|
4
|
-
|
5
|
-
double *
|
7
|
+
bond_conv(times, cflows, r, len, discrete)
|
8
|
+
double *times, *cflows, r;
|
6
9
|
int len;
|
10
|
+
bool discrete;
|
7
11
|
{
|
8
|
-
double
|
12
|
+
double C, B;
|
9
13
|
int i;
|
14
|
+
C = 0;
|
15
|
+
|
16
|
+
for (i = 0; i < len; i++) {
|
17
|
+
double time, time_sq;
|
18
|
+
time = times[i];
|
19
|
+
|
20
|
+
// I'm a little skeptical this is correct
|
21
|
+
if (discrete)
|
22
|
+
time_sq = time * (times[i] + 1);
|
23
|
+
else
|
24
|
+
time_sq = pow(time, 2);
|
25
|
+
|
26
|
+
C += cflows[i] * time_sq * simple_df(r, time, discrete);
|
27
|
+
}
|
10
28
|
|
29
|
+
B = bond_price(times, cflows, r, len, discrete);
|
30
|
+
|
31
|
+
// Same goes for this; I don't know why you'd discount only under discrete
|
32
|
+
// compounding. That doesn't seem like a market convention.
|
33
|
+
if (discrete)
|
34
|
+
return C / pow(1 + r, 2) / B;
|
35
|
+
else
|
36
|
+
return C / B;
|
37
|
+
};
|
38
|
+
|
39
|
+
double
|
40
|
+
bond_dur(times, cflows, r, len, discrete)
|
41
|
+
double *times, *cflows, r;
|
42
|
+
int len;
|
43
|
+
bool discrete;
|
44
|
+
{
|
45
|
+
double S, D1;
|
46
|
+
int i;
|
47
|
+
S = 0;
|
48
|
+
D1 = 0;
|
49
|
+
|
50
|
+
for (i = 0; i < len; i++) {
|
51
|
+
double time, dcflow;
|
52
|
+
|
53
|
+
time = times[i];
|
54
|
+
dcflow = cflows[i] * simple_df(r, time, discrete);
|
55
|
+
|
56
|
+
S += dcflow;
|
57
|
+
D1 += dcflow * time;
|
58
|
+
}
|
59
|
+
|
60
|
+
return D1 / S;
|
61
|
+
}
|
62
|
+
|
63
|
+
double
|
64
|
+
bond_price(times, cflows, r, len, discrete)
|
65
|
+
double *times, *cflows, r;
|
66
|
+
int len;
|
67
|
+
bool discrete;
|
68
|
+
{
|
69
|
+
double p, *cft;
|
70
|
+
int i;
|
11
71
|
p = 0;
|
12
72
|
|
13
73
|
for (i = 0; i < len; i++)
|
14
|
-
p +=
|
74
|
+
p += cflows[i] * simple_df(r, times[i], discrete);
|
15
75
|
|
16
76
|
return p;
|
17
77
|
};
|
18
78
|
|
79
|
+
double
|
80
|
+
bond_ytm(times, cflows, price, len, discrete)
|
81
|
+
double *times, *cflows, price;
|
82
|
+
int len;
|
83
|
+
bool discrete;
|
84
|
+
{
|
85
|
+
double bot, top, r;
|
86
|
+
int i;
|
87
|
+
bot = 0;
|
88
|
+
top = 1;
|
89
|
+
|
90
|
+
while (bond_price(times, cflows, top, len, discrete) > price)
|
91
|
+
top *= 2;
|
92
|
+
|
93
|
+
r = avg(top, bot);
|
94
|
+
|
95
|
+
for (i = 0; i < MAX_ITERATIONS; i++) {
|
96
|
+
double diff;
|
97
|
+
diff = bond_price(times, cflows, r, len, discrete) - price;
|
98
|
+
|
99
|
+
if (fabs(diff) < ACCURACY)
|
100
|
+
return r;
|
101
|
+
|
102
|
+
if (diff > 0.0)
|
103
|
+
bot = r;
|
104
|
+
else
|
105
|
+
top = r;
|
106
|
+
|
107
|
+
r = avg(top, bot);
|
108
|
+
};
|
109
|
+
|
110
|
+
return r;
|
111
|
+
};
|
112
|
+
|
113
|
+
// Ruby singleton functions
|
114
|
+
|
115
|
+
static VALUE
|
116
|
+
convexity(self, _times, _cflows, _r, discrete)
|
117
|
+
VALUE self, _times, _cflows, _r;
|
118
|
+
bool discrete;
|
119
|
+
{
|
120
|
+
int len = RARRAY_LEN(_cflows);
|
121
|
+
double times[len], cflows[len], r;
|
122
|
+
|
123
|
+
rtofa(times, _times, len);
|
124
|
+
rtofa(cflows, _cflows, len);
|
125
|
+
r = NUM2DBL(_r);
|
126
|
+
|
127
|
+
return rb_float_new(bond_conv(times, cflows, r, len, discrete));
|
128
|
+
};
|
129
|
+
|
19
130
|
static VALUE
|
20
|
-
|
21
|
-
VALUE self,
|
131
|
+
duration(self, _times, _cflows, _r, discrete)
|
132
|
+
VALUE self, _times, _cflows, _r;
|
133
|
+
bool discrete;
|
22
134
|
{
|
23
|
-
int len = RARRAY_LEN(
|
24
|
-
double
|
135
|
+
int len = RARRAY_LEN(_cflows);
|
136
|
+
double times[len], cflows[len], r;
|
25
137
|
|
26
|
-
|
27
|
-
rtofa(
|
28
|
-
|
138
|
+
rtofa(times, _times, len);
|
139
|
+
rtofa(cflows, _cflows, len);
|
140
|
+
r = NUM2DBL(_r);
|
29
141
|
|
30
|
-
return rb_float_new(
|
142
|
+
return rb_float_new(bond_dur(times, cflows, r, len, discrete));
|
31
143
|
}
|
32
144
|
|
33
145
|
static VALUE
|
34
|
-
|
35
|
-
VALUE self,
|
146
|
+
macaulay(self, _times, _cflows, _price, discrete)
|
147
|
+
VALUE self, _times, _cflows, _price;
|
148
|
+
bool discrete;
|
36
149
|
{
|
37
|
-
int len = RARRAY_LEN(
|
38
|
-
double
|
39
|
-
int i;
|
40
|
-
|
41
|
-
rtofa(cf_times, rcf_times, len);
|
42
|
-
rtofa(cfs, rcfs, len);
|
43
|
-
r = NUM2DBL(rr);
|
44
|
-
C = 0;
|
150
|
+
int len = RARRAY_LEN(_cflows);
|
151
|
+
double times[len], cflows[len], price, ytm;
|
45
152
|
|
46
|
-
|
47
|
-
|
153
|
+
rtofa(times, _times, len);
|
154
|
+
rtofa(cflows, _cflows, len);
|
155
|
+
price = NUM2DBL(price);
|
48
156
|
|
49
|
-
|
157
|
+
ytm = bond_ytm(times, cflows, price, len, discrete);
|
50
158
|
|
51
|
-
return rb_float_new(
|
159
|
+
return rb_float_new(bond_dur(times, cflows, ytm, len, discrete));
|
52
160
|
};
|
53
161
|
|
54
162
|
static VALUE
|
55
|
-
|
56
|
-
VALUE self,
|
163
|
+
price(self, _times, _cflows, _r, discrete)
|
164
|
+
VALUE self, _times, _cflows, _r;
|
165
|
+
bool discrete;
|
57
166
|
{
|
58
|
-
|
59
|
-
|
167
|
+
int len = RARRAY_LEN(_cflows);
|
168
|
+
double times[len], cflows[len], r;
|
169
|
+
|
170
|
+
rtofa(times, _times, len);
|
171
|
+
rtofa(cflows, _cflows, len);
|
172
|
+
r = NUM2DBL(_r);
|
60
173
|
|
61
|
-
|
62
|
-
|
63
|
-
cfs_len = RARRAY_LEN(rcfs);
|
64
|
-
r = NUM2DBL(rr);
|
65
|
-
S = 0;
|
66
|
-
D1 = 0;
|
174
|
+
return rb_float_new(bond_price(times, cflows, r, len, discrete));
|
175
|
+
}
|
67
176
|
|
68
|
-
|
69
|
-
double cfti, cfi, dcfi;
|
177
|
+
// Helper functions
|
70
178
|
|
71
|
-
|
72
|
-
|
73
|
-
|
179
|
+
// Convexity of a continuously compounded bond
|
180
|
+
static VALUE
|
181
|
+
convexity_continuous(self, _times, _cflows, _r)
|
182
|
+
VALUE self, _times, _cflows, _r;
|
183
|
+
{
|
184
|
+
return convexity(self, _times, _cflows, _r, false);
|
185
|
+
}
|
74
186
|
|
75
|
-
|
76
|
-
|
77
|
-
|
187
|
+
// Convexity of a discretely compounded bond
|
188
|
+
static VALUE
|
189
|
+
convexity_discrete(self, _times, _cflows, _r)
|
190
|
+
VALUE self, _times, _cflows, _r;
|
191
|
+
{
|
192
|
+
return convexity(self, _times, _cflows, _r, true);
|
193
|
+
}
|
194
|
+
|
195
|
+
// Duration of a continuously compounded bond
|
196
|
+
static VALUE
|
197
|
+
duration_continuous(self, _times, _cflows, _r)
|
198
|
+
VALUE self, _times, _cflows, _r;
|
199
|
+
{
|
200
|
+
return duration(self, _times, _cflows, _r, false);
|
201
|
+
}
|
202
|
+
|
203
|
+
// Duration of a discretely compounded bond
|
204
|
+
static VALUE
|
205
|
+
duration_discrete(self, _times, _cflows, _r)
|
206
|
+
VALUE self, _times, _cflows, _r;
|
207
|
+
{
|
208
|
+
return duration(self, _times, _cflows, _r, true);
|
209
|
+
}
|
210
|
+
|
211
|
+
// Macaulay duration of a continuously compounded bond
|
212
|
+
static VALUE
|
213
|
+
macaulay_continuous(self, _times, _cflows, _price)
|
214
|
+
VALUE self, _times, _cflows, _price;
|
215
|
+
{
|
216
|
+
return macaulay(self, _times, _cflows, _price, false);
|
217
|
+
}
|
78
218
|
|
79
|
-
|
219
|
+
// Macaulay duration of a discretely compounded bond
|
220
|
+
static VALUE
|
221
|
+
macaulay_discrete(self, _times, _cflows, _price)
|
222
|
+
VALUE self, _times, _cflows, _price;
|
223
|
+
{
|
224
|
+
return macaulay(self, _times, _cflows, _price, true);
|
225
|
+
}
|
226
|
+
|
227
|
+
// Price of a continuously compounded bond
|
228
|
+
static VALUE
|
229
|
+
price_continuous(self, _times, _cflows, _r)
|
230
|
+
VALUE self, _times, _cflows, _r;
|
231
|
+
{
|
232
|
+
return price(self, _times, _cflows, _r, false);
|
233
|
+
}
|
234
|
+
|
235
|
+
// Price of a discretely compounded bond
|
236
|
+
static VALUE
|
237
|
+
price_discrete(self, _times, _cflows, _r)
|
238
|
+
VALUE self, _times, _cflows, _r;
|
239
|
+
{
|
240
|
+
return price(self, _times, _cflows, _r, true);
|
241
|
+
}
|
242
|
+
|
243
|
+
static VALUE
|
244
|
+
yield_to_maturity(self, _times, _cflows, _price, discrete)
|
245
|
+
VALUE self, _times, _cflows, _price;
|
246
|
+
bool discrete;
|
247
|
+
{
|
248
|
+
int len = RARRAY_LEN(_cflows);
|
249
|
+
double times[len], cflows[len], price;
|
250
|
+
|
251
|
+
rtofa(times, _times, len);
|
252
|
+
rtofa(cflows, _cflows, len);
|
253
|
+
price = NUM2DBL(_price);
|
254
|
+
|
255
|
+
return rb_float_new(bond_ytm(times, cflows, price, len, discrete));
|
256
|
+
};
|
257
|
+
|
258
|
+
// Yield to maturity of a continuously compounded bond
|
259
|
+
static VALUE
|
260
|
+
yield_to_maturity_continuous(self, _times, _cflows, _price)
|
261
|
+
VALUE self, _times, _cflows, _price;
|
262
|
+
{
|
263
|
+
return yield_to_maturity(self, _times, _cflows, _price, false);
|
264
|
+
}
|
265
|
+
|
266
|
+
// Yield to maturity of a discretely compounded bond
|
267
|
+
static VALUE
|
268
|
+
yield_to_maturity_discrete(self, _times, _cflows, _price)
|
269
|
+
VALUE self, _times, _cflows, _price;
|
270
|
+
{
|
271
|
+
return yield_to_maturity(self, _times, _cflows, _price, true);
|
80
272
|
}
|
81
273
|
|
82
274
|
void
|
@@ -85,13 +277,28 @@ init_bond()
|
|
85
277
|
VALUE klass, singleton;
|
86
278
|
|
87
279
|
#if 0
|
88
|
-
|
280
|
+
VALUE module = rb_define_module("Rupee");
|
281
|
+
VALUE superklass = rb_define_class_under(module, "Security", rb_cObject);
|
89
282
|
#endif
|
90
283
|
|
91
|
-
klass = rb_define_class_under(module, "Bond",
|
284
|
+
klass = rb_define_class_under(module, "Bond", superklass);
|
92
285
|
singleton = rb_singleton_class(klass);
|
93
286
|
|
94
|
-
rb_define_singleton_method(klass, "convexity",
|
95
|
-
rb_define_singleton_method(klass, "
|
96
|
-
rb_define_singleton_method(klass, "
|
287
|
+
rb_define_singleton_method(klass, "convexity", convexity_discrete, 3);
|
288
|
+
rb_define_singleton_method(klass, "continuous_convexity", convexity_continuous, 3);
|
289
|
+
rb_define_singleton_method(klass, "continuous_duration", duration_continuous, 3);
|
290
|
+
rb_define_singleton_method(klass, "continuous_macaulay", macaulay_continuous, 3);
|
291
|
+
rb_define_singleton_method(klass, "continuous_price", price_continuous, 3);
|
292
|
+
rb_define_singleton_method(klass, "continuous_yield_to_maturity", yield_to_maturity_continuous, 3);
|
293
|
+
rb_define_alias(singleton, "continuous_yield", "continuous_yield_to_maturity");
|
294
|
+
rb_define_alias(singleton, "continuous_ytm", "continuous_yield_to_maturity");
|
295
|
+
rb_define_singleton_method(klass, "duration", duration_discrete, 3);
|
296
|
+
rb_define_alias(singleton, "dur", "duration");
|
297
|
+
rb_define_singleton_method(klass, "macaulay", macaulay_discrete, 3);
|
298
|
+
rb_define_alias(singleton, "macaulary_duration", "macaulay");
|
299
|
+
rb_define_singleton_method(klass, "price", price_discrete, 3);
|
300
|
+
rb_define_alias(singleton, "value", "price");
|
301
|
+
rb_define_singleton_method(klass, "yield_to_maturity", yield_to_maturity_discrete, 3);
|
302
|
+
rb_define_alias(singleton, "yield", "yield_to_maturity");
|
303
|
+
rb_define_alias(singleton, "ytm", "yield_to_maturity");
|
97
304
|
}
|
data/ext/rupee/future.c
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
#include "rupee.h"
|
2
|
+
|
3
|
+
double
|
4
|
+
future_price(S, r, ttm)
|
5
|
+
double S, r, ttm;
|
6
|
+
{
|
7
|
+
return S * exp(r * ttm);
|
8
|
+
}
|
9
|
+
|
10
|
+
static VALUE
|
11
|
+
price(self, _S, _r, _ttm)
|
12
|
+
VALUE self, _S, _r, _ttm;
|
13
|
+
{
|
14
|
+
double S, r, ttm;
|
15
|
+
|
16
|
+
S = NUM2DBL(_S);
|
17
|
+
r = NUM2DBL(_r);
|
18
|
+
ttm = NUM2DBL(_ttm);
|
19
|
+
|
20
|
+
return rb_float_new(future_price(S, r, ttm));
|
21
|
+
}
|
22
|
+
|
23
|
+
void
|
24
|
+
init_future()
|
25
|
+
{
|
26
|
+
VALUE klass, singleton;
|
27
|
+
|
28
|
+
#if 0
|
29
|
+
VALUE module = rb_define_module("Rupee");
|
30
|
+
VALUE superklass = rb_define_class_under(module, "Security", rb_cObject);
|
31
|
+
#endif
|
32
|
+
|
33
|
+
klass = rb_define_class_under(module, "Future", superklass);
|
34
|
+
singleton = rb_singleton_class(klass);
|
35
|
+
|
36
|
+
rb_define_singleton_method(klass, "price", price, 3);
|
37
|
+
rb_define_alias(singleton, "value", "price");
|
38
|
+
}
|
data/ext/rupee/option.c
CHANGED
@@ -164,15 +164,16 @@ init_option()
|
|
164
164
|
|
165
165
|
#if 0
|
166
166
|
VALUE module = rb_define_module("Rupee");
|
167
|
+
VALUE superklass = rb_define_class_under(module, "Security", rb_cObject);
|
167
168
|
#endif
|
168
169
|
|
169
|
-
klass = rb_define_class_under(module, "Option",
|
170
|
+
klass = rb_define_class_under(module, "Option", superklass);
|
170
171
|
singleton = rb_singleton_class(klass);
|
171
172
|
|
173
|
+
rb_define_singleton_method(klass, "black76", rupee_black76, 6);
|
172
174
|
rb_define_singleton_method(klass, "black_scholes", rupee_black_scholes, 7);
|
173
175
|
rb_define_alias(singleton, "bs", "black_scholes");
|
174
176
|
rb_define_singleton_method(klass, "generalized_black_scholes",
|
175
177
|
rupee_generalized_black_scholes, 7);
|
176
178
|
rb_define_alias(singleton, "gbs", "generalized_black_scholes");
|
177
|
-
rb_define_singleton_method(klass, "black76", rupee_black76, 6);
|
178
179
|
}
|
data/ext/rupee/rupee.c
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
#include "rupee.h"
|
2
2
|
|
3
|
-
VALUE module;
|
3
|
+
VALUE module, superklass;
|
4
4
|
|
5
5
|
void
|
6
6
|
Init_rupee()
|
7
7
|
{
|
8
8
|
module = rb_define_module("Rupee");
|
9
|
+
superklass = rb_define_class_under(module, "Security", rb_cObject);
|
9
10
|
|
10
11
|
init_distribution();
|
11
12
|
init_option();
|
12
13
|
init_bond();
|
14
|
+
init_future();
|
13
15
|
}
|
data/ext/rupee/rupee.h
CHANGED
@@ -7,9 +7,12 @@
|
|
7
7
|
#include <stdbool.h>
|
8
8
|
|
9
9
|
extern VALUE module;
|
10
|
+
extern VALUE superklass;
|
10
11
|
|
11
|
-
/*
|
12
|
+
/* Utilities */
|
12
13
|
double *rtofa(double *dest, VALUE src, int len);
|
14
|
+
double avg(double x, double y);
|
15
|
+
double simple_df(double r, double time, bool discrete);
|
13
16
|
|
14
17
|
/* Statistics */
|
15
18
|
double cnd(double);
|
@@ -21,7 +24,13 @@ double gbs(const char *call_put_flag, double S, double X, double T, double r,
|
|
21
24
|
void init_option();
|
22
25
|
|
23
26
|
/* Bonds */
|
24
|
-
double
|
27
|
+
double bond_dur(double *times, double *cflows, double r, int len, bool discrete);
|
28
|
+
double bond_price(double *times, double *cflows, double r, int len, bool discrete);
|
29
|
+
double bond_ytm(double *times, double *cflows, double r, int len, bool discrete);
|
25
30
|
void init_bond();
|
26
31
|
|
32
|
+
/* Futures */
|
33
|
+
double future_price(double S, double r, double ttm);
|
34
|
+
void init_future();
|
35
|
+
|
27
36
|
#endif
|
data/ext/rupee/util.c
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
#include "rupee.h"
|
2
|
+
|
3
|
+
double
|
4
|
+
simple_df(r, time, discrete)
|
5
|
+
double r, time;
|
6
|
+
bool discrete;
|
7
|
+
{
|
8
|
+
if (discrete)
|
9
|
+
return 1.0 / pow(1.0 + r, time);
|
10
|
+
else
|
11
|
+
return exp(-r * time);
|
12
|
+
}
|
13
|
+
|
14
|
+
double *
|
15
|
+
rtofa(dest, src, len)
|
16
|
+
double *dest;
|
17
|
+
VALUE src;
|
18
|
+
int len;
|
19
|
+
{
|
20
|
+
int i;
|
21
|
+
VALUE *ary;
|
22
|
+
|
23
|
+
ary = RARRAY_PTR(src);
|
24
|
+
|
25
|
+
for (i = 0; i < len; i++)
|
26
|
+
dest[i] = NUM2DBL(ary[i]);
|
27
|
+
|
28
|
+
return dest;
|
29
|
+
}
|
30
|
+
|
31
|
+
double
|
32
|
+
avg(x, y)
|
33
|
+
double x, y;
|
34
|
+
{
|
35
|
+
return 0.5 * (x + y);
|
36
|
+
}
|
data/lib/rupee.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require "rupee/rupee" # keep this as the first require
|
2
2
|
require "rupee/version"
|
3
|
+
require "rupee/security"
|
4
|
+
require "rupee/option.rb"
|
3
5
|
|
4
6
|
# Rupee aims to provide user-friendly tools for use in financial applications.
|
5
7
|
# The gem is in its development stages, but it currently offers:
|
@@ -16,5 +18,6 @@ require "rupee/version"
|
|
16
18
|
|
17
19
|
# This module contains all modules and classes associated with Rupee
|
18
20
|
module Rupee
|
21
|
+
# autoload :Option, "rupee/option.rb"
|
19
22
|
autoload :Quote, "rupee/quote"
|
20
23
|
end
|
data/lib/rupee/option.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require "rupee/security"
|
2
|
+
|
3
|
+
module Rupee
|
4
|
+
class Option < Security
|
5
|
+
attr_accessor :type, :underlying, :strike, :time, :rate, :div_yield, :volatility, :price
|
6
|
+
alias :rfr :rate
|
7
|
+
alias :rfr= :rate=
|
8
|
+
alias :risk_free_rate :rate
|
9
|
+
alias :risk_free_rate= :rate=
|
10
|
+
alias :value :price
|
11
|
+
alias :value= :price=
|
12
|
+
|
13
|
+
def black_scholes
|
14
|
+
@value = self.class.black_scholes @type.to_s, @underlying, @strike, @time, @rate, @div_yield, @volatility
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Call < Option
|
19
|
+
def initialize(opts = {})
|
20
|
+
@type = "call"
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Put < Option
|
26
|
+
def initialize(opts = {})
|
27
|
+
@type = :put
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Rupee
|
2
|
+
# An abstract class from which all Rupee security types inherit
|
3
|
+
class Security
|
4
|
+
# Automatically sets all arguments passed to <tt>initialize</tt> to
|
5
|
+
# instance variables if they exist (think Rails mass assignment).
|
6
|
+
#
|
7
|
+
# require "rupee"
|
8
|
+
#
|
9
|
+
# call = Rupee::Call.new(
|
10
|
+
# :underlying => 60,
|
11
|
+
# :strike => 65,
|
12
|
+
# :time => 0.25,
|
13
|
+
# :rate => 0.08,
|
14
|
+
# :div_yield => 0.00,
|
15
|
+
# :volatility => 0.3
|
16
|
+
# )
|
17
|
+
# puts call.black_scholes
|
18
|
+
# #=> 2.1333718619275794
|
19
|
+
#
|
20
|
+
# You still have the option of avoiding the creation of an object (and the
|
21
|
+
# overhead it entails) by using the class methods directly:
|
22
|
+
#
|
23
|
+
# require "rupee"
|
24
|
+
#
|
25
|
+
# puts Rupee::Option.black_scholes "c", 60, 65, 0.25, 0.08, 0, 0.3
|
26
|
+
# #=> 2.1333718619275794
|
27
|
+
def initialize(opts = {})
|
28
|
+
opts.each do |key, value|
|
29
|
+
writer = key.to_s.+("=").to_sym
|
30
|
+
if respond_to?(writer)
|
31
|
+
send writer, value
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/rupee/version.rb
CHANGED
data/spec/c/bond_spec.rb
CHANGED
@@ -1,17 +1,10 @@
|
|
1
1
|
require File.dirname(__FILE__) + "/../spec_helper"
|
2
2
|
|
3
3
|
# Discrete discounting
|
4
|
-
# bonds price = 102.531
|
5
|
-
# bond yield to maturity = 0.09
|
6
|
-
# bond duration = 2.73895
|
7
4
|
# bond duration modified = 2.5128
|
8
|
-
# bond convexity =8.93248
|
9
5
|
# new bond price = 100
|
10
6
|
#Continous discounting
|
11
|
-
# bonds price = 101.464
|
12
7
|
# bond yield to maturity = 0.09
|
13
|
-
# bond duration = 2.73753
|
14
|
-
# bond convexity =7.86779
|
15
8
|
# new bond price = 104.282
|
16
9
|
|
17
10
|
TOLERANCE = 0.001
|
@@ -23,17 +16,41 @@ describe Rupee::Bond do
|
|
23
16
|
@r = 0.09
|
24
17
|
end
|
25
18
|
|
19
|
+
describe "with discrete discounting" do
|
20
|
+
it "should produce an accurate price" do
|
21
|
+
Rupee::Bond.price(@times, @cflows, @r).should be_within(TOLERANCE).of 102.531
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should produce an accurate duration" do
|
25
|
+
Rupee::Bond.duration(@times, @cflows, @r).should be_within(TOLERANCE).of 2.73895
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should produce an accurate convexity" do
|
29
|
+
Rupee::Bond.convexity(@times, @cflows, @r).should be_within(TOLERANCE).of 8.93248
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should produce an accurate yield to maturity" do
|
33
|
+
@price = Rupee::Bond.price(@times, @cflows, @r)
|
34
|
+
Rupee::Bond.yield_to_maturity(@times, @cflows, @price).should be_within(TOLERANCE).of @r
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
26
38
|
describe "with continuous discounting" do
|
27
39
|
it "should produce an accurate price" do
|
28
|
-
Rupee::Bond.
|
40
|
+
Rupee::Bond.continuous_price(@times, @cflows, @r).should be_within(TOLERANCE).of 101.464
|
29
41
|
end
|
30
42
|
|
31
43
|
it "should produce an accurate duration" do
|
32
|
-
Rupee::Bond.
|
44
|
+
Rupee::Bond.continuous_duration(@times, @cflows, @r).should be_within(TOLERANCE).of 2.73753
|
33
45
|
end
|
34
46
|
|
35
47
|
it "should produce an accurate convexity" do
|
36
|
-
Rupee::Bond.
|
48
|
+
Rupee::Bond.continuous_convexity(@times, @cflows, @r).should be_within(TOLERANCE).of 7.86779
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should produce an accurate yield to maturity" do
|
52
|
+
@price = Rupee::Bond.continuous_price(@times, @cflows, @r)
|
53
|
+
Rupee::Bond.continuous_yield_to_maturity(@times, @cflows, @price).should be_within(TOLERANCE).of @r
|
37
54
|
end
|
38
55
|
end
|
39
56
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../spec_helper"
|
2
|
+
|
3
|
+
describe Rupee::Future do
|
4
|
+
before :each do
|
5
|
+
@underlying = 100
|
6
|
+
@rfr = 0.10
|
7
|
+
@ttm = 0.5
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should produce an accurate price" do
|
11
|
+
Rupee::Future.price(@underlying, @rfr, @ttm).should be_within(TOLERANCE).of 105.127
|
12
|
+
end
|
13
|
+
end
|
data/tasks/benchmark.rake
CHANGED
@@ -3,19 +3,48 @@ autoload :Benchmark, "benchmark"
|
|
3
3
|
namespace :benchmark do
|
4
4
|
task :default => :black_scholes
|
5
5
|
|
6
|
-
desc "Run Black-Scholes
|
6
|
+
desc "Run Black-Scholes on a European call 100,000 times"
|
7
7
|
task :black_scholes do
|
8
8
|
require "rupee"
|
9
9
|
require "rupee/benchmark"
|
10
10
|
|
11
|
-
n =
|
12
|
-
Benchmark.bm(
|
13
|
-
x.report "
|
11
|
+
n = 100_000
|
12
|
+
Benchmark.bm(19) do |x|
|
13
|
+
x.report "Rupee (class):" do
|
14
14
|
n.times do
|
15
15
|
Rupee::Option.black_scholes "c", 60, 65, 0.25, 0.08, 0, 0.3
|
16
16
|
end
|
17
17
|
end
|
18
|
-
|
18
|
+
|
19
|
+
x.report "Rupee (one object):" do
|
20
|
+
call = Rupee::Call.new(
|
21
|
+
:underlying => 60,
|
22
|
+
:strike => 65,
|
23
|
+
:time => 0.25,
|
24
|
+
:rate => 0.08,
|
25
|
+
:div_yield => 0.00,
|
26
|
+
:volatility => 0.3
|
27
|
+
)
|
28
|
+
|
29
|
+
n.times do
|
30
|
+
call.black_scholes
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
x.report "Rupee (new object):" do
|
35
|
+
n.times do
|
36
|
+
Rupee::Call.new(
|
37
|
+
:underlying => 60,
|
38
|
+
:strike => 65,
|
39
|
+
:time => 0.25,
|
40
|
+
:rate => 0.08,
|
41
|
+
:div_yield => 0.00,
|
42
|
+
:volatility => 0.3
|
43
|
+
).black_scholes
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
x.report("Pure Ruby:") do
|
19
48
|
n.times do
|
20
49
|
Rupee::Benchmark.black_scholes "c", 60, 65, 0.25, 0.08, 0.3
|
21
50
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rupee
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -13,7 +13,7 @@ date: 2011-09-28 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
16
|
-
requirement: &
|
16
|
+
requirement: &71003600 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '1.0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *71003600
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &71002450 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '2.0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *71002450
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: autotest
|
38
|
-
requirement: &
|
38
|
+
requirement: &70999390 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ~>
|
@@ -43,7 +43,7 @@ dependencies:
|
|
43
43
|
version: '4.0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70999390
|
47
47
|
description: rupee aims to provide user-friendly tools for use in financial gems and
|
48
48
|
applications.
|
49
49
|
email:
|
@@ -61,20 +61,24 @@ files:
|
|
61
61
|
- README.md
|
62
62
|
- Rakefile
|
63
63
|
- ext/rupee/bond.c
|
64
|
-
- ext/rupee/conv.c
|
65
64
|
- ext/rupee/distribution.c
|
66
65
|
- ext/rupee/extconf.rb
|
66
|
+
- ext/rupee/future.c
|
67
67
|
- ext/rupee/option.c
|
68
68
|
- ext/rupee/rupee.c
|
69
69
|
- ext/rupee/rupee.h
|
70
|
+
- ext/rupee/util.c
|
70
71
|
- lib/rupee.rb
|
71
72
|
- lib/rupee/benchmark.rb
|
73
|
+
- lib/rupee/option.rb
|
72
74
|
- lib/rupee/quote.rb
|
73
75
|
- lib/rupee/quote/source.rb
|
76
|
+
- lib/rupee/security.rb
|
74
77
|
- lib/rupee/version.rb
|
75
78
|
- rupee.gemspec
|
76
79
|
- spec/c/bond_spec.rb
|
77
80
|
- spec/c/distribution_spec.rb
|
81
|
+
- spec/c/future_spec.rb
|
78
82
|
- spec/c/option_spec.rb
|
79
83
|
- spec/import/quote_spec.rb
|
80
84
|
- spec/spec_helper.rb
|
@@ -107,5 +111,6 @@ summary: Financial tools for Ruby
|
|
107
111
|
test_files:
|
108
112
|
- spec/c/bond_spec.rb
|
109
113
|
- spec/c/distribution_spec.rb
|
114
|
+
- spec/c/future_spec.rb
|
110
115
|
- spec/c/option_spec.rb
|
111
116
|
- spec/import/quote_spec.rb
|
data/ext/rupee/conv.c
DELETED