ruby-trade 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/README.md +99 -0
- data/examples/market_maker.rb +42 -0
- data/lib/Gemfile +6 -0
- data/lib/Gemfile.lock +23 -0
- data/lib/client.rb +203 -0
- data/lib/order.rb +49 -0
- data/lib/ruby-trade.rb +1 -0
- data/rubytrade.gemspec +12 -0
- data/server/Gemfile +12 -0
- data/server/Gemfile.lock +57 -0
- data/server/account.rb +39 -0
- data/server/app.rb +20 -0
- data/server/exchange.rb +59 -0
- data/server/order-book.rb +113 -0
- data/server/order.rb +60 -0
- data/server/public/app.js +51 -0
- data/server/public/dist/css/bootstrap-theme.css +459 -0
- data/server/public/dist/css/bootstrap-theme.min.css +9 -0
- data/server/public/dist/css/bootstrap.css +7098 -0
- data/server/public/dist/css/bootstrap.min.css +9 -0
- data/server/public/dist/fonts/glyphicons-halflings-regular.eot +0 -0
- data/server/public/dist/fonts/glyphicons-halflings-regular.svg +229 -0
- data/server/public/dist/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/server/public/dist/fonts/glyphicons-halflings-regular.woff +0 -0
- data/server/public/dist/js/bootstrap.js +2002 -0
- data/server/public/dist/js/bootstrap.min.js +9 -0
- data/server/public/flot/API.md +1464 -0
- data/server/public/flot/CONTRIBUTING.md +99 -0
- data/server/public/flot/FAQ.md +75 -0
- data/server/public/flot/LICENSE.txt +22 -0
- data/server/public/flot/Makefile +12 -0
- data/server/public/flot/NEWS.md +893 -0
- data/server/public/flot/PLUGINS.md +143 -0
- data/server/public/flot/README.md +110 -0
- data/server/public/flot/build.log +0 -0
- data/server/public/flot/examples/ajax/data-eu-gdp-growth-1.json +4 -0
- data/server/public/flot/examples/ajax/data-eu-gdp-growth-2.json +4 -0
- data/server/public/flot/examples/ajax/data-eu-gdp-growth-3.json +4 -0
- data/server/public/flot/examples/ajax/data-eu-gdp-growth-4.json +4 -0
- data/server/public/flot/examples/ajax/data-eu-gdp-growth-5.json +4 -0
- data/server/public/flot/examples/ajax/data-eu-gdp-growth.json +4 -0
- data/server/public/flot/examples/ajax/data-japan-gdp-growth.json +4 -0
- data/server/public/flot/examples/ajax/data-usa-gdp-growth.json +4 -0
- data/server/public/flot/examples/ajax/index.html +173 -0
- data/server/public/flot/examples/annotating/index.html +87 -0
- data/server/public/flot/examples/axes-interacting/index.html +97 -0
- data/server/public/flot/examples/axes-multiple/index.html +77 -0
- data/server/public/flot/examples/axes-time-zones/date.js +893 -0
- data/server/public/flot/examples/axes-time-zones/index.html +114 -0
- data/server/public/flot/examples/axes-time-zones/tz/africa +1181 -0
- data/server/public/flot/examples/axes-time-zones/tz/antarctica +413 -0
- data/server/public/flot/examples/axes-time-zones/tz/asia +2717 -0
- data/server/public/flot/examples/axes-time-zones/tz/australasia +1719 -0
- data/server/public/flot/examples/axes-time-zones/tz/backward +117 -0
- data/server/public/flot/examples/axes-time-zones/tz/etcetera +81 -0
- data/server/public/flot/examples/axes-time-zones/tz/europe +2856 -0
- data/server/public/flot/examples/axes-time-zones/tz/factory +10 -0
- data/server/public/flot/examples/axes-time-zones/tz/iso3166.tab +276 -0
- data/server/public/flot/examples/axes-time-zones/tz/leapseconds +100 -0
- data/server/public/flot/examples/axes-time-zones/tz/northamerica +3235 -0
- data/server/public/flot/examples/axes-time-zones/tz/pacificnew +28 -0
- data/server/public/flot/examples/axes-time-zones/tz/solar87 +390 -0
- data/server/public/flot/examples/axes-time-zones/tz/solar88 +390 -0
- data/server/public/flot/examples/axes-time-zones/tz/solar89 +395 -0
- data/server/public/flot/examples/axes-time-zones/tz/southamerica +1711 -0
- data/server/public/flot/examples/axes-time-zones/tz/systemv +38 -0
- data/server/public/flot/examples/axes-time-zones/tz/yearistype.sh +38 -0
- data/server/public/flot/examples/axes-time-zones/tz/zone.tab +441 -0
- data/server/public/flot/examples/axes-time/index.html +137 -0
- data/server/public/flot/examples/background.png +0 -0
- data/server/public/flot/examples/basic-options/index.html +91 -0
- data/server/public/flot/examples/basic-usage/index.html +57 -0
- data/server/public/flot/examples/canvas/index.html +75 -0
- data/server/public/flot/examples/categories/index.html +64 -0
- data/server/public/flot/examples/examples.css +97 -0
- data/server/public/flot/examples/image/hs-2004-27-a-large-web.jpg +0 -0
- data/server/public/flot/examples/image/index.html +69 -0
- data/server/public/flot/examples/index.html +80 -0
- data/server/public/flot/examples/interacting/index.html +130 -0
- data/server/public/flot/examples/navigate/arrow-down.gif +0 -0
- data/server/public/flot/examples/navigate/arrow-left.gif +0 -0
- data/server/public/flot/examples/navigate/arrow-right.gif +0 -0
- data/server/public/flot/examples/navigate/arrow-up.gif +0 -0
- data/server/public/flot/examples/navigate/index.html +153 -0
- data/server/public/flot/examples/percentiles/index.html +79 -0
- data/server/public/flot/examples/realtime/index.html +122 -0
- data/server/public/flot/examples/resize/index.html +76 -0
- data/server/public/flot/examples/selection/index.html +141 -0
- data/server/public/flot/examples/series-errorbars/index.html +150 -0
- data/server/public/flot/examples/series-pie/index.html +818 -0
- data/server/public/flot/examples/series-toggle/index.html +121 -0
- data/server/public/flot/examples/series-types/index.html +90 -0
- data/server/public/flot/examples/shared/jquery-ui/jquery-ui.min.css +6 -0
- data/server/public/flot/examples/shared/jquery-ui/jquery-ui.min.js +6 -0
- data/server/public/flot/examples/stacking/index.html +107 -0
- data/server/public/flot/examples/symbols/index.html +76 -0
- data/server/public/flot/examples/threshold/index.html +76 -0
- data/server/public/flot/examples/tracking/index.html +135 -0
- data/server/public/flot/examples/visitors/index.html +146 -0
- data/server/public/flot/examples/zooming/index.html +144 -0
- data/server/public/flot/excanvas.js +1428 -0
- data/server/public/flot/excanvas.min.js +1 -0
- data/server/public/flot/jquery.colorhelpers.js +179 -0
- data/server/public/flot/jquery.colorhelpers.min.js +21 -0
- data/server/public/flot/jquery.flot.canvas.js +345 -0
- data/server/public/flot/jquery.flot.canvas.min.js +28 -0
- data/server/public/flot/jquery.flot.categories.js +190 -0
- data/server/public/flot/jquery.flot.categories.min.js +44 -0
- data/server/public/flot/jquery.flot.crosshair.js +176 -0
- data/server/public/flot/jquery.flot.crosshair.min.js +59 -0
- data/server/public/flot/jquery.flot.errorbars.js +353 -0
- data/server/public/flot/jquery.flot.errorbars.min.js +63 -0
- data/server/public/flot/jquery.flot.fillbetween.js +226 -0
- data/server/public/flot/jquery.flot.fillbetween.min.js +30 -0
- data/server/public/flot/jquery.flot.image.js +241 -0
- data/server/public/flot/jquery.flot.image.min.js +53 -0
- data/server/public/flot/jquery.flot.js +3061 -0
- data/server/public/flot/jquery.flot.min.js +29 -0
- data/server/public/flot/jquery.flot.navigate.js +346 -0
- data/server/public/flot/jquery.flot.navigate.min.js +86 -0
- data/server/public/flot/jquery.flot.pie.js +817 -0
- data/server/public/flot/jquery.flot.pie.min.js +56 -0
- data/server/public/flot/jquery.flot.resize.js +60 -0
- data/server/public/flot/jquery.flot.resize.min.js +19 -0
- data/server/public/flot/jquery.flot.selection.js +360 -0
- data/server/public/flot/jquery.flot.selection.min.js +79 -0
- data/server/public/flot/jquery.flot.stack.js +188 -0
- data/server/public/flot/jquery.flot.stack.min.js +36 -0
- data/server/public/flot/jquery.flot.symbol.js +71 -0
- data/server/public/flot/jquery.flot.symbol.min.js +14 -0
- data/server/public/flot/jquery.flot.threshold.js +142 -0
- data/server/public/flot/jquery.flot.threshold.min.js +43 -0
- data/server/public/flot/jquery.flot.time.js +431 -0
- data/server/public/flot/jquery.flot.time.min.js +9 -0
- data/server/public/flot/jquery.js +9472 -0
- data/server/public/flot/jquery.min.js +2 -0
- data/server/public/index.html +53 -0
- data/server/public/jquery-2.0.3.min.js +6 -0
- data/server/public/sockjs-0.2.1.min.js +27 -0
- data/server/server.rb +156 -0
- data/server/test/test_order_book.rb +118 -0
- data/server/web_server.rb +110 -0
- metadata +188 -0
data/README.md
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
# Ruby-Trade
|
2
|
+
|
3
|
+
Ruby-Trade is a game where each player builds an AI to compete against other AIs
|
4
|
+
in a virtual stock exchange.
|
5
|
+
|
6
|
+
The way it works is just like a real stock exchange: you place orders to trade
|
7
|
+
into the market to buy or sell shares.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Just install the gem (not working just yet)
|
12
|
+
|
13
|
+
gem install ruby-trade
|
14
|
+
|
15
|
+
## Lingo
|
16
|
+
|
17
|
+
Before getting started, there are a few definitions that you should know about:
|
18
|
+
|
19
|
+
* "fill" - An event triggered when one of your orders gets fully executed (that
|
20
|
+
is, all the shares that you requested to buy or sell are bought/sold).
|
21
|
+
* "partial fill" - The same as a fill, but not all of the shares in your order
|
22
|
+
were executed. For example if you sent an order to buy 100 shares and someone
|
23
|
+
only sold you 50, it is called a partial fill.
|
24
|
+
* "bid" - The highest price of all the buy orders.
|
25
|
+
* "ask" - The lowest price of all the sell orders.
|
26
|
+
* "last" - The last price that some shares were traded at.
|
27
|
+
* "spread" - The difference between the bid and the ask.
|
28
|
+
* "position" - The amount of stock that you have. If it is positive then it is
|
29
|
+
called a "long" position, if it is negative then it is called a "short" position.
|
30
|
+
Yes, it is possible to have negative shares.
|
31
|
+
* "Level 1" - The bid, the ask, and the last.
|
32
|
+
* "Outside the market" - Any buy order with a price less than the bid or any sell
|
33
|
+
order with a price higher than the ask is considered "outside the market."
|
34
|
+
|
35
|
+
## Client
|
36
|
+
|
37
|
+
Here is an example client:
|
38
|
+
|
39
|
+
require 'ruby-trade'
|
40
|
+
|
41
|
+
class MyApp
|
42
|
+
include RubyTrade::Client
|
43
|
+
|
44
|
+
# Called by the system when we connect to the exchange server
|
45
|
+
def self.on_connect
|
46
|
+
puts "sending order"
|
47
|
+
@buy_order = buy 100, at: 10.0
|
48
|
+
end
|
49
|
+
|
50
|
+
# Called whenever something happens on the exchange
|
51
|
+
def self.on_tick level1
|
52
|
+
puts "Cash: #{cash}"
|
53
|
+
puts "Stock: #{stock}"
|
54
|
+
puts "Bid: #{level1["bid"]}"
|
55
|
+
puts "Ask: #{level1["ask"]}"
|
56
|
+
puts "Last: #{level1["last"]}"
|
57
|
+
end
|
58
|
+
|
59
|
+
# Called when an order gets filled
|
60
|
+
def self.on_fill order, amount, price
|
61
|
+
puts "Order ID #{order.id} was filled for #{amount} shares at $%.2f" % price
|
62
|
+
end
|
63
|
+
|
64
|
+
# Called when an order gets partially filled
|
65
|
+
def self.on_partial_fill order, amount, price
|
66
|
+
puts "Order ID #{order.id} was partially filled for #{amount} shares at $%.2f" % price
|
67
|
+
|
68
|
+
# Cancel the order
|
69
|
+
@buy_order.cancel!
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
# Connect to the server
|
75
|
+
MyApp.connect_to "127.0.0.1", as: "Jim"
|
76
|
+
|
77
|
+
## Events
|
78
|
+
|
79
|
+
### Dividend
|
80
|
+
|
81
|
+
Every half hour, a dividend is paid out. Anybody who has a long position will
|
82
|
+
gain cash equal to the dividend times the number of shares that they own; anybody
|
83
|
+
with a short position will lose cash equal to the dividend times the number of
|
84
|
+
shares that they are short.
|
85
|
+
|
86
|
+
### Big Trades
|
87
|
+
|
88
|
+
At random times during the trading session, a large trade may be made. These trades
|
89
|
+
will be set at some price outside the market, the distance determined randomly.
|
90
|
+
These trades do not count towards the score.
|
91
|
+
|
92
|
+
### Market Makers
|
93
|
+
|
94
|
+
While not really an event, there will be a number of traders in the market who
|
95
|
+
are designated as "market makers." They keep buy orders and sell orders a little
|
96
|
+
bit outside the market to ensure that there is usually someone there to buy when
|
97
|
+
people want to sell, or sell when people want to buy. Note that they are not
|
98
|
+
always there, so traders should not rely on them being there. They are not there
|
99
|
+
to make money and do not count towards the score.
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "ruby-trade"
|
2
|
+
|
3
|
+
TradeAmount = 50_000
|
4
|
+
InitialPrice = 10.0
|
5
|
+
Distance = 2.0
|
6
|
+
|
7
|
+
class Marketmaker
|
8
|
+
include RubyTrade::Client
|
9
|
+
|
10
|
+
def self.on_connect *args
|
11
|
+
puts "Connected."
|
12
|
+
|
13
|
+
update_orders
|
14
|
+
|
15
|
+
this = self
|
16
|
+
EM.add_periodic_timer 5 do
|
17
|
+
this.update_orders
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.on_tick level1
|
22
|
+
@level1 = level1
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.update_orders
|
26
|
+
@buy_order.cancel! if @buy_order
|
27
|
+
@sell_order.cancel! if @sell_order
|
28
|
+
|
29
|
+
last = @level1 ? @level1[:last] : InitialPrice
|
30
|
+
last ||= InitialPrice
|
31
|
+
|
32
|
+
# buy and sell a certain percentage from the last
|
33
|
+
buy_price = last * (1.0 - Distance / 100)
|
34
|
+
sell_price = last * (1.0 + Distance / 100)
|
35
|
+
|
36
|
+
@buy_order = buy TradeAmount, at: buy_price
|
37
|
+
@sell_order = sell TradeAmount, at: sell_price
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
Marketmaker.connect_to "127.0.0.1", as: "MarketMaker", ai: true
|
data/lib/Gemfile
ADDED
data/lib/Gemfile.lock
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
algorithms (0.6.1)
|
5
|
+
em-zeromq (0.4.2)
|
6
|
+
eventmachine (>= 1.0.0)
|
7
|
+
ffi (>= 1.0.0)
|
8
|
+
ffi-rzmq (~> 1.0.1)
|
9
|
+
eventmachine (1.0.3)
|
10
|
+
ffi (1.9.0)
|
11
|
+
ffi-rzmq (1.0.3)
|
12
|
+
ffi
|
13
|
+
json (1.8.1)
|
14
|
+
zmq (2.1.4)
|
15
|
+
|
16
|
+
PLATFORMS
|
17
|
+
ruby
|
18
|
+
|
19
|
+
DEPENDENCIES
|
20
|
+
algorithms
|
21
|
+
em-zeromq
|
22
|
+
json
|
23
|
+
zmq
|
data/lib/client.rb
ADDED
@@ -0,0 +1,203 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'json'
|
3
|
+
require 'em-zeromq'
|
4
|
+
|
5
|
+
require_relative "order"
|
6
|
+
|
7
|
+
DEFAULT_FEED_PORT = 9000
|
8
|
+
DEFAULT_ORDER_PORT = 9001
|
9
|
+
|
10
|
+
module RubyTrade
|
11
|
+
module ConnectionClient
|
12
|
+
def self.setup args, parent
|
13
|
+
@@username = args[:as]
|
14
|
+
@@ai = args[:ai] || false
|
15
|
+
@@parent = parent
|
16
|
+
end
|
17
|
+
|
18
|
+
def post_init
|
19
|
+
# identify with the server
|
20
|
+
data = {
|
21
|
+
action: "identify",
|
22
|
+
name: @@username,
|
23
|
+
ai: @@ai
|
24
|
+
}.to_json
|
25
|
+
|
26
|
+
send_data_f data
|
27
|
+
|
28
|
+
@order_no = 0
|
29
|
+
@orders = {}
|
30
|
+
@cash, @stock = 0, 0
|
31
|
+
@connect_triggered = false
|
32
|
+
|
33
|
+
@@parent.child = self
|
34
|
+
end
|
35
|
+
|
36
|
+
def cash; @cash; end
|
37
|
+
def stock; @stock; end
|
38
|
+
|
39
|
+
def send_order side, size, price
|
40
|
+
@order_no += 1
|
41
|
+
|
42
|
+
order = Order.new @order_no, side, price, size
|
43
|
+
order.add_observer self
|
44
|
+
|
45
|
+
@orders[@order_no] = order
|
46
|
+
|
47
|
+
send_data_f({
|
48
|
+
action: "new_order",
|
49
|
+
local_id: @order_no,
|
50
|
+
size: size,
|
51
|
+
price: price,
|
52
|
+
side: side
|
53
|
+
}.to_json)
|
54
|
+
|
55
|
+
order
|
56
|
+
end
|
57
|
+
|
58
|
+
def update what, *args
|
59
|
+
case what
|
60
|
+
when :cancel
|
61
|
+
order = args[0]
|
62
|
+
send_data_f({
|
63
|
+
action: "cancel_order",
|
64
|
+
id: order.id
|
65
|
+
}.to_json)
|
66
|
+
else
|
67
|
+
# Don't need to handle anything else
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Send a buy order
|
72
|
+
def buy amount, args
|
73
|
+
send_order "buy", amount, args[:at]
|
74
|
+
end
|
75
|
+
|
76
|
+
# Send a sell order
|
77
|
+
def sell amount, args
|
78
|
+
send_order "sell", amount, args[:at]
|
79
|
+
end
|
80
|
+
|
81
|
+
# Send data with tokens
|
82
|
+
def send_data_f data
|
83
|
+
send_data "\x02#{data}\x03"
|
84
|
+
end
|
85
|
+
|
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
|
+
# Called by EM when we receive data
|
96
|
+
def receive_data data
|
97
|
+
clean(data).each do |msg|
|
98
|
+
handle_message JSON.parse msg
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Recalculate cash/stock balances
|
103
|
+
def update_account data
|
104
|
+
order = @orders[data["local_id"]]
|
105
|
+
|
106
|
+
if order.side == "buy"
|
107
|
+
@cash -= data["price"] * data["amount"]
|
108
|
+
@stock += data["amount"]
|
109
|
+
else
|
110
|
+
@cash += data["price"] * data["amount"]
|
111
|
+
@stock -= data["amount"]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Process a message from the server
|
116
|
+
def handle_message data
|
117
|
+
case data["action"]
|
118
|
+
when "order_accept"
|
119
|
+
when "order_fill"
|
120
|
+
update_account data
|
121
|
+
@@parent.on_fill @orders[data["local_id"]], data["amount"], data["price"]
|
122
|
+
when "order_partial_fill"
|
123
|
+
update_account data
|
124
|
+
@@parent.on_partial_fill @orders[data["local_id"]], data["amount"], data["price"]
|
125
|
+
when "order_cancel"
|
126
|
+
# Don't need to do anything here
|
127
|
+
when "account_update"
|
128
|
+
@cash, @stock = data["cash"], data["stock"]
|
129
|
+
|
130
|
+
if not @connect_triggered
|
131
|
+
@connect_triggered = true
|
132
|
+
@@parent.on_connect
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
module Client
|
139
|
+
def self.on_connect *args; end
|
140
|
+
def self.on_tick *args; end
|
141
|
+
def self.on_fill *args; end
|
142
|
+
def self.on_partial_fill *args; end
|
143
|
+
|
144
|
+
module ClassMethods
|
145
|
+
# hook so we can call child methods
|
146
|
+
def child= child
|
147
|
+
@@child = child
|
148
|
+
end
|
149
|
+
|
150
|
+
# Called when we receive feed data
|
151
|
+
def process_message data
|
152
|
+
case data["action"]
|
153
|
+
when "tick"
|
154
|
+
self.on_tick data["level1"]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def buy *args; @@child.buy(*args); end
|
159
|
+
def sell *args; @@child.sell(*args); end
|
160
|
+
def cash; @@child.cash; end
|
161
|
+
def stock; @@child.stock; end
|
162
|
+
|
163
|
+
def connect_to server, args
|
164
|
+
feed_port = args[:feed_port] || DEFAULT_FEED_PORT
|
165
|
+
order_port = args[:order_port] || DEFAULT_ORDER_PORT
|
166
|
+
|
167
|
+
if not args[:as]
|
168
|
+
raise "Need to specify a username: connect_to \"...\", as: \"username\""
|
169
|
+
end
|
170
|
+
|
171
|
+
@zmq_context = EM::ZeroMQ::Context.new 1
|
172
|
+
|
173
|
+
EM.run do
|
174
|
+
@feed = @zmq_context.socket ZMQ::SUB
|
175
|
+
|
176
|
+
puts "Listening to feed on #{server}:#{feed_port}"
|
177
|
+
@feed.connect "tcp://#{server}:#{feed_port}"
|
178
|
+
@feed.subscribe
|
179
|
+
|
180
|
+
@feed.on :message do |part|
|
181
|
+
begin
|
182
|
+
self.process_message JSON.parse part.copy_out_string
|
183
|
+
ensure
|
184
|
+
part.close
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
ConnectionClient.setup args, self
|
189
|
+
|
190
|
+
puts "Connecting to order server #{server}:#{order_port}"
|
191
|
+
EM.connect server, order_port, ConnectionClient
|
192
|
+
|
193
|
+
Signal.trap("INT") { EM.stop }
|
194
|
+
Signal.trap("TERM") { EM.stop }
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def self.included subclass
|
200
|
+
subclass.extend ClassMethods
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
data/lib/order.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'observer'
|
2
|
+
|
3
|
+
class Order
|
4
|
+
include Observable
|
5
|
+
|
6
|
+
attr_reader :id, :local_id, :side, :price, :size, :sent_at, :status
|
7
|
+
|
8
|
+
def initialize id, side, price, size
|
9
|
+
@id, @side, @price, @size = id, side, price, size
|
10
|
+
@sent_at = Time.now
|
11
|
+
@cancelled = false
|
12
|
+
@status = :pending_accept
|
13
|
+
end
|
14
|
+
|
15
|
+
def status= new_status
|
16
|
+
@status = new_status
|
17
|
+
end
|
18
|
+
|
19
|
+
def <=> order
|
20
|
+
if order.price == price
|
21
|
+
@sent_at <=> order.sent_at
|
22
|
+
else
|
23
|
+
price <=> order.price
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def cancelled?
|
28
|
+
@cancelled
|
29
|
+
end
|
30
|
+
|
31
|
+
def fill! amount
|
32
|
+
changed
|
33
|
+
|
34
|
+
status = :fill
|
35
|
+
status = :partial_fill if amount < @size
|
36
|
+
|
37
|
+
@size -= amount
|
38
|
+
|
39
|
+
notify_observers status, self
|
40
|
+
end
|
41
|
+
|
42
|
+
def cancel!
|
43
|
+
return if @cancelled # can only cancel an order once
|
44
|
+
|
45
|
+
@cancelled = true
|
46
|
+
changed
|
47
|
+
notify_observers :cancel, self
|
48
|
+
end
|
49
|
+
end
|
data/lib/ruby-trade.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative "client"
|
data/rubytrade.gemspec
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "ruby-trade"
|
3
|
+
s.version = "0.1"
|
4
|
+
s.date = "2013-11-09"
|
5
|
+
s.summary = "A stock market simulation game."
|
6
|
+
s.description = ""
|
7
|
+
s.authors = ["Rob Britton"]
|
8
|
+
s.email = "rob@robbritton.com"
|
9
|
+
s.files = `git ls-files`.split("\n")
|
10
|
+
s.require_paths = ["lib"]
|
11
|
+
s.license = "MIT"
|
12
|
+
end
|