ruby-trade 0.2 → 0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|