calc_profit 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module CalcProfit
4
+ class Transaction
5
+ attr_reader :code, :date, :price, :ref, :info, :raw_shares
6
+ attr_accessor :shares
7
+
8
+ def initialize(trans_hash)
9
+ if trans_hash[:code] =~ /^\s*[pP]/
10
+ @code = 'P'
11
+ elsif trans_hash[:code] =~ /^\s*[sS]/
12
+ @code = 'S'
13
+ else
14
+ raise "Bad code (#{code}) supplied to Transaction constructor."
15
+ end
16
+ @price = trans_hash[:price].to_f
17
+ @shares = trans_hash[:shares].to_f.round
18
+ @raw_shares = trans_hash[:raw_shares].to_f.round
19
+ case trans_hash[:date]
20
+ when String
21
+ @date = Date.parse(trans_hash[:date])
22
+ when Date
23
+ @date = trans_hash[:date]
24
+ else
25
+ raise "Bad date (#{date}) supplied to Transaction constructor."
26
+ end
27
+ @ref = trans_hash[:ref]
28
+ @info = trans_hash[:info]
29
+ end
30
+
31
+ def table_row
32
+ hash = {}
33
+ hash[:ref] = ref
34
+ hash[:date] = date.iso
35
+ hash[:code] = code
36
+ hash[:shares] = shares.to_s
37
+ hash[:price] = price.to_s
38
+ hash[:info] = info
39
+ hash
40
+ end
41
+
42
+ def purchase?
43
+ code == 'P'
44
+ end
45
+
46
+ def sale?
47
+ code == 'S'
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,181 @@
1
+ module CalcProfit
2
+ class TransactionGroup
3
+ attr_reader :profit, :matches
4
+ attr_reader :purchase_shares, :sale_shares
5
+
6
+ def initialize(table = nil)
7
+ @purchases = []
8
+ @sales = []
9
+ @trans_by_ref = {}
10
+ @matches = []
11
+ @profit = 0
12
+ @purchase_shares = 0
13
+ @sale_shares = 0
14
+ if table
15
+ add_table_transactions(table)
16
+ end
17
+ end
18
+
19
+ def add_table_transactions(table)
20
+ table.rows.each do |row|
21
+ self << Transaction.new(row)
22
+ end
23
+ end
24
+
25
+ def <<(t)
26
+ # Add a new transaction
27
+ if t.purchase?
28
+ @purchases << t
29
+ @trans_by_ref[t.ref.to_s] = t
30
+ elsif t.sale?
31
+ @sales << t
32
+ @trans_by_ref[t.ref.to_s] = t
33
+ else
34
+ raise ArgumentError,
35
+ "Can only add purchase or sale transactions to TransactionGroup."
36
+ end
37
+ end
38
+
39
+ def match
40
+ pp = @purchases.dup2
41
+ ss = @sales.dup2
42
+ until pp.empty? or ss.empty? do
43
+ # For each sale remaining, find the remaining purchase
44
+ # with 6 months having the lowest price and match
45
+ # to the extent of the lower number of shares
46
+ break if ss.empty?
47
+ best_sale = ss.max { |a, b| a.price <=> b.price }
48
+ eligible_purchases = pp.partition { |p|
49
+ p.date.within_6mos_of?(best_sale.date) && p.price < best_sale.price
50
+ }
51
+ eligible_purchases = eligible_purchases[0]
52
+ if eligible_purchases.empty?
53
+ ss.delete(best_sale)
54
+ next
55
+ end
56
+ best_prch = eligible_purchases.min { |a, b| a.price <=> b.price }
57
+ # Take number of shares as the smaller of the sale or buy
58
+ if best_sale.shares >= best_prch.shares
59
+ s = Transaction.new(code: 'S', date: best_sale.date, price: best_sale.price,
60
+ shares: best_prch.shares, ref: best_sale.ref,
61
+ info: best_sale.info)
62
+ p = Transaction.new(code: 'P', date: best_prch.date, price: best_prch.price,
63
+ shares: best_prch.shares, ref: best_prch.ref,
64
+ info: best_prch.info)
65
+ best_sale.shares -= best_prch.shares
66
+ pp.delete(best_prch)
67
+ else
68
+ s = Transaction.new(code: 'S', date: best_sale.date, price: best_sale.price,
69
+ shares: best_sale.shares, ref: best_sale.ref,
70
+ info: best_sale.info)
71
+ p = Transaction.new(code: 'P', date: best_prch.date, price: best_prch.price,
72
+ shares: best_sale.shares, ref: best_prch.ref,
73
+ info: best_prch.info)
74
+ best_prch.shares -= best_sale.shares
75
+ ss.delete(best_sale)
76
+ end
77
+ m = Match.new(p, s)
78
+ unless m.profit < 0.01
79
+ @matches << m
80
+ @profit += m.profit
81
+ @purchase_shares += p.shares
82
+ @sale_shares += s.shares
83
+ end
84
+ end
85
+ @matches.empty? ? nil : match_table
86
+ end
87
+
88
+ def match_table
89
+ table = Table.new
90
+ @matches.each do |m|
91
+ table << m.table_row
92
+ end
93
+ table
94
+ end
95
+
96
+ def transaction_table
97
+ trans = @purchases + @sales
98
+ table = Table.new
99
+ trans.each do |tr|
100
+ table << tr.table_row
101
+ end
102
+ table
103
+ end
104
+
105
+ # This method attempts to find an optimal, profit-maximizing set of
106
+ # matches by formulating a transportation problem and passing it to
107
+ # lp_solve. Requires lp_solve, which on ubuntu can be installed with
108
+ #
109
+ # $ apt-get install lp_solve
110
+ def tp_solve
111
+ lpf = File.new('profit.lp', 'w')
112
+
113
+ # Write the objective function
114
+ lpf.print("max: ")
115
+ penalty = -1000.0
116
+ have_eqn = false
117
+ @purchases.each do |p|
118
+ @sales.each do |s|
119
+ if p.date.within_6mos_of?(s.date) and s.price > p.price
120
+ profit = s.price - p.price
121
+ lpf.print("#{profit} X_#{p.ref}@@#{s.ref} + ")
122
+ have_eqn = true
123
+ else
124
+ lpf.print("#{penalty} X_#{p.ref}@@#{s.ref} + ")
125
+ have_eqn = true
126
+ end
127
+ end
128
+ end
129
+ lpf.print("0;\n\n") if have_eqn
130
+
131
+ # Write the purchase contraints
132
+ @purchases.each do |p|
133
+ lpf.print("P_#{p.ref}: ")
134
+ @sales.each do |s|
135
+ lpf.print("X_#{p.ref}@@#{s.ref} + ")
136
+ end
137
+ lpf.print("0 <= #{p.shares};\n")
138
+ end
139
+
140
+ # Write the sales contraints
141
+ @sales.each do |s|
142
+ lpf.print("S_#{s.ref}: ")
143
+ @purchases.each do |p|
144
+ lpf.print("X_#{p.ref}@@#{s.ref} + ")
145
+ end
146
+ lpf.print("0 <= #{s.shares};\n")
147
+ end
148
+ lpf.close
149
+
150
+ # Now, pass the file into lp_solve and parse the solution
151
+ calculated_profit = 0.0
152
+ IO.popen("lp_solve profit.lp") do |sol|
153
+ #File.open("profit.sol") do |sol|
154
+ sol.each do |line|
155
+ next if line =~ /^\s*$/
156
+ if line =~ /^Value of objective function:\s+([-+e.\d]+)/
157
+ calculated_profit = $1.to_f
158
+ elsif line =~ /^X_(\S*)@@(\S*)\s+(\d+)$/
159
+ next if $3.to_f == 0.0
160
+ p = @trans_by_ref[$1].dup
161
+ s = @trans_by_ref[$2].dup
162
+ p.shares = s.shares = $3.to_f
163
+ if s.price - p.price < 0.01
164
+ STDERR.print "Sale #{s.ref} @ #{s.price} and Purchase #{p.ref} @ #{p.price} has no profit.\n"
165
+ else
166
+ m = Match.new(p, s)
167
+ @matches << m
168
+ @profit += m.profit
169
+ @purchase_shares += p.shares
170
+ @sale_shares += s.shares
171
+ end
172
+ end
173
+ end
174
+ end
175
+ unless calculated_profit == @profit
176
+ STDERR.print "Warning: profit calculated by lp_solve was #{calculated_profit}; by matches was #{@profit}\n"
177
+ end
178
+ !@matches.empty?
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,7 @@
1
+ module CalcProfit
2
+ MAJOR = 0
3
+ MINOR = 1
4
+ PATCH = 1
5
+
6
+ VERSION = [MAJOR, MINOR, PATCH].compact.join('.')
7
+ end
@@ -0,0 +1,23 @@
1
+ require 'test_helper'
2
+
3
+ class AddressTest < ActiveSupport::TestCase
4
+ def test_easy
5
+ a = Address.new('14625 Dearborn', '', 'Overland Park', 'KS', '66223', '')
6
+ assert_equal('14625 Dearborn', a.street1, "Bad parse of street1")
7
+ assert_equal('', a.street2, "Bad parse of street2")
8
+ assert_equal('Overland Park', a.city, "Bad parse of city")
9
+ assert_equal('KS', a.state, "Bad parse of state")
10
+ assert_equal('66223', a.zip, "Bad parse of zip")
11
+ assert_equal('United States', a.country, "Bad parse of country")
12
+ end
13
+
14
+ def test_hard
15
+ a = Address.new('14625 Dearborn', '', 'Overland Park, KS 66223')
16
+ assert_equal('14625 Dearborn', a.street1, "Bad parse of street1")
17
+ assert_equal('', a.street2, "Bad parse of street2")
18
+ assert_equal('Overland Park', a.city, "Bad parse of city")
19
+ assert_equal('KS', a.state, "Bad parse of state")
20
+ assert_equal('66223', a.zip, "Bad parse of zip")
21
+ assert_equal('United States', a.country, "Bad parse of country")
22
+ end
23
+ end
data/test/TestDBase.rb ADDED
@@ -0,0 +1,47 @@
1
+ require 'test/unit'
2
+ require 'Section16/DBase' unless defined? $db
3
+
4
+ begin
5
+ $db.do('DROP TABLE junk') if $db.tables.include?('junk')
6
+ res = $db.do('CREATE TABLE junk (i integer primary key, s text);')
7
+ rescue DBI::DatabaseError => e
8
+ puts "Error: #{e.errstr}"
9
+ end
10
+
11
+ class TestDate < Test::Unit::TestCase
12
+
13
+ def test_dbase_is_up
14
+ assert($db.connected?)
15
+ end
16
+
17
+ def test_insert
18
+ $db.do("INSERT INTO junk (i, s) VALUES (1, 'a');")
19
+ $db.do("INSERT INTO junk (i, s) VALUES (2, 'b');")
20
+ $db.do("INSERT INTO junk (i, s) VALUES (3, 'c');")
21
+ $db.do("INSERT INTO junk (i, s) VALUES (4, 'd');")
22
+ rows = $db.select_all('SELECT * FROM junk;')
23
+ assert_equal(4, rows.length)
24
+ end
25
+
26
+ def test_odb
27
+ $odb.trace(2)
28
+ assert($odb.ping, "No ping response to $odb")
29
+ assert($odb.tables.include?('owner'), "Can't find owner table")
30
+ assert($odb.tables.include?('issuer'), "Can't find issuer table")
31
+ assert($odb.tables.include?('own_iss'), "Can't find own_iss table")
32
+ assert($odb.tables.include?('own_fil'), "Can't find own_fil table")
33
+ end
34
+
35
+ class Junk < ActiveRecord::Base
36
+ set_table_name('junk')
37
+ set_primary_key('i')
38
+ end
39
+
40
+ def test_zactive_record
41
+ assert_equal(Junk.find(2, 4).length, 2,
42
+ "Active record class didn't find 2 Junks")
43
+ end
44
+ end
45
+
46
+
47
+
data/test/TestDate.rb ADDED
@@ -0,0 +1,267 @@
1
+ # Code Generated by ZenTest v. 2.3.0
2
+ # classname: asrt / meth = ratio%
3
+ # Date: 0 / 15 = 0.00%
4
+
5
+ require 'test/unit' unless defined? $ZENTEST and $ZENTEST
6
+ require 'Section16/Date'
7
+
8
+ class TestDate < Test::Unit::TestCase
9
+
10
+ def test_nth_wday_in_year_month(n, wday, year, month)
11
+ assert_equals(Date.new(1957, 30, 11),
12
+ Date.nth_wday_in_year_month(5, 6, 1957, 11),
13
+ "1957-11-30 is 5th Saturday in November, 1957")
14
+ assert_equals(Date.new(1957, 30, 11),
15
+ Date.nth_wday_in_year_month(-1, 6, 1957, 11),
16
+ "1957-11-30 is last Saturday in November, 1957")
17
+ end
18
+
19
+ def test_last_day_in_year_month
20
+ assert_equal(29, Date.last_day_in_year_month(2000, 2),
21
+ "February 28 is last day in Feb, 2000")
22
+ assert_equal(28, Date.last_day_in_year_month(1900, 2),
23
+ "February 29 is last day in Feb, 1900")
24
+ end
25
+
26
+ def test_weekend_eh
27
+ assert(!Date.new(1985, 7, 16).weekend?,
28
+ "1985-07-16 is not a weekend day")
29
+ assert(Date.new(1957, 9, 22).weekend?,
30
+ "1957-09-22 is a weekend day")
31
+ end
32
+
33
+ def test_easter_this_year
34
+ assert_equal(Date.new(2000, 4, 23),
35
+ Date.new(2000, 9, 22).easter_this_year,
36
+ "2000-4-23 should be Easter in 2000")
37
+ end
38
+
39
+ def test_easter_eh
40
+ d = Date.new(2000, 8, 18)
41
+ assert(!d.easter?, "#{d} should not be Easter")
42
+ assert(Date.new(2000, 4, 23).easter?,
43
+ "2000-04-23 should be Easter")
44
+ assert(Date.new(1988, 4, 3).easter?,
45
+ "1988-04-03 should be Easter")
46
+ end
47
+
48
+ def test_nth_wday_in_month_eh
49
+ assert(Date.new(1957, 11, 30).nth_wday_in_month?(5, 6, 11),
50
+ '1957-11-30 is the 5th Saturday in November of its year')
51
+ assert(Date.new(1957, 9, 22).nth_wday_in_month?(4, 0, 9),
52
+ '1957-09-22 is the 4th Sunday in September of its year')
53
+ end
54
+
55
+ def test_fed_fixed_holiday_eh
56
+ assert(Date.new(1988, 1, 1).fed_fixed_holiday?,
57
+ "Feds celebrate New Years Day")
58
+ assert(Date.new(1988, 7, 4).fed_fixed_holiday?,
59
+ "Feds celebrate Independence Day")
60
+ assert(Date.new(1988, 11, 11).fed_fixed_holiday?,
61
+ "Feds celebrate Veterans Day")
62
+ assert(Date.new(1988, 12, 25).fed_fixed_holiday?,
63
+ "Feds celebrate Christmas Day")
64
+ assert(Date.new(1988, 12, 31).fed_fixed_holiday?,
65
+ "Feds celebrate New Years Eve")
66
+ (1988..1990).each do |y|
67
+ h = []
68
+ (1..12).each do |m|
69
+ (1..Date.last_day_in_year_month(y, m)).each do |d|
70
+ d = Date.new(y, m, d)
71
+ h.push(d) if d.fed_fixed_holiday?
72
+ end
73
+ end
74
+ assert_equal(5, h.length,
75
+ "Found #{h.length} fixed holidays in #{y}")
76
+ end
77
+ end
78
+
79
+ def test_fed_moveable_feast_eh
80
+ assert(Date.new(1988, 1, 18).fed_moveable_feast?,
81
+ "Feds celebrate MLK Day")
82
+ assert(Date.new(1988, 2, 15).fed_moveable_feast?,
83
+ "Feds celebrate Washington's Birthday")
84
+ assert(Date.new(1988, 5, 30).fed_moveable_feast?,
85
+ "Feds celebrate Memorial Day")
86
+ assert(Date.new(1988, 9, 5).fed_moveable_feast?,
87
+ "Feds celebrate Labor Day")
88
+ assert(Date.new(1988, 10, 10).fed_moveable_feast?,
89
+ "Feds celebrate Columbus Day")
90
+ assert(Date.new(1988, 11, 24).fed_moveable_feast?,
91
+ "Feds celebrate Tahnksgiving")
92
+ (1988..1990).each do |y|
93
+ h = []
94
+ (1..12).each do |m|
95
+ (1..Date.last_day_in_year_month(y, m)).each do |d|
96
+ d = Date.new(y, m, d)
97
+ h.push(d) if d.fed_moveable_feast?
98
+ end
99
+ end
100
+ assert_equal(6, h.length,
101
+ "Found #{h.length} floating holidays in #{y}")
102
+ end
103
+ end
104
+
105
+ def test_fed_holiday_eh
106
+ assert(Date.new(1988, 1, 1).fed_holiday?,
107
+ "Feds celebrate New Years Day")
108
+ assert(Date.new(1988, 1, 18).fed_holiday?,
109
+ "Feds celebrate MLK Day")
110
+ assert(Date.new(1988, 2, 15).fed_holiday?,
111
+ "Feds celebrate Washington's Birthday")
112
+ assert(Date.new(1988, 5, 30).fed_holiday?,
113
+ "Feds celebrate Memorial Day")
114
+ assert(Date.new(1988, 7, 4).fed_holiday?,
115
+ "Feds celebrate Independence Day")
116
+ assert(Date.new(1988, 9, 5).fed_holiday?,
117
+ "Feds celebrate Labor Day")
118
+ assert(Date.new(1988, 10, 10).fed_holiday?,
119
+ "Feds celebrate Columbus Day")
120
+ assert(Date.new(1988, 11, 24).fed_holiday?,
121
+ "Feds celebrate Tahnksgiving")
122
+ assert(Date.new(1988, 11, 11).fed_holiday?,
123
+ "Feds celebrate Veterans Day")
124
+ assert(Date.new(1988, 12, 25).fed_holiday?,
125
+ "Feds celebrate Christmas Day")
126
+ assert(Date.new(1988, 12, 31).fed_holiday?,
127
+ "Feds celebrate New Years Eve")
128
+ end
129
+
130
+ def test_nyse_fixed_holiday_eh
131
+ assert(Date.new(1988, 1, 1).nyse_fixed_holiday?,
132
+ "NYSE celebrates New Years Day")
133
+ assert(Date.new(1988, 7, 4).nyse_fixed_holiday?,
134
+ "NYSE celebrates Independence Day")
135
+ assert(Date.new(1988, 12, 25).nyse_fixed_holiday?,
136
+ "NYSE celebrates Christmas Day")
137
+ (1988..1990).each do |y|
138
+ h = []
139
+ (1..12).each do |m|
140
+ (1..Date.last_day_in_year_month(y, m)).each do |d|
141
+ d = Date.new(y, m, d)
142
+ h.push(d) if d.nyse_fixed_holiday?
143
+ end
144
+ end
145
+ assert_equal(3, h.length,
146
+ "Found #{h.length} fixed NYSE holidays in #{y}")
147
+ end
148
+ end
149
+
150
+ def test_nyse_moveable_feast_eh
151
+ assert(Date.new(1988, 1, 18).nyse_moveable_feast?,
152
+ "NYSE celebrates MLK Day")
153
+ assert(Date.new(1988, 2, 15).nyse_moveable_feast?,
154
+ "NYSE celebrates Washington's Birthday")
155
+ assert(Date.new(1988, 4, 1).nyse_moveable_feast?,
156
+ "NYSE celebrates Good Friday")
157
+ assert(Date.new(1988, 5, 30).nyse_moveable_feast?,
158
+ "NYSE celebrates Memorial Day")
159
+ assert(Date.new(1988, 9, 5).nyse_moveable_feast?,
160
+ "NYSE celebrates Labor Day")
161
+ assert(Date.new(1988, 11, 24).nyse_moveable_feast?,
162
+ "NYSE celebrates Thanksgiving")
163
+ (1988..1990).each do |y|
164
+ h = []
165
+ (1..12).each do |m|
166
+ (1..Date.last_day_in_year_month(y, m)).each do |d|
167
+ d = Date.new(y, m, d)
168
+ h.push(d) if d.nyse_moveable_feast?
169
+ end
170
+ end
171
+ assert_equal(6, h.length,
172
+ "Found #{h.length} floating holidays in #{y}")
173
+ end
174
+ end
175
+
176
+ def test_nyse_holiday_eh
177
+ assert(Date.new(1988, 1, 1).nyse_holiday?,
178
+ "NYSE celebrates New Years Day")
179
+ assert(Date.new(1988, 7, 4).nyse_holiday?,
180
+ "NYSE celebrates Independence Day")
181
+ assert(Date.new(1988, 12, 25).nyse_holiday?,
182
+ "NYSE celebrates Christmas Day")
183
+ assert(Date.new(1988, 1, 18).nyse_holiday?,
184
+ "NYSE celebrates MLK Day")
185
+ assert(Date.new(1988, 2, 15).nyse_holiday?,
186
+ "NYSE celebrates Washington's Birthday")
187
+ assert(Date.new(1988, 4, 1).nyse_holiday?,
188
+ "NYSE celebrates Good Friday")
189
+ assert(Date.new(1988, 5, 30).nyse_holiday?,
190
+ "NYSE celebrates Memorial Day")
191
+ assert(Date.new(1988, 9, 5).nyse_holiday?,
192
+ "NYSE celebrates Labor Day")
193
+ assert(Date.new(1988, 11, 24).nyse_holiday?,
194
+ "NYSE celebrates Thanksgiving")
195
+ end
196
+
197
+ def test_fed_workday_eh
198
+ assert(!Date.new(1988, 1, 1).fed_workday?,
199
+ "Feds celebrate New Years Day")
200
+ assert(!Date.new(1988, 1, 18).fed_workday?,
201
+ "Feds celebrate MLK Day")
202
+ assert(!Date.new(1988, 2, 15).fed_workday?,
203
+ "Feds celebrate Washington's Birthday")
204
+ assert(!Date.new(1988, 5, 30).fed_workday?,
205
+ "Feds celebrate Memorial Day")
206
+ assert(!Date.new(1988, 7, 4).fed_workday?,
207
+ "Feds celebrate Independence Day")
208
+ assert(!Date.new(1988, 9, 5).fed_workday?,
209
+ "Feds celebrate Labor Day")
210
+ assert(!Date.new(1988, 10, 10).fed_workday?,
211
+ "Feds celebrate Columbus Day")
212
+ assert(!Date.new(1988, 11, 24).fed_workday?,
213
+ "Feds celebrate Tahnksgiving")
214
+ assert(!Date.new(1988, 11, 11).fed_workday?,
215
+ "Feds celebrate Veterans Day")
216
+ assert(!Date.new(1988, 12, 25).fed_workday?,
217
+ "Feds celebrate Christmas Day")
218
+ assert(!Date.new(1988, 12, 31).fed_workday?,
219
+ "Feds celebrate New Years Eve")
220
+ end
221
+
222
+ def test_nyse_workday_eh
223
+ assert(!Date.new(1988, 1, 1).nyse_workday?,
224
+ "NYSE celebrates New Years Day")
225
+ assert(!Date.new(1988, 7, 4).nyse_workday?,
226
+ "NYSE celebrates Independence Day")
227
+ assert(!Date.new(1988, 12, 25).nyse_workday?,
228
+ "NYSE celebrates Christmas Day")
229
+ assert(!Date.new(1988, 1, 18).nyse_workday?,
230
+ "NYSE celebrates MLK Day")
231
+ assert(!Date.new(1988, 2, 15).nyse_workday?,
232
+ "NYSE celebrates Washington's Birthday")
233
+ assert(!Date.new(1988, 4, 1).nyse_workday?,
234
+ "NYSE celebrates Good Friday")
235
+ assert(!Date.new(1988, 5, 30).nyse_workday?,
236
+ "NYSE celebrates Memorial Day")
237
+ assert(!Date.new(1988, 9, 5).nyse_workday?,
238
+ "NYSE celebrates Labor Day")
239
+ assert(!Date.new(1988, 11, 24).nyse_workday?,
240
+ "NYSE celebrates Thanksgiving")
241
+ end
242
+
243
+ def test_next_nyse_workday
244
+ assert_equal(Date.new(1988, 3, 31).next_nyse_workday,
245
+ Date.new(1988, 4, 4),
246
+ "Next NYSE workday after 1988-03-31 is 1988-04-04")
247
+ end
248
+
249
+ def test_next_fed_workday
250
+ assert_equal(Date.new(1988, 3, 31).next_fed_workday,
251
+ Date.new(1988, 4, 1),
252
+ "Next Fed workday after 1988-03-31 is 1988-04-01")
253
+ end
254
+
255
+ def test_iso
256
+ assert_equal(Date.new(1957, 9, 2).iso, "1957-09-02", "Bad iso format")
257
+ end
258
+
259
+ def test_num
260
+ assert_equal(Date.new(1957, 9, 2).num, "19570902", "Bad num format")
261
+ end
262
+
263
+ def test_within6
264
+ assert(Date.new(1957, 9, 22).within_6mos_of?(Date.new(1957, 6, 5)))
265
+ end
266
+ end
267
+