ruby-trade 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (144) hide show
  1. data/.gitignore +5 -0
  2. data/README.md +99 -0
  3. data/examples/market_maker.rb +42 -0
  4. data/lib/Gemfile +6 -0
  5. data/lib/Gemfile.lock +23 -0
  6. data/lib/client.rb +203 -0
  7. data/lib/order.rb +49 -0
  8. data/lib/ruby-trade.rb +1 -0
  9. data/rubytrade.gemspec +12 -0
  10. data/server/Gemfile +12 -0
  11. data/server/Gemfile.lock +57 -0
  12. data/server/account.rb +39 -0
  13. data/server/app.rb +20 -0
  14. data/server/exchange.rb +59 -0
  15. data/server/order-book.rb +113 -0
  16. data/server/order.rb +60 -0
  17. data/server/public/app.js +51 -0
  18. data/server/public/dist/css/bootstrap-theme.css +459 -0
  19. data/server/public/dist/css/bootstrap-theme.min.css +9 -0
  20. data/server/public/dist/css/bootstrap.css +7098 -0
  21. data/server/public/dist/css/bootstrap.min.css +9 -0
  22. data/server/public/dist/fonts/glyphicons-halflings-regular.eot +0 -0
  23. data/server/public/dist/fonts/glyphicons-halflings-regular.svg +229 -0
  24. data/server/public/dist/fonts/glyphicons-halflings-regular.ttf +0 -0
  25. data/server/public/dist/fonts/glyphicons-halflings-regular.woff +0 -0
  26. data/server/public/dist/js/bootstrap.js +2002 -0
  27. data/server/public/dist/js/bootstrap.min.js +9 -0
  28. data/server/public/flot/API.md +1464 -0
  29. data/server/public/flot/CONTRIBUTING.md +99 -0
  30. data/server/public/flot/FAQ.md +75 -0
  31. data/server/public/flot/LICENSE.txt +22 -0
  32. data/server/public/flot/Makefile +12 -0
  33. data/server/public/flot/NEWS.md +893 -0
  34. data/server/public/flot/PLUGINS.md +143 -0
  35. data/server/public/flot/README.md +110 -0
  36. data/server/public/flot/build.log +0 -0
  37. data/server/public/flot/examples/ajax/data-eu-gdp-growth-1.json +4 -0
  38. data/server/public/flot/examples/ajax/data-eu-gdp-growth-2.json +4 -0
  39. data/server/public/flot/examples/ajax/data-eu-gdp-growth-3.json +4 -0
  40. data/server/public/flot/examples/ajax/data-eu-gdp-growth-4.json +4 -0
  41. data/server/public/flot/examples/ajax/data-eu-gdp-growth-5.json +4 -0
  42. data/server/public/flot/examples/ajax/data-eu-gdp-growth.json +4 -0
  43. data/server/public/flot/examples/ajax/data-japan-gdp-growth.json +4 -0
  44. data/server/public/flot/examples/ajax/data-usa-gdp-growth.json +4 -0
  45. data/server/public/flot/examples/ajax/index.html +173 -0
  46. data/server/public/flot/examples/annotating/index.html +87 -0
  47. data/server/public/flot/examples/axes-interacting/index.html +97 -0
  48. data/server/public/flot/examples/axes-multiple/index.html +77 -0
  49. data/server/public/flot/examples/axes-time-zones/date.js +893 -0
  50. data/server/public/flot/examples/axes-time-zones/index.html +114 -0
  51. data/server/public/flot/examples/axes-time-zones/tz/africa +1181 -0
  52. data/server/public/flot/examples/axes-time-zones/tz/antarctica +413 -0
  53. data/server/public/flot/examples/axes-time-zones/tz/asia +2717 -0
  54. data/server/public/flot/examples/axes-time-zones/tz/australasia +1719 -0
  55. data/server/public/flot/examples/axes-time-zones/tz/backward +117 -0
  56. data/server/public/flot/examples/axes-time-zones/tz/etcetera +81 -0
  57. data/server/public/flot/examples/axes-time-zones/tz/europe +2856 -0
  58. data/server/public/flot/examples/axes-time-zones/tz/factory +10 -0
  59. data/server/public/flot/examples/axes-time-zones/tz/iso3166.tab +276 -0
  60. data/server/public/flot/examples/axes-time-zones/tz/leapseconds +100 -0
  61. data/server/public/flot/examples/axes-time-zones/tz/northamerica +3235 -0
  62. data/server/public/flot/examples/axes-time-zones/tz/pacificnew +28 -0
  63. data/server/public/flot/examples/axes-time-zones/tz/solar87 +390 -0
  64. data/server/public/flot/examples/axes-time-zones/tz/solar88 +390 -0
  65. data/server/public/flot/examples/axes-time-zones/tz/solar89 +395 -0
  66. data/server/public/flot/examples/axes-time-zones/tz/southamerica +1711 -0
  67. data/server/public/flot/examples/axes-time-zones/tz/systemv +38 -0
  68. data/server/public/flot/examples/axes-time-zones/tz/yearistype.sh +38 -0
  69. data/server/public/flot/examples/axes-time-zones/tz/zone.tab +441 -0
  70. data/server/public/flot/examples/axes-time/index.html +137 -0
  71. data/server/public/flot/examples/background.png +0 -0
  72. data/server/public/flot/examples/basic-options/index.html +91 -0
  73. data/server/public/flot/examples/basic-usage/index.html +57 -0
  74. data/server/public/flot/examples/canvas/index.html +75 -0
  75. data/server/public/flot/examples/categories/index.html +64 -0
  76. data/server/public/flot/examples/examples.css +97 -0
  77. data/server/public/flot/examples/image/hs-2004-27-a-large-web.jpg +0 -0
  78. data/server/public/flot/examples/image/index.html +69 -0
  79. data/server/public/flot/examples/index.html +80 -0
  80. data/server/public/flot/examples/interacting/index.html +130 -0
  81. data/server/public/flot/examples/navigate/arrow-down.gif +0 -0
  82. data/server/public/flot/examples/navigate/arrow-left.gif +0 -0
  83. data/server/public/flot/examples/navigate/arrow-right.gif +0 -0
  84. data/server/public/flot/examples/navigate/arrow-up.gif +0 -0
  85. data/server/public/flot/examples/navigate/index.html +153 -0
  86. data/server/public/flot/examples/percentiles/index.html +79 -0
  87. data/server/public/flot/examples/realtime/index.html +122 -0
  88. data/server/public/flot/examples/resize/index.html +76 -0
  89. data/server/public/flot/examples/selection/index.html +141 -0
  90. data/server/public/flot/examples/series-errorbars/index.html +150 -0
  91. data/server/public/flot/examples/series-pie/index.html +818 -0
  92. data/server/public/flot/examples/series-toggle/index.html +121 -0
  93. data/server/public/flot/examples/series-types/index.html +90 -0
  94. data/server/public/flot/examples/shared/jquery-ui/jquery-ui.min.css +6 -0
  95. data/server/public/flot/examples/shared/jquery-ui/jquery-ui.min.js +6 -0
  96. data/server/public/flot/examples/stacking/index.html +107 -0
  97. data/server/public/flot/examples/symbols/index.html +76 -0
  98. data/server/public/flot/examples/threshold/index.html +76 -0
  99. data/server/public/flot/examples/tracking/index.html +135 -0
  100. data/server/public/flot/examples/visitors/index.html +146 -0
  101. data/server/public/flot/examples/zooming/index.html +144 -0
  102. data/server/public/flot/excanvas.js +1428 -0
  103. data/server/public/flot/excanvas.min.js +1 -0
  104. data/server/public/flot/jquery.colorhelpers.js +179 -0
  105. data/server/public/flot/jquery.colorhelpers.min.js +21 -0
  106. data/server/public/flot/jquery.flot.canvas.js +345 -0
  107. data/server/public/flot/jquery.flot.canvas.min.js +28 -0
  108. data/server/public/flot/jquery.flot.categories.js +190 -0
  109. data/server/public/flot/jquery.flot.categories.min.js +44 -0
  110. data/server/public/flot/jquery.flot.crosshair.js +176 -0
  111. data/server/public/flot/jquery.flot.crosshair.min.js +59 -0
  112. data/server/public/flot/jquery.flot.errorbars.js +353 -0
  113. data/server/public/flot/jquery.flot.errorbars.min.js +63 -0
  114. data/server/public/flot/jquery.flot.fillbetween.js +226 -0
  115. data/server/public/flot/jquery.flot.fillbetween.min.js +30 -0
  116. data/server/public/flot/jquery.flot.image.js +241 -0
  117. data/server/public/flot/jquery.flot.image.min.js +53 -0
  118. data/server/public/flot/jquery.flot.js +3061 -0
  119. data/server/public/flot/jquery.flot.min.js +29 -0
  120. data/server/public/flot/jquery.flot.navigate.js +346 -0
  121. data/server/public/flot/jquery.flot.navigate.min.js +86 -0
  122. data/server/public/flot/jquery.flot.pie.js +817 -0
  123. data/server/public/flot/jquery.flot.pie.min.js +56 -0
  124. data/server/public/flot/jquery.flot.resize.js +60 -0
  125. data/server/public/flot/jquery.flot.resize.min.js +19 -0
  126. data/server/public/flot/jquery.flot.selection.js +360 -0
  127. data/server/public/flot/jquery.flot.selection.min.js +79 -0
  128. data/server/public/flot/jquery.flot.stack.js +188 -0
  129. data/server/public/flot/jquery.flot.stack.min.js +36 -0
  130. data/server/public/flot/jquery.flot.symbol.js +71 -0
  131. data/server/public/flot/jquery.flot.symbol.min.js +14 -0
  132. data/server/public/flot/jquery.flot.threshold.js +142 -0
  133. data/server/public/flot/jquery.flot.threshold.min.js +43 -0
  134. data/server/public/flot/jquery.flot.time.js +431 -0
  135. data/server/public/flot/jquery.flot.time.min.js +9 -0
  136. data/server/public/flot/jquery.js +9472 -0
  137. data/server/public/flot/jquery.min.js +2 -0
  138. data/server/public/index.html +53 -0
  139. data/server/public/jquery-2.0.3.min.js +6 -0
  140. data/server/public/sockjs-0.2.1.min.js +27 -0
  141. data/server/server.rb +156 -0
  142. data/server/test/test_order_book.rb +118 -0
  143. data/server/web_server.rb +110 -0
  144. metadata +188 -0
@@ -0,0 +1,5 @@
1
+ # Vim files
2
+ *.swp
3
+
4
+ # Compiled gem
5
+ *.gem
@@ -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
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'zmq'
4
+ gem 'algorithms'
5
+ gem 'json'
6
+ gem 'em-zeromq'
@@ -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
@@ -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
@@ -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
@@ -0,0 +1 @@
1
+ require_relative "client"
@@ -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