rmarket 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Guardfile +9 -0
- data/README +24 -0
- data/Rakefile +1 -0
- data/lib/rmarket/order.rb +10 -0
- data/lib/rmarket/order_book.rb +50 -0
- data/lib/rmarket/order_ledger.rb +89 -0
- data/lib/rmarket/trader.rb +21 -0
- data/lib/rmarket/version.rb +3 -0
- data/lib/rmarket.rb +3 -0
- data/rmarket.gemspec +27 -0
- data/spec/rmarket/order_book_spec.rb +244 -0
- data/spec/rmarket/order_ledger_spec.rb +25 -0
- data/spec/rmarket/trader_spec.rb +11 -0
- data/spec/spec_helper.rb +1 -0
- metadata +110 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'rspec', :version => 2, :cli => '--color --format doc' do
|
5
|
+
watch(%r{^spec/.+_spec\.rb$})
|
6
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
7
|
+
watch('spec/spec_helper.rb') { "spec" }
|
8
|
+
end
|
9
|
+
|
data/README
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
RMarket is a ruby gem for making stock market simulations.
|
2
|
+
|
3
|
+
== Included Classes
|
4
|
+
|
5
|
+
* Order - container for a limit order; stores price, quantity, symbol, and trader
|
6
|
+
* OrderLedger - one side of a CDA order book; enforces transactions to occur at the current bid/ask price depending on its assigned type
|
7
|
+
* OrderBook - a model of a CDA order book; manages a buy and sell ledger and enforces orders to match a specific label
|
8
|
+
* Trader - tracks a portfolio or stocks and cash; passes off the real work to the user defined objects: TradingStrategy, ValueFunction, and Beliefs
|
9
|
+
|
10
|
+
== Contributing
|
11
|
+
|
12
|
+
If you are interested in making a contribution, first propose the change or added feature on the issues page. Once this project is a little more hammered down, we'll move to the fork-pull request model.
|
13
|
+
|
14
|
+
== Copyright
|
15
|
+
|
16
|
+
(The MIT License)
|
17
|
+
|
18
|
+
Copyright (c) 2011 {bcassell (Ben-Alexander Cassell)}[http://github.com/bcassell] {egtaonline (University of Michigan - Strategic Reasoning Group)}[http://github.com/egtaonline]
|
19
|
+
|
20
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
21
|
+
|
22
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
23
|
+
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module RMarket
|
2
|
+
class Order
|
3
|
+
attr_reader :type, :asset_label, :price, :trader
|
4
|
+
attr_accessor :quantity
|
5
|
+
|
6
|
+
def initialize(type, price, trader=nil, quantity=1, asset_label="")
|
7
|
+
@type, @price, @trader, @quantity, @asset_label = type, price, trader, quantity, asset_label
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module RMarket
|
2
|
+
class OrderBook
|
3
|
+
attr_reader :asset_label
|
4
|
+
|
5
|
+
def initialize(asset_label="")
|
6
|
+
@buy_ledger = OrderLedger.new("buy", asset_label)
|
7
|
+
@sell_ledger = OrderLedger.new("sell", asset_label)
|
8
|
+
@asset_label = asset_label
|
9
|
+
end
|
10
|
+
|
11
|
+
def matches?(order)
|
12
|
+
return false if order.asset_label != @asset_label
|
13
|
+
if order.type == "buy"
|
14
|
+
return false if @sell_ledger.outstanding_order_count == 0 || ask > order.price
|
15
|
+
else
|
16
|
+
return false if @buy_ledger.outstanding_order_count == 0 || bid < order.price
|
17
|
+
end
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def submit_order(order)
|
22
|
+
raise "Asset label of order does not match OrderBook" if order.asset_label != @asset_label
|
23
|
+
if order.type == "buy"
|
24
|
+
order = @sell_ledger.transact(order) if matches?(order)
|
25
|
+
@buy_ledger.submit_order(order) if order != nil
|
26
|
+
else
|
27
|
+
order = @buy_ledger.transact(order) if matches?(order)
|
28
|
+
@sell_ledger.submit_order(order) if order != nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def outstanding_bid_count; @buy_ledger.outstanding_order_count; end
|
33
|
+
def outstanding_ask_count; @sell_ledger.outstanding_order_count; end
|
34
|
+
|
35
|
+
def bid; @buy_ledger.bid; end
|
36
|
+
def ask; @sell_ledger.ask; end
|
37
|
+
|
38
|
+
def bid_quantity; @buy_ledger.bid_quantity; end
|
39
|
+
def ask_quantity; @sell_ledger.ask_quantity; end
|
40
|
+
|
41
|
+
def remove_prior_orders(trader)
|
42
|
+
@buy_ledger.remove_prior_orders(trader)
|
43
|
+
@sell_ledger.remove_prior_orders(trader)
|
44
|
+
end
|
45
|
+
|
46
|
+
def snapshot
|
47
|
+
{"asset_label" => @asset_label, "bid" => bid, "bid_quantity" => bid_quantity, "ask" => ask, "ask_quantity" => ask_quantity}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module RMarket
|
2
|
+
class OrderLedger
|
3
|
+
|
4
|
+
def initialize(type=nil, asset_label="")
|
5
|
+
@orders = []
|
6
|
+
@type = type
|
7
|
+
@asset_label = asset_label
|
8
|
+
end
|
9
|
+
|
10
|
+
def transact(order)
|
11
|
+
while order.quantity > 0 && price != 0 && price != 1/0.0
|
12
|
+
if @type == "buy" && order.type == "sell"
|
13
|
+
if bid > order.price
|
14
|
+
if @orders.last.quantity > order.quantity
|
15
|
+
@orders.last.trader.update_account(-bid*order.quantity, order.quantity, @asset_label)
|
16
|
+
order.trader.update_account(bid*order.quantity, -order.quantity, @asset_label)
|
17
|
+
@orders.last.quantity -= order.quantity
|
18
|
+
return nil
|
19
|
+
else
|
20
|
+
@orders.last.trader.update_account(-bid*@orders.last.quantity, @orders.last.quantity, @asset_label)
|
21
|
+
order.trader.update_account(bid*@orders.last.quantity, -@orders.last.quantity, @asset_label)
|
22
|
+
order.quantity -= @orders.last.quantity
|
23
|
+
@orders.pop
|
24
|
+
return nil if order.quantity == 0
|
25
|
+
end
|
26
|
+
else
|
27
|
+
return order
|
28
|
+
end
|
29
|
+
elsif @type == "sell" && order.type == "buy"
|
30
|
+
if ask < order.price
|
31
|
+
if @orders.first.quantity > order.quantity
|
32
|
+
@orders.first.trader.update_account(ask*order.quantity, -order.quantity, @asset_label)
|
33
|
+
order.trader.update_account(-ask*order.quantity, order.quantity, @asset_label)
|
34
|
+
@orders.first.quantity -= order.quantity
|
35
|
+
return nil
|
36
|
+
else
|
37
|
+
@orders.first.trader.update_account(ask*@orders.first.quantity, -@orders.first.quantity, @asset_label)
|
38
|
+
order.trader.update_account(-ask*@orders.first.quantity, @orders.first.quantity, @asset_label)
|
39
|
+
order.quantity -= @orders.first.quantity
|
40
|
+
@orders.delete_at(0)
|
41
|
+
return nil if order.quantity == 0
|
42
|
+
end
|
43
|
+
else
|
44
|
+
return order
|
45
|
+
end
|
46
|
+
else
|
47
|
+
raise "Orders of the same type cannot transact"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
return order
|
51
|
+
end
|
52
|
+
|
53
|
+
def submit_order(order)
|
54
|
+
@type ||= order.type
|
55
|
+
index = @orders.index{|i| i.price > order.price}
|
56
|
+
index == nil ? @orders << order : @orders.insert(index, order)
|
57
|
+
end
|
58
|
+
|
59
|
+
def outstanding_order_count; @orders.size; end
|
60
|
+
|
61
|
+
def bid
|
62
|
+
raise "OrderLedger is of type \"sell\" and cannot supply a \"bid\"" if @type == "sell"
|
63
|
+
@orders == [] ? 0 : @orders.last.price
|
64
|
+
end
|
65
|
+
|
66
|
+
def bid_quantity
|
67
|
+
raise "OrderLedger is of type \"sell\" and cannot supply a \"bid\"" if @type == "sell"
|
68
|
+
@orders == [] ? 0 : @orders.last.quantity
|
69
|
+
end
|
70
|
+
|
71
|
+
def ask
|
72
|
+
raise "OrderLedger is of type \"buy\" and cannot supply an \"ask\"" if @type == "buy"
|
73
|
+
@orders == [] ? 1/0.0 : @orders.first.price
|
74
|
+
end
|
75
|
+
|
76
|
+
def ask_quantity
|
77
|
+
raise "OrderLedger is of type \"buy\" and cannot supply an \"ask\"" if @type == "buy"
|
78
|
+
@orders == [] ? 0 : @orders.first.quantity
|
79
|
+
end
|
80
|
+
|
81
|
+
def remove_prior_orders(trader); @orders.reject!{|x| x.trader == trader}; end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def price
|
86
|
+
@type == "buy" ? bid : ask
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module RMarket
|
2
|
+
class Trader
|
3
|
+
attr_reader :cash, :shares, :value_function, :beliefs, :id
|
4
|
+
attr_accessor :trading_strategy
|
5
|
+
|
6
|
+
def initialize(cash=0, shares={}, value_function=nil, trading_strategy=nil, beliefs=nil, id=nil)
|
7
|
+
@cash, @shares, @trading_strategy, @value_function, @beliefs, @id = cash, shares, trading_strategy, value_function, beliefs, id
|
8
|
+
end
|
9
|
+
|
10
|
+
def update_account(cash, shares, asset_label)
|
11
|
+
@shares[asset_label] = @shares[asset_label].to_f + shares
|
12
|
+
@cash += cash
|
13
|
+
end
|
14
|
+
|
15
|
+
def update_beliefs(news); @beliefs.update(news); end
|
16
|
+
|
17
|
+
def submit_orders(order_book)
|
18
|
+
@trading_strategy.construct_orders(@cash, @shares, @value_function, @beliefs, order_book.snapshot).each {|order| order_book.submit_order(order)}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/rmarket.rb
ADDED
data/rmarket.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "rmarket/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "rmarket"
|
7
|
+
s.version = RMarket::VERSION
|
8
|
+
s.authors = ["Ben-Alexander Cassell"]
|
9
|
+
s.email = ["bcassell@umich.edu"]
|
10
|
+
s.homepage = "http://www.github.com/egtaonline/rmarket"
|
11
|
+
s.summary = %q{Classes for modeling market interactions in ruby}
|
12
|
+
s.description = %q{rmarket provides classes for modeling market interactions in ruby, such as an order book that operates as a CDA.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "rmarket"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
s.add_development_dependency "rspec"
|
23
|
+
s.add_development_dependency "growl"
|
24
|
+
s.add_development_dependency "guard-rspec"
|
25
|
+
s.add_development_dependency "rb-fsevent"
|
26
|
+
# s.add_runtime_dependency "rest-client"
|
27
|
+
end
|
@@ -0,0 +1,244 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module RMarket
|
4
|
+
describe OrderBook do
|
5
|
+
describe "#matches?" do
|
6
|
+
before(:each) { @order_book = OrderBook.new }
|
7
|
+
it "should never match if empty" do
|
8
|
+
for i in 1..10 do
|
9
|
+
@order_book.matches?(Order.new("buy", i)).should == false
|
10
|
+
@order_book.matches?(Order.new("sell", i)).should == false
|
11
|
+
end
|
12
|
+
end
|
13
|
+
it "should match if a buy order has a price higher than ask" do
|
14
|
+
@order_book.submit_order(Order.new("sell", 1.5))
|
15
|
+
@order_book.matches?(Order.new("buy", 1.7)).should == true
|
16
|
+
end
|
17
|
+
it "should match if a sell order has a price lower than bid" do
|
18
|
+
@order_book.submit_order(Order.new("buy", 1.5))
|
19
|
+
@order_book.matches?(Order.new("sell", 1.3)).should == true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "#submit_order" do
|
24
|
+
context "empty order book" do
|
25
|
+
before(:each) { @order_book = OrderBook.new }
|
26
|
+
it "should raise error if order doesn't match asset type" do
|
27
|
+
lambda{ @order_book.submit_order(Order.new("buy", 1.5, nil, 1, "fasd"))}.should raise_error("Asset label of order does not match OrderBook")
|
28
|
+
end
|
29
|
+
|
30
|
+
context "incoming buy order" do
|
31
|
+
before(:each) { @order_book.submit_order(Order.new("buy", 1.5)) }
|
32
|
+
it "should have 1 buy order" do
|
33
|
+
@order_book.outstanding_bid_count.should == 1
|
34
|
+
end
|
35
|
+
it "should have a bid equal to the price of that order" do
|
36
|
+
@order_book.bid.should == 1.5
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "incoming sell order" do
|
41
|
+
before(:each) { @order_book.submit_order(Order.new("sell", 1.5)) }
|
42
|
+
it "should have 1 sell order" do
|
43
|
+
@order_book.outstanding_ask_count.should == 1
|
44
|
+
end
|
45
|
+
it "should have an ask equal to the price of that order" do
|
46
|
+
@order_book.ask.should == 1.5
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "order book has existing buy order from trader A" do
|
51
|
+
before(:each) do
|
52
|
+
@order_book = OrderBook.new
|
53
|
+
@traderA = double('trader')
|
54
|
+
@traderB = double('trader')
|
55
|
+
@order_book.submit_order(Order.new("buy", 1.5, @traderA))
|
56
|
+
end
|
57
|
+
|
58
|
+
context "incoming buy order is higher" do
|
59
|
+
before(:each) { @order_book.submit_order(Order.new("buy", 1.7, @traderB)) }
|
60
|
+
it "should have 2 buy orders" do
|
61
|
+
@order_book.outstanding_bid_count.should == 2
|
62
|
+
end
|
63
|
+
it "should have a bid equal to the price of that order" do
|
64
|
+
@order_book.bid.should == 1.7
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "incoming buy order is lower" do
|
69
|
+
before(:each) { @order_book.submit_order(Order.new("buy", 1.3, @traderB)) }
|
70
|
+
it "should have 2 buy orders" do
|
71
|
+
@order_book.outstanding_bid_count.should == 2
|
72
|
+
end
|
73
|
+
it "should have a bid equal to the price of the prior order" do
|
74
|
+
@order_book.bid.should == 1.5
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context "incoming sell order is higher" do
|
79
|
+
before(:each) { @order_book.submit_order(Order.new("sell", 1.7, @traderB)) }
|
80
|
+
it "should have 1 buy order" do
|
81
|
+
@order_book.outstanding_bid_count.should == 1
|
82
|
+
end
|
83
|
+
it "should have 1 sell order" do
|
84
|
+
@order_book.outstanding_ask_count.should == 1
|
85
|
+
end
|
86
|
+
it "should have a bid equal to the price of the prior order" do
|
87
|
+
@order_book.bid.should == 1.5
|
88
|
+
end
|
89
|
+
it "should have an ask equal to the price of that order" do
|
90
|
+
@order_book.ask.should == 1.7
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context "incoming sell order is lower" do
|
95
|
+
before(:each) do
|
96
|
+
@traderA.should_receive("update_account").with(-1.5, 1, "")
|
97
|
+
@traderB.should_receive("update_account").with(1.5, -1, "")
|
98
|
+
@order_book.submit_order(Order.new("sell", 1.3, @traderB))
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should have 0 buy orders" do
|
102
|
+
@order_book.outstanding_bid_count.should == 0
|
103
|
+
end
|
104
|
+
it "should have 0 sell orders" do
|
105
|
+
@order_book.outstanding_ask_count.should == 0
|
106
|
+
end
|
107
|
+
it "should have a bid equal to that of an empty order book" do
|
108
|
+
@order_book.bid.should == 0
|
109
|
+
end
|
110
|
+
it "should have an ask equal to that of an empty order book" do
|
111
|
+
@order_book.ask.should == 1/0.0
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
context "order book has existing sell order from trader A" do
|
118
|
+
before(:each) do
|
119
|
+
@order_book = OrderBook.new
|
120
|
+
@traderA = double('trader')
|
121
|
+
@traderB = double('trader')
|
122
|
+
@order_book.submit_order(Order.new("sell", 1.5, @traderA))
|
123
|
+
end
|
124
|
+
|
125
|
+
context "incoming buy order is higher" do
|
126
|
+
before(:each) do
|
127
|
+
@traderA.should_receive("update_account").with(1.5, -1, "")
|
128
|
+
@traderB.should_receive("update_account").with(-1.5, 1, "")
|
129
|
+
@order_book.submit_order(Order.new("buy", 1.7, @traderB))
|
130
|
+
end
|
131
|
+
it "should have 0 buy orders" do
|
132
|
+
@order_book.outstanding_bid_count.should == 0
|
133
|
+
end
|
134
|
+
it "should have 0 sell orders" do
|
135
|
+
@order_book.outstanding_ask_count.should == 0
|
136
|
+
end
|
137
|
+
it "should have a bid equal to that of an empty order book" do
|
138
|
+
@order_book.bid.should == 0
|
139
|
+
end
|
140
|
+
it "should have an ask equal to that of an empty order book" do
|
141
|
+
@order_book.ask.should == 1/0.0
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context "incoming buy order is lower" do
|
146
|
+
before(:each) { @order_book.submit_order(Order.new("buy", 1.3, @traderB)) }
|
147
|
+
it "should have 1 buy order" do
|
148
|
+
@order_book.outstanding_bid_count.should == 1
|
149
|
+
end
|
150
|
+
it "should have 1 sell order" do
|
151
|
+
@order_book.outstanding_bid_count.should == 1
|
152
|
+
end
|
153
|
+
it "should have a bid equal to the price of the new order" do
|
154
|
+
@order_book.bid.should == 1.3
|
155
|
+
end
|
156
|
+
it "should have a ask equal to the price of the prior order" do
|
157
|
+
@order_book.ask.should == 1.5
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
context "incoming sell order is higher" do
|
162
|
+
before(:each) { @order_book.submit_order(Order.new("sell", 1.7, @traderB)) }
|
163
|
+
it "should have 0 buy orders" do
|
164
|
+
@order_book.outstanding_bid_count.should == 0
|
165
|
+
end
|
166
|
+
it "should have 2 sell orders" do
|
167
|
+
@order_book.outstanding_ask_count.should == 2
|
168
|
+
end
|
169
|
+
it "should have a ask equal to the price of the prior order" do
|
170
|
+
@order_book.ask.should == 1.5
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
context "incoming sell order is lower" do
|
175
|
+
before(:each) do
|
176
|
+
@order_book.submit_order(Order.new("sell", 1.3, @traderB))
|
177
|
+
end
|
178
|
+
|
179
|
+
it "should have 0 buy orders" do
|
180
|
+
@order_book.outstanding_bid_count.should == 0
|
181
|
+
end
|
182
|
+
it "should have 2 sell orders" do
|
183
|
+
@order_book.outstanding_ask_count.should == 2
|
184
|
+
end
|
185
|
+
it "should have a bid equal to that of an empty order book" do
|
186
|
+
@order_book.bid.should == 0
|
187
|
+
end
|
188
|
+
it "should have an ask equal to the price of the new order" do
|
189
|
+
@order_book.ask.should == 1.3
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
context "order book has existing buy order from trader A for multiple units" do
|
194
|
+
before(:each) do
|
195
|
+
@order_book = OrderBook.new("IBM")
|
196
|
+
@traderA = double('trader')
|
197
|
+
@traderB = double('trader')
|
198
|
+
@order_book.submit_order(Order.new("buy", 1.5, @traderA, 2, "IBM"))
|
199
|
+
end
|
200
|
+
|
201
|
+
context "incoming matching sell order for 1" do
|
202
|
+
before(:each) do
|
203
|
+
@traderA.should_receive("update_account").with(-1.5, 1, "IBM")
|
204
|
+
@traderB.should_receive("update_account").with(1.5, -1, "IBM")
|
205
|
+
@order_book.submit_order(Order.new("sell", 1.4, @traderB, 1, "IBM"))
|
206
|
+
end
|
207
|
+
it "should have 1 buy order" do
|
208
|
+
@order_book.outstanding_bid_count.should == 1
|
209
|
+
end
|
210
|
+
it "should have 1 unit in that buy order" do
|
211
|
+
@order_book.bid_quantity.should == 1
|
212
|
+
end
|
213
|
+
it "should have bid equal to the price of the prior order" do
|
214
|
+
@order_book.bid.should == 1.5
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
context "incoming sell order requests 3 units" do
|
219
|
+
before(:each) do
|
220
|
+
@traderA.should_receive("update_account").with(-3.0, 2, "IBM")
|
221
|
+
@traderB.should_receive("update_account").with(3.0, -2, "IBM")
|
222
|
+
@order_book.submit_order(Order.new("sell", 1.4, @traderB, 3, "IBM"))
|
223
|
+
end
|
224
|
+
it "should have 0 buy orders" do
|
225
|
+
@order_book.bid_quantity.should == 0
|
226
|
+
end
|
227
|
+
it "should have 1 sell order" do
|
228
|
+
@order_book.outstanding_ask_count.should == 1
|
229
|
+
end
|
230
|
+
it "should have 1 unit in that sell order" do
|
231
|
+
@order_book.ask_quantity.should == 1
|
232
|
+
end
|
233
|
+
it "should have bid price of zero" do
|
234
|
+
@order_book.bid.should == 0
|
235
|
+
end
|
236
|
+
it "should have ask equal to the price of the new order" do
|
237
|
+
@order_book.ask.should == 1.4
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module RMarket
|
4
|
+
describe OrderLedger do
|
5
|
+
describe "#submit_order" do
|
6
|
+
context "empty order ledger" do
|
7
|
+
before(:each) { @order_ledger = OrderLedger.new }
|
8
|
+
|
9
|
+
context "incoming buy order" do
|
10
|
+
before(:each) { @order_ledger.submit_order(Order.new("buy", 1.5)) }
|
11
|
+
|
12
|
+
it "should have 1 order" do
|
13
|
+
@order_ledger.outstanding_order_count.should == 1
|
14
|
+
end
|
15
|
+
it "should have a bid equal to the price of that order" do
|
16
|
+
@order_ledger.bid.should == 1.5
|
17
|
+
end
|
18
|
+
it "should implicitly become a buy ledger" do
|
19
|
+
lambda{ @order_ledger.ask }.should raise_error("OrderLedger is of type \"buy\" and cannot supply an \"ask\"")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module RMarket
|
4
|
+
describe Trader do
|
5
|
+
describe "#update_account" do
|
6
|
+
before(:each) { @trader = Trader.new; @trader.update_account(200, -1, "IBM") }
|
7
|
+
it { @trader.cash.should == 200 }
|
8
|
+
it { @trader.shares.should == {"IBM" => -1} }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'rmarket'
|
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rmarket
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ben-Alexander Cassell
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-11-01 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &70175638857640 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70175638857640
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: growl
|
27
|
+
requirement: &70175638857140 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70175638857140
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: guard-rspec
|
38
|
+
requirement: &70175638856680 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70175638856680
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rb-fsevent
|
49
|
+
requirement: &70175638856220 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70175638856220
|
58
|
+
description: rmarket provides classes for modeling market interactions in ruby, such
|
59
|
+
as an order book that operates as a CDA.
|
60
|
+
email:
|
61
|
+
- bcassell@umich.edu
|
62
|
+
executables: []
|
63
|
+
extensions: []
|
64
|
+
extra_rdoc_files: []
|
65
|
+
files:
|
66
|
+
- .gitignore
|
67
|
+
- Gemfile
|
68
|
+
- Guardfile
|
69
|
+
- README
|
70
|
+
- Rakefile
|
71
|
+
- lib/rmarket.rb
|
72
|
+
- lib/rmarket/order.rb
|
73
|
+
- lib/rmarket/order_book.rb
|
74
|
+
- lib/rmarket/order_ledger.rb
|
75
|
+
- lib/rmarket/trader.rb
|
76
|
+
- lib/rmarket/version.rb
|
77
|
+
- rmarket.gemspec
|
78
|
+
- spec/rmarket/order_book_spec.rb
|
79
|
+
- spec/rmarket/order_ledger_spec.rb
|
80
|
+
- spec/rmarket/trader_spec.rb
|
81
|
+
- spec/spec_helper.rb
|
82
|
+
homepage: http://www.github.com/egtaonline/rmarket
|
83
|
+
licenses: []
|
84
|
+
post_install_message:
|
85
|
+
rdoc_options: []
|
86
|
+
require_paths:
|
87
|
+
- lib
|
88
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ! '>='
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
requirements: []
|
101
|
+
rubyforge_project: rmarket
|
102
|
+
rubygems_version: 1.8.10
|
103
|
+
signing_key:
|
104
|
+
specification_version: 3
|
105
|
+
summary: Classes for modeling market interactions in ruby
|
106
|
+
test_files:
|
107
|
+
- spec/rmarket/order_book_spec.rb
|
108
|
+
- spec/rmarket/order_ledger_spec.rb
|
109
|
+
- spec/rmarket/trader_spec.rb
|
110
|
+
- spec/spec_helper.rb
|