ruby-trade 0.1

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.
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