ruby-trade 0.2 → 0.3
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 +83 -1
- data/examples/stress.rb +37 -0
- data/lib/client.rb +8 -9
- data/lib/order.rb +1 -1
- data/{rubytrade.gemspec → ruby-trade.gemspec} +1 -1
- data/server/account.rb +10 -1
- data/server/common.rb +22 -0
- data/server/exchange.rb +16 -3
- data/server/public/app.js +7 -0
- data/server/public/index.html +13 -5
- data/server/server.rb +35 -17
- data/server/test/test_common.rb +76 -0
- data/server/web_server.rb +16 -2
- metadata +5 -2
data/README.md
CHANGED
@@ -8,10 +8,77 @@ into the market to buy or sell shares.
|
|
8
8
|
|
9
9
|
## Installation
|
10
10
|
|
11
|
-
Just install the gem
|
11
|
+
Just install the gem:
|
12
12
|
|
13
13
|
gem install ruby-trade
|
14
14
|
|
15
|
+
Mac users: some people are having some issues installing the ZeroMQ libraries
|
16
|
+
on a Mac. If you install an older version of ZeroMQ it should work:
|
17
|
+
|
18
|
+
brew install zeromq22
|
19
|
+
gem install ruby-trade
|
20
|
+
|
21
|
+
## Mechanics
|
22
|
+
|
23
|
+
The mechanics of the market are for the most part like a real stock market. In
|
24
|
+
ruby-trade there is only one stock, and everybody buys and sells that stock from
|
25
|
+
other players within the market.
|
26
|
+
|
27
|
+
### Orders
|
28
|
+
|
29
|
+
The only way to buy or sell shares is through orders. The client script sends
|
30
|
+
a orders to the market to buy or sell a number of shares at a specified price.
|
31
|
+
When you send an order, the server checks to see if your order can be matched
|
32
|
+
with any of the other orders and if it can be, it will execute a trade and your
|
33
|
+
script will receive a notification.
|
34
|
+
|
35
|
+
The server is real-time, there is no time interval between when things happen.
|
36
|
+
If your script sends an order, it is sent to the market immediately.
|
37
|
+
|
38
|
+
### Matching Example
|
39
|
+
|
40
|
+
Here's an example of how the server will attempt to match a new order into the
|
41
|
+
market. Suppose here are the existing orders:
|
42
|
+
|
43
|
+
* Trader A has a buy order for 5k shares at $9.00
|
44
|
+
* Trader B has a sell order for 10k shares at $10.00
|
45
|
+
* Trader C has:
|
46
|
+
* a sell order for 2k shares at $10.00 but it was placed after trader B's order
|
47
|
+
* a sell order for 10k shares at $10.50
|
48
|
+
* a buy order for 10k shares at $8.00.
|
49
|
+
|
50
|
+
In this case the "best" buy order is trader A's order at $9 because it has the
|
51
|
+
highest price (picture yourself in the position of a seller, would you rather
|
52
|
+
sell your shares to someone at $9 or at $8?). This best price is called the "bid".
|
53
|
+
The best sell order on the other hand is trader B's sell order at $10, and this
|
54
|
+
is called the "ask".
|
55
|
+
|
56
|
+
Now Trader D comes along and sends a buy order for 15k shares at $12. Here's how
|
57
|
+
the server will match up the orders:
|
58
|
+
|
59
|
+
1. Trader D will buy 10k shares from trader B at $10.00 (it starts at the best
|
60
|
+
sell price).
|
61
|
+
2. Trader D will then buy 2k shares from trader C at $10.00 (it resolves ties at
|
62
|
+
a certain price level using a first-come-first-serve algorithm).
|
63
|
+
3. Trader D will finally buy 3k shares from trader C at $10.50. The first two
|
64
|
+
orders at $10.00 will be gone, and trader C's order at $10.50 will be updated
|
65
|
+
to only have 7k shares left.
|
66
|
+
|
67
|
+
When this is over, the "bid" will still be $9.00 from trader A's order, but the
|
68
|
+
"ask" will have gone up to $10.50 because all the orders at $10.00 are now gone.
|
69
|
+
The "last" price (the price that the last trade was at) would be $10.50.
|
70
|
+
|
71
|
+
Next, trader E sends a sell order for 10k shares at $8.50. The matching is like
|
72
|
+
this:
|
73
|
+
|
74
|
+
1. Trader E will sell 5k shares to trader A at $9.00 (the best buy price).
|
75
|
+
2. Since there are no more orders left that are greater than or equal to $8.50,
|
76
|
+
trader E's order will enter the market as a sell order for 5k shares at $8.50.
|
77
|
+
|
78
|
+
The "bid" gets updated to be $8.00 (for trader C's buy order) and the "ask" gets
|
79
|
+
updated to be $8.50 (trader E's new sell order). The "last" will be $9.00.
|
80
|
+
|
81
|
+
|
15
82
|
## Lingo
|
16
83
|
|
17
84
|
Before getting started, there are a few definitions that you should know about:
|
@@ -74,6 +141,21 @@ Here is an example client:
|
|
74
141
|
# Connect to the server
|
75
142
|
MyApp.connect_to "127.0.0.1", as: "Jim"
|
76
143
|
|
144
|
+
### Hooks
|
145
|
+
|
146
|
+
The following hooks are available:
|
147
|
+
|
148
|
+
* `on_connect` - Called when the client connects to the server.
|
149
|
+
* `on_tick level` - Called whenever something happens in the exchange. `level1`
|
150
|
+
is a hash containing `"bid"`, `"ask"`, and `"last"`.
|
151
|
+
* `on_fill order, amount, price` - Called when `order` is filled. `amount` is
|
152
|
+
the amount (usually the size of the order, but will be less if the order was
|
153
|
+
partially filled before), and `price` is the price that it was filled at.
|
154
|
+
* `on_partial_fill order, amount, price` - Same as `on_fill`, but this order is
|
155
|
+
still live in the market.
|
156
|
+
* `on_dividend amount` - Called when a dividend is received, `amount` is the
|
157
|
+
cash value of the dividend (which will be negative for short positions).
|
158
|
+
|
77
159
|
## Events
|
78
160
|
|
79
161
|
### Dividend
|
data/examples/stress.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'ruby-trade'
|
2
|
+
|
3
|
+
TradeAmount = 20_000
|
4
|
+
NumOrders = 2000
|
5
|
+
InitialPrice = 10.0
|
6
|
+
|
7
|
+
class Slammer
|
8
|
+
include RubyTrade::Client
|
9
|
+
|
10
|
+
def self.on_connect *args
|
11
|
+
puts "Connected."
|
12
|
+
|
13
|
+
hit_it
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.hit_it
|
17
|
+
@orders = (1..NumOrders).map do
|
18
|
+
buy 100, at: InitialPrice
|
19
|
+
end
|
20
|
+
|
21
|
+
EM.add_timer 1 do
|
22
|
+
@orders.each do |order|
|
23
|
+
order.cancel!
|
24
|
+
end
|
25
|
+
|
26
|
+
EM.add_timer 0.5 do
|
27
|
+
hit_it
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.on_tick level1
|
33
|
+
@level1 = level1
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
Slammer.connect_to "127.0.0.1", as: "Slammer", ai: true
|
data/lib/client.rb
CHANGED
@@ -3,12 +3,15 @@ require 'json'
|
|
3
3
|
require 'em-zeromq'
|
4
4
|
|
5
5
|
require_relative "order"
|
6
|
+
require_relative "../server/common"
|
6
7
|
|
7
8
|
DEFAULT_FEED_PORT = 9000
|
8
9
|
DEFAULT_ORDER_PORT = 9001
|
9
10
|
|
10
11
|
module RubyTrade
|
11
12
|
module ConnectionClient
|
13
|
+
include LineCleaner
|
14
|
+
|
12
15
|
def self.setup args, parent
|
13
16
|
@@username = args[:as]
|
14
17
|
@@ai = args[:ai] || false
|
@@ -25,6 +28,7 @@ module RubyTrade
|
|
25
28
|
|
26
29
|
send_data_f data
|
27
30
|
|
31
|
+
@buffer = ""
|
28
32
|
@order_no = 0
|
29
33
|
@orders = {}
|
30
34
|
@cash, @stock = 0, 0
|
@@ -83,15 +87,6 @@ module RubyTrade
|
|
83
87
|
send_data "\x02#{data}\x03"
|
84
88
|
end
|
85
89
|
|
86
|
-
# Strip off begin/end transmission tokens
|
87
|
-
def clean data
|
88
|
-
if data.length > 2
|
89
|
-
data[1..-2].split("\x03\x02")
|
90
|
-
else
|
91
|
-
[]
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
90
|
# Called by EM when we receive data
|
96
91
|
def receive_data data
|
97
92
|
clean(data).each do |msg|
|
@@ -132,6 +127,9 @@ module RubyTrade
|
|
132
127
|
@connect_triggered = true
|
133
128
|
@@parent.on_connect
|
134
129
|
end
|
130
|
+
when "dividend"
|
131
|
+
@cash += data["value"]
|
132
|
+
@@parent.on_dividend data["value"]
|
135
133
|
end
|
136
134
|
end
|
137
135
|
end
|
@@ -142,6 +140,7 @@ module RubyTrade
|
|
142
140
|
def on_tick *args; end
|
143
141
|
def on_fill *args; end
|
144
142
|
def on_partial_fill *args; end
|
143
|
+
def on_dividend *args; end
|
145
144
|
|
146
145
|
# hook so we can call child methods
|
147
146
|
def child= child
|
data/lib/order.rb
CHANGED
data/server/account.rb
CHANGED
@@ -11,11 +11,20 @@ class Account
|
|
11
11
|
@ai = false
|
12
12
|
end
|
13
13
|
|
14
|
+
def process_dividend amount
|
15
|
+
value = stock * amount
|
16
|
+
|
17
|
+
@cash += value
|
18
|
+
|
19
|
+
changed
|
20
|
+
notify_observers :dividend, {amount: amount, value: value}
|
21
|
+
end
|
22
|
+
|
14
23
|
def update_name name
|
15
24
|
if @name != name
|
16
25
|
@name = name
|
17
26
|
changed
|
18
|
-
notify_observers
|
27
|
+
notify_observers :name_change, name
|
19
28
|
end
|
20
29
|
end
|
21
30
|
|
data/server/common.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module LineCleaner
|
2
|
+
def clean data
|
3
|
+
@buffer ||= ""
|
4
|
+
@buffer += data
|
5
|
+
|
6
|
+
msgs = []
|
7
|
+
|
8
|
+
while @buffer.length > 0
|
9
|
+
next_close = @buffer.index "\x03"
|
10
|
+
|
11
|
+
break if next_close.nil?
|
12
|
+
|
13
|
+
next_piece = @buffer[1...next_close]
|
14
|
+
|
15
|
+
msgs << next_piece
|
16
|
+
|
17
|
+
@buffer = @buffer[(next_close + 1)..-1]
|
18
|
+
end
|
19
|
+
|
20
|
+
msgs
|
21
|
+
end
|
22
|
+
end
|
data/server/exchange.rb
CHANGED
@@ -3,8 +3,10 @@ require_relative 'order-book'
|
|
3
3
|
require_relative 'order'
|
4
4
|
require_relative 'server'
|
5
5
|
|
6
|
-
|
7
|
-
|
6
|
+
StartingEquity = 0
|
7
|
+
StartingCash = 10_000
|
8
|
+
DividendAmount = 0.25
|
9
|
+
DividendFrequency = 600
|
8
10
|
|
9
11
|
class Exchange
|
10
12
|
def initialize
|
@@ -14,8 +16,19 @@ class Exchange
|
|
14
16
|
@book = OrderBook.new
|
15
17
|
end
|
16
18
|
|
19
|
+
def accounts
|
20
|
+
@accounts.values
|
21
|
+
end
|
22
|
+
|
23
|
+
# Pay dividends to all accounts
|
24
|
+
def pay_dividends
|
25
|
+
@accounts.values.each do |account|
|
26
|
+
account.process_dividend DividendAmount
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
17
30
|
def identify data
|
18
|
-
account = @accounts[data["peer_name"]] || Account.new(data["peer_name"], data["name"],
|
31
|
+
account = @accounts[data["peer_name"]] || Account.new(data["peer_name"], data["name"], StartingEquity, StartingCash)
|
19
32
|
|
20
33
|
account.ai = data["ai"]
|
21
34
|
account.update_name data["name"]
|
data/server/public/app.js
CHANGED
@@ -26,6 +26,13 @@ $(function () {
|
|
26
26
|
$(".ask").html(data.level1.ask.toFixed(2));
|
27
27
|
$(".last").html(data.level1.last.toFixed(2));
|
28
28
|
break;
|
29
|
+
case "accounts":
|
30
|
+
$("#leaderboard tbody").html(
|
31
|
+
$.map(data.accounts, function (obj) {
|
32
|
+
return "<tr><td>" + obj.join("</td><td>") + "</td></tr>";
|
33
|
+
}).join("")
|
34
|
+
);
|
35
|
+
break;
|
29
36
|
}
|
30
37
|
};
|
31
38
|
|
data/server/public/index.html
CHANGED
@@ -37,11 +37,19 @@
|
|
37
37
|
</div>
|
38
38
|
</div>
|
39
39
|
<div class = "row-fluid">
|
40
|
-
<div class = "col-md-
|
41
|
-
|
42
|
-
|
43
|
-
<
|
44
|
-
|
40
|
+
<div class = "col-md-2"> </div>
|
41
|
+
<div class = "col-md-8">
|
42
|
+
<table class = "table table-bordered table-striped" id = "leaderboard">
|
43
|
+
<thead>
|
44
|
+
<tr>
|
45
|
+
<th>Name</th>
|
46
|
+
<th>Value</th>
|
47
|
+
<th>Cash</th>
|
48
|
+
<th>Stock</th>
|
49
|
+
</tr>
|
50
|
+
</thead>
|
51
|
+
<tbody></tbody>
|
52
|
+
</table>
|
45
53
|
</div>
|
46
54
|
</div>
|
47
55
|
</div>
|
data/server/server.rb
CHANGED
@@ -3,14 +3,36 @@ require 'json'
|
|
3
3
|
|
4
4
|
require_relative 'exchange'
|
5
5
|
require_relative 'web_server'
|
6
|
+
require_relative 'common'
|
7
|
+
|
8
|
+
AccountUpdateFrequency = 30
|
6
9
|
|
7
10
|
class OrderServer < EM::Connection
|
11
|
+
include LineCleaner
|
12
|
+
|
8
13
|
def self.setup parent
|
9
14
|
@@exchange = Exchange.new
|
10
15
|
@@parent = parent
|
16
|
+
|
17
|
+
EM.add_periodic_timer DividendFrequency do
|
18
|
+
@@exchange.pay_dividends
|
19
|
+
end
|
20
|
+
|
21
|
+
EM.add_periodic_timer AccountUpdateFrequency do
|
22
|
+
level1 = @@exchange.level1
|
23
|
+
Webapp.update_accounts(@@exchange.accounts.select { |account|
|
24
|
+
#not account.ai?
|
25
|
+
true
|
26
|
+
}.map { |account|
|
27
|
+
[account.name, account.net_value(level1[:last]), account.stock, account.cash]
|
28
|
+
}.sort_by { |row|
|
29
|
+
row[3]
|
30
|
+
})
|
31
|
+
end
|
11
32
|
end
|
12
33
|
|
13
34
|
def post_init
|
35
|
+
@buffer = ""
|
14
36
|
@my_orders = {}
|
15
37
|
end
|
16
38
|
|
@@ -52,20 +74,17 @@ class OrderServer < EM::Connection
|
|
52
74
|
local_id: order.local_id
|
53
75
|
}.to_json)
|
54
76
|
@@parent.tick @@exchange
|
77
|
+
when :dividend
|
78
|
+
send_data_f({
|
79
|
+
action: "dividend",
|
80
|
+
value: args[0][:value]
|
81
|
+
}.to_json)
|
55
82
|
end
|
56
83
|
end
|
57
84
|
|
58
85
|
def send_data_f data
|
59
86
|
send_data "\x02#{data}\x03"
|
60
87
|
end
|
61
|
-
|
62
|
-
def clean data
|
63
|
-
if data.length > 2
|
64
|
-
data[1..-2].split("\x03\x02")
|
65
|
-
else
|
66
|
-
[]
|
67
|
-
end
|
68
|
-
end
|
69
88
|
|
70
89
|
def handle_message data
|
71
90
|
case data["action"]
|
@@ -73,15 +92,17 @@ class OrderServer < EM::Connection
|
|
73
92
|
_, ip = Socket.unpack_sockaddr_in get_peername
|
74
93
|
data["peer_name"] = ip
|
75
94
|
puts "User #{data['name']}@#{data["peer_name"]} connected."
|
95
|
+
|
96
|
+
@account.delete_observer self if @account
|
76
97
|
@account = @@exchange.identify data
|
98
|
+
@account.add_observer self
|
99
|
+
|
77
100
|
send_data_f({
|
78
101
|
action: "account_update",
|
79
102
|
cash: @account.cash,
|
80
103
|
stock: @account.stock
|
81
104
|
}.to_json)
|
82
105
|
when "new_order"
|
83
|
-
puts "new order"
|
84
|
-
|
85
106
|
error, order = @@exchange.new_order @account, data
|
86
107
|
|
87
108
|
if error
|
@@ -102,14 +123,13 @@ class OrderServer < EM::Connection
|
|
102
123
|
price: order.price
|
103
124
|
}.to_json)
|
104
125
|
|
105
|
-
@my_orders[order.
|
126
|
+
@my_orders[order.local_id] = order
|
106
127
|
@@exchange.send_order order
|
107
128
|
@@parent.tick @@exchange
|
108
129
|
end
|
109
130
|
when "cancel_order"
|
110
131
|
order = @my_orders[data["id"]]
|
111
132
|
if order
|
112
|
-
puts "cancelling #{data["id"]}"
|
113
133
|
@@exchange.cancel_order order
|
114
134
|
@@parent.tick @@exchange
|
115
135
|
else
|
@@ -127,15 +147,13 @@ end
|
|
127
147
|
|
128
148
|
class Server
|
129
149
|
def tick exchange
|
130
|
-
puts "sending tick"
|
131
150
|
msg = {
|
132
151
|
action: "tick",
|
133
152
|
level1: exchange.level1
|
134
153
|
}
|
135
154
|
Webapp.level1_update exchange.level1
|
136
155
|
|
137
|
-
|
138
|
-
puts @feed_socket.send_msg(msg.to_json)
|
156
|
+
@feed_socket.send_msg(msg.to_json)
|
139
157
|
end
|
140
158
|
|
141
159
|
def start args = {}
|
@@ -145,9 +163,9 @@ class Server
|
|
145
163
|
|
146
164
|
@context = EM::ZeroMQ::Context.new 1
|
147
165
|
|
148
|
-
OrderServer.setup self
|
149
|
-
|
150
166
|
EM.run do
|
167
|
+
OrderServer.setup self
|
168
|
+
|
151
169
|
puts "Listening for clients on #{order_port}"
|
152
170
|
EM.start_server "0.0.0.0", order_port, OrderServer
|
153
171
|
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require_relative "../common"
|
2
|
+
|
3
|
+
class Cleaner
|
4
|
+
include LineCleaner
|
5
|
+
end
|
6
|
+
|
7
|
+
describe LineCleaner do
|
8
|
+
before :each do
|
9
|
+
@cleaner = Cleaner.new
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should work with a basic message" do
|
13
|
+
res = @cleaner.clean "\x02this is a message\x03"
|
14
|
+
|
15
|
+
res.length.should == 1
|
16
|
+
res[0].should == "this is a message"
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should split multiple messages" do
|
20
|
+
res = @cleaner.clean "\x02this is a message\x03\x02this is another message\x03\x02this is a third message\x03"
|
21
|
+
|
22
|
+
res.length.should == 3
|
23
|
+
res[0].should == "this is a message"
|
24
|
+
res[1].should == "this is another message"
|
25
|
+
res[2].should == "this is a third message"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should carry through different transmissions" do
|
29
|
+
res = @cleaner.clean "\x02this is a message\x03\x02this is a second"
|
30
|
+
|
31
|
+
res.length.should == 1
|
32
|
+
res[0].should == "this is a message"
|
33
|
+
|
34
|
+
res = @cleaner.clean " message that has been chopped in half\x03\x02this is a final message\x03"
|
35
|
+
|
36
|
+
res.length.should == 2
|
37
|
+
res[0].should == "this is a second message that has been chopped in half"
|
38
|
+
res[1].should == "this is a final message"
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should buffer a big message" do
|
42
|
+
res = @cleaner.clean "\x02this is a message that does not"
|
43
|
+
|
44
|
+
res.length.should == 0
|
45
|
+
|
46
|
+
res = @cleaner.clean " end until later\x03"
|
47
|
+
|
48
|
+
res.length.should == 1
|
49
|
+
res[0].should == "this is a message that does not end until later"
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should not split on end of string" do
|
53
|
+
res = @cleaner.clean "\x02this is a message with a split at the end\x03\x02"
|
54
|
+
|
55
|
+
res.length.should == 1
|
56
|
+
res[0].should == "this is a message with a split at the end"
|
57
|
+
|
58
|
+
res = @cleaner.clean "and here is the end\x03"
|
59
|
+
|
60
|
+
res.length.should == 1
|
61
|
+
res[0].should == "and here is the end"
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should not split at the start of the string" do
|
65
|
+
res = @cleaner.clean "\x02this is the message with the split at the start"
|
66
|
+
|
67
|
+
res.length.should == 0
|
68
|
+
|
69
|
+
res = @cleaner.clean "\x03\x02and here is the second bit.\x03"
|
70
|
+
|
71
|
+
res.length.should == 2
|
72
|
+
res[0].should == "this is the message with the split at the start"
|
73
|
+
res[1].should == "and here is the second bit."
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
data/server/web_server.rb
CHANGED
@@ -2,14 +2,13 @@ require "sinatra/base"
|
|
2
2
|
require "sinatra-websocket"
|
3
3
|
require "thin"
|
4
4
|
require "rack"
|
5
|
-
require "rack/sockjs"
|
6
5
|
require 'observer'
|
7
6
|
require 'json'
|
8
7
|
|
9
8
|
class Level1
|
10
9
|
include Observable
|
11
10
|
|
12
|
-
attr_reader :level1
|
11
|
+
attr_reader :level1, :accounts
|
13
12
|
|
14
13
|
def initialize
|
15
14
|
@level1 = {
|
@@ -24,6 +23,12 @@ class Level1
|
|
24
23
|
changed
|
25
24
|
notify_observers :level1, level1
|
26
25
|
end
|
26
|
+
|
27
|
+
def update_accounts accounts
|
28
|
+
@accounts = accounts
|
29
|
+
changed
|
30
|
+
notify_observers :accounts, accounts
|
31
|
+
end
|
27
32
|
end
|
28
33
|
|
29
34
|
class SocketWrapper
|
@@ -44,6 +49,11 @@ class SocketWrapper
|
|
44
49
|
action: action,
|
45
50
|
level1: data[0]
|
46
51
|
}.to_json)
|
52
|
+
when :accounts
|
53
|
+
@socket.send({
|
54
|
+
action: action,
|
55
|
+
accounts: data[0]
|
56
|
+
}.to_json)
|
47
57
|
end
|
48
58
|
end
|
49
59
|
end
|
@@ -88,6 +98,10 @@ class Webapp < Sinatra::Base
|
|
88
98
|
def self.level1_update level1
|
89
99
|
@@level1.update_level1 level1
|
90
100
|
end
|
101
|
+
|
102
|
+
def self.update_accounts accounts
|
103
|
+
@@level1.update_accounts accounts
|
104
|
+
end
|
91
105
|
end
|
92
106
|
|
93
107
|
def run_webserver opts
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-trade
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.3'
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -21,16 +21,18 @@ files:
|
|
21
21
|
- README.md
|
22
22
|
- examples/market_maker.rb
|
23
23
|
- examples/slammer.rb
|
24
|
+
- examples/stress.rb
|
24
25
|
- lib/Gemfile
|
25
26
|
- lib/Gemfile.lock
|
26
27
|
- lib/client.rb
|
27
28
|
- lib/order.rb
|
28
29
|
- lib/ruby-trade.rb
|
29
|
-
-
|
30
|
+
- ruby-trade.gemspec
|
30
31
|
- server/Gemfile
|
31
32
|
- server/Gemfile.lock
|
32
33
|
- server/account.rb
|
33
34
|
- server/app.rb
|
35
|
+
- server/common.rb
|
34
36
|
- server/exchange.rb
|
35
37
|
- server/order-book.rb
|
36
38
|
- server/order.rb
|
@@ -159,6 +161,7 @@ files:
|
|
159
161
|
- server/public/jquery-2.0.3.min.js
|
160
162
|
- server/public/sockjs-0.2.1.min.js
|
161
163
|
- server/server.rb
|
164
|
+
- server/test/test_common.rb
|
162
165
|
- server/test/test_order_book.rb
|
163
166
|
- server/web_server.rb
|
164
167
|
homepage:
|