fin 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/.gitignore +26 -0
- data/HISTORY +27 -0
- data/LICENSE +20 -0
- data/README.rdoc +36 -0
- data/Rakefile +25 -0
- data/VERSION +1 -0
- data/features/order_book.feature +9 -0
- data/features/step_definitions/order_book_steps.rb +0 -0
- data/features/support/env.rb +10 -0
- data/features/support/world.rb +12 -0
- data/lib/fin.rb +13 -0
- data/lib/fin/book.rb +50 -0
- data/lib/fin/book_manager.rb +42 -0
- data/lib/fin/changed_list.rb +49 -0
- data/lib/fin/container_list.rb +33 -0
- data/lib/fin/deal_list.rb +18 -0
- data/lib/fin/indexed_list.rb +74 -0
- data/lib/fin/models/deal.rb +75 -0
- data/lib/fin/models/instrument.rb +78 -0
- data/lib/fin/models/model.rb +39 -0
- data/lib/fin/models/money_limit.rb +81 -0
- data/lib/fin/models/order.rb +45 -0
- data/lib/fin/models/position.rb +57 -0
- data/lib/fin/order_list.rb +17 -0
- data/lib/legacy.rb +443 -0
- data/lib/version.rb +8 -0
- data/spec/fin/book_spec.rb +215 -0
- data/spec/fin/changed_list_spec.rb +16 -0
- data/spec/fin/container_list_spec.rb +63 -0
- data/spec/fin/deal_list_spec.rb +102 -0
- data/spec/fin/indexed_list_spec.rb +20 -0
- data/spec/fin/models/deal_spec.rb +140 -0
- data/spec/fin/models/instrument_spec.rb +54 -0
- data/spec/fin/models/model_spec.rb +109 -0
- data/spec/fin/models/money_limit_spec.rb +143 -0
- data/spec/fin/models/order_spec.rb +67 -0
- data/spec/fin/models/position_spec.rb +74 -0
- data/spec/fin/models/shared_examples.rb +5 -0
- data/spec/fin/order_list_spec.rb +140 -0
- data/spec/fin/shared_examples.rb +355 -0
- data/spec/spec_helper.rb +17 -0
- data/tasks/common.rake +18 -0
- data/tasks/doc.rake +14 -0
- data/tasks/gem.rake +40 -0
- data/tasks/git.rake +34 -0
- data/tasks/spec.rake +16 -0
- data/tasks/version.rake +71 -0
- metadata +155 -0
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'fin/shared_examples.rb'
|
3
|
+
|
4
|
+
describe Fin::OrderList do
|
5
|
+
subject { Fin::OrderList.new }
|
6
|
+
let(:item_index) { @item.id }
|
7
|
+
let (:new_item_book_index) {new_item.price}
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
@item = Fin::Order.new :isin_id => 1234, :id => 0, :price => 20
|
11
|
+
@item1 = Fin::Order.new :isin_id => 1234, :id => 1, :price => 10
|
12
|
+
@same_isin_item = @item1
|
13
|
+
@item2 = Fin::Order.new :isin_id => 5678, :id => 2, :price => 10
|
14
|
+
@diff_isin_item = @item2
|
15
|
+
@zero_price_item = Fin::Order.new :isin_id => 1234, :id => 3, :price => 0
|
16
|
+
@repeat_item = Fin::Order.new :isin_id => 1234, :id => 0, :price => 13
|
17
|
+
@repeat_zero_price_item = Fin::Order.new :isin_id => 1234, :id => 0, :price => 0
|
18
|
+
end
|
19
|
+
|
20
|
+
it_behaves_like 'changed_list'
|
21
|
+
|
22
|
+
specify { subject.books.should be_empty }
|
23
|
+
|
24
|
+
it 'returns order_book for any isin_id, even if it was not initialized' do
|
25
|
+
order_book = subject.books[1313]
|
26
|
+
order_book.should_not be_nil
|
27
|
+
order_book.should be_an Fin::Book
|
28
|
+
order_book.should be_empty
|
29
|
+
end
|
30
|
+
|
31
|
+
describe 'adding item' do
|
32
|
+
let(:expected_number_of_books) { 1 }
|
33
|
+
|
34
|
+
context 'to empty OrderList' do
|
35
|
+
let(:new_item) { @item }
|
36
|
+
|
37
|
+
it_behaves_like 'adding_item_to_books'
|
38
|
+
|
39
|
+
context 'with zero price' do
|
40
|
+
let(:new_item) { @zero_price_item }
|
41
|
+
|
42
|
+
it_behaves_like 'not_adding_item_to_books'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'to non-empty OrderList' do
|
47
|
+
before(:each) do
|
48
|
+
subject.add(@item).size.should == 1
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'with existing isin' do
|
52
|
+
let(:new_item) { @same_isin_item }
|
53
|
+
|
54
|
+
it_behaves_like 'adding_item_to_books'
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'with different isin' do
|
58
|
+
let(:new_item) { @diff_isin_item }
|
59
|
+
let(:expected_number_of_books) { 2 }
|
60
|
+
|
61
|
+
it_behaves_like 'adding_item_to_books'
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'with zero price' do
|
65
|
+
let(:new_item) { @zero_price_item }
|
66
|
+
|
67
|
+
it_behaves_like 'not_adding_item_to_books'
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'with repeat isin/id and non-zero price' do
|
71
|
+
let(:new_item) { @repeat_item }
|
72
|
+
|
73
|
+
it_behaves_like 'adding_item_to_books'
|
74
|
+
|
75
|
+
it 'changes price of item in list' do
|
76
|
+
subject.add new_item
|
77
|
+
subject[item_index].price.should_not == @item.price
|
78
|
+
subject[item_index].price.should == @repeat_item.price
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'removes old item from appropriate order book' do
|
82
|
+
subject.add new_item
|
83
|
+
order_book = subject.books[@item.isin_id]
|
84
|
+
order_book.should_not have_key @item.price
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'with repeat isin/id and zero price' do
|
89
|
+
let(:new_item) { @repeat_zero_price_item }
|
90
|
+
|
91
|
+
it_behaves_like 'not_adding_item_to_books'
|
92
|
+
|
93
|
+
it 'removes old item from appropriate order book' do
|
94
|
+
subject.add new_item
|
95
|
+
order_book = subject.books[@item.isin_id]
|
96
|
+
order_book.should_not have_key @item.price
|
97
|
+
order_book.size.should == 0
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe 'removing item' do
|
104
|
+
before(:each) do
|
105
|
+
subject.add(@item).size.should == 1
|
106
|
+
end
|
107
|
+
|
108
|
+
context 'with existing item' do
|
109
|
+
let(:unwanted_item) { @item }
|
110
|
+
let(:expected_size) { 0 }
|
111
|
+
|
112
|
+
it 'deletes item from the list' do
|
113
|
+
subject.remove(unwanted_item)
|
114
|
+
subject[unwanted_item.id].should == nil
|
115
|
+
subject.size.should == expected_size
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'removes item from its related order book' do
|
119
|
+
subject.remove(unwanted_item)
|
120
|
+
subject.books[unwanted_item.isin_id].should_not have_key unwanted_item.price
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context 'with non-existing item' do
|
125
|
+
let(:unwanted_item) { @item1 }
|
126
|
+
let(:expected_size) { 1 }
|
127
|
+
|
128
|
+
it 'still returns the list itself' do
|
129
|
+
subject.remove(unwanted_item).should == subject
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'deletes nothing from the list' do
|
133
|
+
subject.remove(unwanted_item)
|
134
|
+
subject[item_index].should == @item
|
135
|
+
subject.size.should == expected_size
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
@@ -0,0 +1,355 @@
|
|
1
|
+
shared_examples_for 'removing items' do
|
2
|
+
context 'removing existing item' do
|
3
|
+
it 'returns to indicate success' do
|
4
|
+
remove_operation(@item).should == returns_if_remove_success #@item
|
5
|
+
end
|
6
|
+
|
7
|
+
it 'removes item from indexed list' do
|
8
|
+
remove_operation(@item)
|
9
|
+
subject.values.should_not include @item
|
10
|
+
subject.should_not have_key item_index
|
11
|
+
subject[item_index].should == nil
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'does not remove other items' do
|
15
|
+
remove_operation(@item)
|
16
|
+
subject.values.should include @item1
|
17
|
+
subject.size.should == 1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'deleting non-included items' do
|
22
|
+
it 'returns to indicate failure' do
|
23
|
+
remove_operation(@item2).should == returns_if_remove_failed #nil
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'removes nothing from the list' do
|
27
|
+
remove_operation(@item2)
|
28
|
+
subject.values.should include @item, @item1
|
29
|
+
subject.size.should == 2
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'does not raise if given nonsense instead of proper item to delete' do
|
33
|
+
[nil, 0, 123, 'nonsense', [1, 2, 3], {:this => 'sucks'}].each do |non_item|
|
34
|
+
remove_operation(non_item)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
shared_examples_for 'adding items' do
|
41
|
+
it 'runs check on item' do
|
42
|
+
subject.should_receive(:check).with(@item)
|
43
|
+
add_operation(@item)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'adds item only if it passes check' do
|
47
|
+
subject.should_receive(:check).with(@item).and_return(false)
|
48
|
+
add_operation(@item).should == returns_if_add_failed
|
49
|
+
subject.values.should_not include @item
|
50
|
+
subject.should_not have_key item_index
|
51
|
+
subject.size.should == 0
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'returns either self or item in case of success' do
|
55
|
+
add_operation(@item).should == returns_if_add_success
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'adds items to indexed list' do
|
59
|
+
add_operation(@item)
|
60
|
+
subject.values.should include @item
|
61
|
+
subject.should have_key item_index
|
62
|
+
subject[item_index].should == @item
|
63
|
+
subject.size.should == 1
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'does not add same items twice' do
|
67
|
+
subject.add(@item)
|
68
|
+
add_operation(@item)
|
69
|
+
subject.values.should include @item
|
70
|
+
subject.should have_key item_index
|
71
|
+
subject[item_index].should == @item
|
72
|
+
subject.size.should == 1
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'even adds item using << as alias to #add' do
|
76
|
+
subject << @item
|
77
|
+
subject.values.should include @item
|
78
|
+
subject.should have_key item_index
|
79
|
+
subject[item_index].should == @item
|
80
|
+
subject.size.should == 1
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
shared_examples_for 'changed_list' do
|
86
|
+
it_behaves_like 'index_list'
|
87
|
+
|
88
|
+
it 'has changed and updated attributes, true at creation' do
|
89
|
+
subject.changed.should == true
|
90
|
+
subject.updated.should == true
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'has change_count attribute, 0 at creation' do
|
94
|
+
subject.change_count.should == 0
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'allows setting of updated attribute externally' do
|
98
|
+
subject.updated = false
|
99
|
+
subject.updated.should == false
|
100
|
+
subject.updated = true
|
101
|
+
subject.updated.should == true
|
102
|
+
end
|
103
|
+
|
104
|
+
context 'when item is being added to list' do
|
105
|
+
before(:each) { subject.changed = false; subject.updated = false }
|
106
|
+
|
107
|
+
context 'successfully' do
|
108
|
+
it 'auto-sets changed attribute, but not updated attribute' do
|
109
|
+
subject.add @item
|
110
|
+
subject.changed.should == true
|
111
|
+
subject.updated.should == false
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'increases changed_count' do
|
115
|
+
subject.add @item
|
116
|
+
subject.change_count.should == 1
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
unless described_class == Fin::ChangedList # it can add ANYTHING...
|
121
|
+
context 'unsuccessfully' do
|
122
|
+
[nil, "none", 1313, [1, 2, 3]].each do |non_item|
|
123
|
+
it 'doesn`t set changed or updated attributes' do
|
124
|
+
subject.add non_item
|
125
|
+
subject.changed.should == false
|
126
|
+
subject.updated.should == false
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'doesn`t increase changed_count' do
|
130
|
+
subject.add non_item
|
131
|
+
subject.change_count.should == 0
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context 'when item is being removed from list' do
|
139
|
+
before(:each) do
|
140
|
+
subject.add(@item)
|
141
|
+
subject.changed = false
|
142
|
+
subject.updated = false
|
143
|
+
end
|
144
|
+
|
145
|
+
context 'successfully' do
|
146
|
+
it 'auto-sets changed attribute, but not updated attribute' do
|
147
|
+
subject.remove @item
|
148
|
+
subject.changed.should == true
|
149
|
+
subject.updated.should == false
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'increases changed_count' do
|
153
|
+
subject.remove @item
|
154
|
+
subject.change_count.should == 2 # added, then removed
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
context 'unsuccessfully' do
|
159
|
+
[nil, "none", 1313, [1, 2, 3], @item1].each do |non_item|
|
160
|
+
it ' doesn`t set changed or updated attributes' do
|
161
|
+
subject.remove non_item
|
162
|
+
subject.changed.should == false
|
163
|
+
subject.updated.should == false
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'doesn`t increase changed_count' do
|
167
|
+
subject.remove non_item
|
168
|
+
subject.change_count.should == 1 # initial addition, but not removal
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
shared_examples_for 'index_list' do
|
177
|
+
|
178
|
+
describe 'items get/set' do
|
179
|
+
it 'returns item from list by their index' do
|
180
|
+
p subject.add(@item)
|
181
|
+
p item_index
|
182
|
+
subject[item_index].should == @item
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'returns nil when item with requested index not in collection' do
|
186
|
+
subject[item_index].should == nil
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'is not possible to add item into collection directly' do
|
190
|
+
expect { subject[item_index] = @item }.to raise_error NoMethodError
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
describe '#index' do
|
195
|
+
it 'indexes arbitrary items by their index (redefined in subclasses)' do
|
196
|
+
subject.index(@item).should == item_index
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
describe '#add?' do
|
201
|
+
def add_operation item
|
202
|
+
subject.add? item
|
203
|
+
end
|
204
|
+
|
205
|
+
let(:returns_if_add_success) { @item }
|
206
|
+
let(:returns_if_add_failed) { nil }
|
207
|
+
it_behaves_like 'adding items'
|
208
|
+
end
|
209
|
+
|
210
|
+
describe '#add' do
|
211
|
+
def add_operation item
|
212
|
+
subject.add item
|
213
|
+
end
|
214
|
+
|
215
|
+
let(:returns_if_add_success) { subject }
|
216
|
+
let(:returns_if_add_failed) { subject }
|
217
|
+
it_behaves_like 'adding items'
|
218
|
+
end
|
219
|
+
|
220
|
+
context 'with couple of items in the list' do
|
221
|
+
before(:each) { subject.add(@item).add(@item1).size.should == 2 }
|
222
|
+
|
223
|
+
describe '#remove?' do
|
224
|
+
def remove_operation item
|
225
|
+
subject.remove? item
|
226
|
+
end
|
227
|
+
|
228
|
+
let(:returns_if_remove_success) { @item }
|
229
|
+
let(:returns_if_remove_failed) { nil }
|
230
|
+
it_behaves_like 'removing items'
|
231
|
+
end
|
232
|
+
|
233
|
+
describe '#remove' do
|
234
|
+
def remove_operation item
|
235
|
+
subject.remove item
|
236
|
+
end
|
237
|
+
|
238
|
+
let(:returns_if_remove_success) { subject }
|
239
|
+
let(:returns_if_remove_failed) { subject }
|
240
|
+
it_behaves_like 'removing items'
|
241
|
+
end
|
242
|
+
|
243
|
+
describe '#clear' do
|
244
|
+
context 'without block' do
|
245
|
+
it 'removes all items from list' do
|
246
|
+
subject.clear
|
247
|
+
subject.should be_empty
|
248
|
+
end
|
249
|
+
|
250
|
+
it 'ensures that #remove method is called once for each item in list' do
|
251
|
+
subject.should_receive(:remove).twice
|
252
|
+
subject.clear
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
context 'with block given' do
|
257
|
+
it 'yields items from list in index order' do
|
258
|
+
@count = 0
|
259
|
+
@items = []
|
260
|
+
subject.clear do |item|
|
261
|
+
@count += 1
|
262
|
+
@items << item
|
263
|
+
end
|
264
|
+
@count.should == 2
|
265
|
+
if subject.index(@item) < subject.index(@item1)
|
266
|
+
@items.should == [@item, @item1]
|
267
|
+
else
|
268
|
+
@items.should == [@item1, @item]
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
it 'only removes items for which given block returns true' do
|
273
|
+
subject.should_receive(:remove).once
|
274
|
+
subject.clear { |item| true if item == @item1 }
|
275
|
+
subject.should have_key item_index
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
describe '#each' do
|
281
|
+
it 'yields items from list in index order' do
|
282
|
+
@count = 0
|
283
|
+
@items = []
|
284
|
+
subject.each do |item|
|
285
|
+
@count += 1
|
286
|
+
@items << item
|
287
|
+
end
|
288
|
+
@count.should == 2
|
289
|
+
if subject.index(@item) < subject.index(@item1)
|
290
|
+
@items.should == [@item, @item1]
|
291
|
+
else
|
292
|
+
@items.should == [@item1, @item]
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
it 'returns list item in array, sorted by their index, if no block given' do
|
297
|
+
if subject.index(@item) < subject.index(@item1)
|
298
|
+
subject.each.should == [@item, @item1]
|
299
|
+
else
|
300
|
+
subject.each.should == [@item1, @item]
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
shared_examples_for 'adding_item_to_books' do
|
308
|
+
it_behaves_like 'creating_books'
|
309
|
+
|
310
|
+
it 'adds this item to appropriate book' do
|
311
|
+
subject.add new_item
|
312
|
+
book = subject.books[new_item.isin_id]
|
313
|
+
book.should have_key new_item_book_index
|
314
|
+
book[new_item_book_index].should == new_item
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
shared_examples_for 'not_adding_item_to_books' do
|
319
|
+
it_behaves_like 'creating_books'
|
320
|
+
|
321
|
+
it 'does not add this item to list' do
|
322
|
+
subject.add new_item
|
323
|
+
book = subject.books[new_item.isin_id]
|
324
|
+
book.should_not have_key new_item_book_index
|
325
|
+
book[new_item_book_index].should == nil
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
shared_examples_for 'creating_books' do
|
330
|
+
it 'creates appropriate order book' do
|
331
|
+
subject.add(new_item)
|
332
|
+
subject.books.should have(expected_number_of_books).books
|
333
|
+
book = subject.books[new_item.isin_id]
|
334
|
+
book.should be_an Fin::Book
|
335
|
+
book.isin_id.should == new_item.isin_id
|
336
|
+
end
|
337
|
+
|
338
|
+
it 'sets item`s book property correctly' do
|
339
|
+
subject.add(new_item)
|
340
|
+
book = subject.books[new_item.isin_id]
|
341
|
+
if new_item_book_index == 0
|
342
|
+
case subject
|
343
|
+
when Fin::OrderList
|
344
|
+
new_item.book.should == nil
|
345
|
+
else
|
346
|
+
new_item.book.should == book
|
347
|
+
end
|
348
|
+
else
|
349
|
+
new_item.book.should == book
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
|
355
|
+
|