stockfighter 0.3.1 → 0.4.0
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.
- checksums.yaml +4 -4
- data/README.md +40 -8
- data/Rakefile +8 -0
- data/lib/stockfighter/api.rb +27 -16
- data/lib/stockfighter/gm.rb +30 -16
- data/lib/stockfighter/version.rb +1 -1
- data/lib/stockfighter/websockets.rb +65 -51
- data/stockfighter.gemspec +1 -0
- data/test/test_api.rb +76 -0
- metadata +19 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 732fe76a171ed98a78d642e01b24606254ca687b
|
4
|
+
data.tar.gz: d2798df2beb98d49509180d44196ec8a0ed6d444
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5f44263691227792ed908acc7f9eb009889bcf4b3f2dbe936499cab18ef3ca3632a1cb04f4016ee091da2f763cf316baaf753663d363f303b11608f8d5b56f2d
|
7
|
+
data.tar.gz: dd09455db5254b0d77438738a3b35cde95f66ffcd8cd7562e8136028706d939bf8626568f29760fbd545f458a0535f7d7c80ab3cac23d039d506a4149effabaf
|
data/README.md
CHANGED
@@ -43,22 +43,33 @@ gm = Stockfighter::GM.new(key: "supersecretapikey1234567", level: "first_steps")
|
|
43
43
|
|
44
44
|
api = Stockfighter::Api.new(gm.config)
|
45
45
|
|
46
|
-
# Use the GM to register message callbacks for messages received
|
46
|
+
# Use the GM to register message callbacks for messages received & trading day notification from the GM.
|
47
|
+
# The GM needs to be initialized with polling: true to set up polling of the GM and enable callbacks.
|
47
48
|
|
48
49
|
gm = Stockfighter::GM.new(key: "supersecretapikey1234567", level: "first_steps", polling: true)
|
49
50
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
51
|
+
ansi_code = Hash.new
|
52
|
+
ansi_code['success'] = "\e[#32m"
|
53
|
+
ansi_code['info'] = "\e[#34m"
|
54
|
+
ansi_code['warning'] = "\e[#33m"
|
55
|
+
ansi_code['error'] = "\e[#31m"
|
56
|
+
ansi_code['danger'] = "\e[#31m"
|
57
|
+
|
58
|
+
gm.add_message_callback { |type,message|
|
59
|
+
abort("Unhandled message type #{type}") unless ansi_code.key?(type)
|
60
|
+
puts "#{ansi_code[type]}#{message}\e[0m"
|
55
61
|
}
|
62
|
+
|
56
63
|
gm.add_state_change_callback { |previous_state, new_state|
|
57
64
|
if new_state == 'won'
|
58
65
|
puts "You've won!"
|
59
66
|
end
|
60
67
|
}
|
61
68
|
|
69
|
+
gm.add_trading_day_callback { |previous_trading_day, current_trading_day, end_of_the_world_day|
|
70
|
+
# Due to the relatively infrequent polling (to avoid rate limiting), the callback will not be called on every individual trading day
|
71
|
+
}
|
72
|
+
|
62
73
|
# Restart the level
|
63
74
|
|
64
75
|
gm.restart
|
@@ -120,19 +131,37 @@ api = Stockfighter::Api.new(key: key, account: account, symbol: symbol, venue: v
|
|
120
131
|
```ruby
|
121
132
|
websockets = Stockfighter::Websockets.new(gm.config)
|
122
133
|
websockets.add_quote_callback { |quote|
|
134
|
+
# Ensure you don't have long running operations (eg calling api.*) as part of this
|
135
|
+
# callback method as the event processing for all websockets is performed on 1 thread.
|
123
136
|
puts quote
|
124
137
|
}
|
138
|
+
|
125
139
|
websockets.add_execution_callback { |execution|
|
140
|
+
# Ensure you don't have long running operations (eg calling api.*) as part of this
|
141
|
+
# callback method as the event processing for all websockets is performed on 1 thread.
|
126
142
|
puts execution
|
127
143
|
}
|
144
|
+
|
128
145
|
websockets.start()
|
146
|
+
|
147
|
+
# The tickertape websocket can be optionally disabled when calling start()
|
148
|
+
|
149
|
+
websockets = Stockfighter::Websockets.new(gm.config)
|
150
|
+
websockets.add_execution_callback { |execution|
|
151
|
+
puts execution
|
152
|
+
}
|
153
|
+
|
154
|
+
websockets.start(tickertape_enabled:false)
|
155
|
+
|
156
|
+
# The executions websocket can also be disabled by passing executions_enabled:false when calling start()
|
157
|
+
|
129
158
|
```
|
130
159
|
|
131
160
|
## Todo
|
132
161
|
|
133
162
|
* ~~TODO: Usage instructions!~~
|
134
163
|
* ~~TODO: Game master integration~~
|
135
|
-
* TODO: Tests
|
164
|
+
* TODO: Tests!
|
136
165
|
* TODO: Error Handling (partially complete)
|
137
166
|
|
138
167
|
## Contributing
|
@@ -141,4 +170,7 @@ websockets.start()
|
|
141
170
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
142
171
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
143
172
|
4. Push to the branch (`git push origin my-new-feature`)
|
144
|
-
5.
|
173
|
+
5. Write some tests
|
174
|
+
6. Run all the tests using the following command:
|
175
|
+
`API_KEY="insert_your_api_key_here" rake test`
|
176
|
+
7. Create a new Pull Request
|
data/Rakefile
CHANGED
data/lib/stockfighter/api.rb
CHANGED
@@ -2,9 +2,8 @@ require 'httparty'
|
|
2
2
|
|
3
3
|
module Stockfighter
|
4
4
|
class Api
|
5
|
-
|
6
|
-
|
7
|
-
def initialize(key:, account:, symbol:, venue:)
|
5
|
+
def initialize(key:, account:, symbol:, venue:, base_url: "https://api.stockfighter.io/ob/api")
|
6
|
+
@base_url = base_url
|
8
7
|
@api_key = key
|
9
8
|
@account = account
|
10
9
|
@symbol = symbol
|
@@ -12,7 +11,7 @@ module Stockfighter
|
|
12
11
|
end
|
13
12
|
|
14
13
|
def get_quote
|
15
|
-
|
14
|
+
perform_request("get", "#{@base_url}/venues/#{@venue}/stocks/#{@symbol}/quote")
|
16
15
|
end
|
17
16
|
|
18
17
|
def place_order(price:, quantity:, direction:, order_type:)
|
@@ -25,37 +24,49 @@ module Stockfighter
|
|
25
24
|
"direction" => direction,
|
26
25
|
"orderType" => order_type
|
27
26
|
}
|
28
|
-
|
29
|
-
HTTParty.post("#{BASE_URL}/venues/#{@venue}/stocks/#{@symbol}/orders", body: JSON.dump(order),
|
30
|
-
headers: auth_header).parsed_response
|
27
|
+
perform_request("post", "#{@base_url}/venues/#{@venue}/stocks/#{@symbol}/orders", body: JSON.dump(order))
|
31
28
|
end
|
32
29
|
|
33
30
|
def cancel_order(order_id)
|
34
|
-
|
31
|
+
perform_request("delete", "#{@base_url}/venues/#{@venue}/stocks/#{@symbol}/orders/#{order_id}")
|
35
32
|
end
|
36
33
|
|
37
34
|
def order_status(order_id)
|
38
|
-
|
35
|
+
perform_request("get", "#{@base_url}/venues/#{@venue}/stocks/#{@symbol}/orders/#{order_id}")
|
39
36
|
end
|
40
37
|
|
41
38
|
def order_book
|
42
|
-
|
39
|
+
perform_request("get", "#{@base_url}/venues/#{@venue}/stocks/#{@symbol}")
|
43
40
|
end
|
44
41
|
|
45
42
|
def venue_up?
|
46
|
-
response =
|
43
|
+
response = perform_request("get", "#{@base_url}/venues/#{@venue}/heartbeat")
|
47
44
|
response["ok"]
|
48
45
|
end
|
49
46
|
|
50
47
|
def status_all
|
51
|
-
|
48
|
+
perform_request("get", "#{@base_url}/venues/#{@venue}/accounts/#{@account}/orders")
|
52
49
|
end
|
53
50
|
|
54
|
-
def
|
55
|
-
|
56
|
-
|
51
|
+
def perform_request(action, url, body:nil)
|
52
|
+
options = {
|
53
|
+
:headers => {"X-Starfighter-Authorization" => @api_key},
|
54
|
+
:format => :json
|
55
|
+
}
|
56
|
+
if body != nil
|
57
|
+
options[:body] = body
|
58
|
+
end
|
59
|
+
response = HTTParty.method(action).call(url, options)
|
57
60
|
|
58
|
-
|
61
|
+
if response.code == 200 and response["ok"]
|
62
|
+
response
|
63
|
+
elsif not response["ok"]
|
64
|
+
raise "Error response received from #{url}: #{response['error']}"
|
65
|
+
else
|
66
|
+
raise "HTTP error response received from #{url}: #{response.code}"
|
67
|
+
end
|
68
|
+
end
|
59
69
|
|
70
|
+
private :perform_request
|
60
71
|
end
|
61
72
|
end
|
data/lib/stockfighter/gm.rb
CHANGED
@@ -11,12 +11,13 @@ module Stockfighter
|
|
11
11
|
@api_key = key
|
12
12
|
@level = level
|
13
13
|
|
14
|
-
@
|
15
|
-
@message_callbacks = Hash.new { |h,k| h[k] = [] }
|
14
|
+
@message_callbacks = []
|
16
15
|
@state_change_callbacks = []
|
16
|
+
@trading_day_callbacks = []
|
17
17
|
|
18
18
|
new_level_response = perform_request("post", "#{GM_URL}/levels/#{level}")
|
19
19
|
previous_state = new_level_response['state']
|
20
|
+
previous_trading_day = 0
|
20
21
|
|
21
22
|
if polling
|
22
23
|
# websocket API functionality instead of polling would be great here
|
@@ -31,19 +32,34 @@ module Stockfighter
|
|
31
32
|
}
|
32
33
|
previous_state = current_state
|
33
34
|
end
|
35
|
+
|
36
|
+
if response.key?('details')
|
37
|
+
details = response['details']
|
38
|
+
current_trading_day = details['tradingDay']
|
39
|
+
end_of_the_world_day = details['endOfTheWorldDay']
|
40
|
+
if previous_trading_day != current_trading_day
|
41
|
+
@trading_day_callbacks.each { |callback|
|
42
|
+
callback.call(previous_trading_day, current_trading_day, end_of_the_world_day)
|
43
|
+
}
|
44
|
+
previous_trading_day = current_trading_day
|
45
|
+
end
|
46
|
+
end
|
34
47
|
end
|
35
48
|
end
|
36
49
|
end
|
37
50
|
|
38
|
-
def add_message_callback(
|
39
|
-
|
40
|
-
@message_callbacks[type] << block
|
51
|
+
def add_message_callback(&block)
|
52
|
+
@message_callbacks << block
|
41
53
|
end
|
42
54
|
|
43
55
|
def add_state_change_callback(&block)
|
44
56
|
@state_change_callbacks << block
|
45
57
|
end
|
46
58
|
|
59
|
+
def add_trading_day_callback(&block)
|
60
|
+
@trading_day_callbacks << block
|
61
|
+
end
|
62
|
+
|
47
63
|
def config
|
48
64
|
if @config[:account] && @config[:venue] && @config[:symbol]
|
49
65
|
@config
|
@@ -74,7 +90,11 @@ module Stockfighter
|
|
74
90
|
end
|
75
91
|
|
76
92
|
def perform_request(action, url)
|
77
|
-
|
93
|
+
options = {
|
94
|
+
:headers => {"X-Starfighter-Authorization" => @api_key},
|
95
|
+
:format => :json
|
96
|
+
}
|
97
|
+
response = HTTParty.method(action).call(url, options)
|
78
98
|
if response.code != 200
|
79
99
|
raise "HTTP error response received from #{url}: #{response.code}"
|
80
100
|
end
|
@@ -84,17 +104,11 @@ module Stockfighter
|
|
84
104
|
|
85
105
|
if response.key?('flash')
|
86
106
|
flash = response['flash']
|
87
|
-
|
88
|
-
@message_callbacks
|
89
|
-
callback.call(
|
107
|
+
flash.each { |type,message|
|
108
|
+
@message_callbacks.each { |callback|
|
109
|
+
callback.call(type, message)
|
90
110
|
}
|
91
|
-
|
92
|
-
@message_callbacks['info'].each { |callback|
|
93
|
-
callback.call(flash['info'])
|
94
|
-
}
|
95
|
-
else
|
96
|
-
raise "TODO: Unhandled flash scenario: #{response}"
|
97
|
-
end
|
111
|
+
}
|
98
112
|
end
|
99
113
|
|
100
114
|
if response.key?('instructions')
|
data/lib/stockfighter/version.rb
CHANGED
@@ -13,7 +13,7 @@ module Stockfighter
|
|
13
13
|
@execution_callbacks = []
|
14
14
|
end
|
15
15
|
|
16
|
-
def start()
|
16
|
+
def start(tickertape_enabled:true, executions_enabled:true)
|
17
17
|
|
18
18
|
EM.epoll
|
19
19
|
EM.run do
|
@@ -22,71 +22,85 @@ module Stockfighter
|
|
22
22
|
abort("Error raised during event loop: #{e}")
|
23
23
|
}
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
25
|
+
if tickertape_enabled
|
26
|
+
tickertape_options = {
|
27
|
+
:uri => "#{WS_URL}/#{@account}/venues/#{@venue}/tickertape",
|
28
|
+
:ssl => true
|
29
|
+
}
|
29
30
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
raise "tickertape websocket: Error response received: #{msg}"
|
31
|
+
tickertape = WebSocket::EventMachine::Client.connect(tickertape_options)
|
32
|
+
tickertape.onopen do
|
33
|
+
puts "#{@account} tickertape websocket: connected"
|
34
34
|
end
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
35
|
+
|
36
|
+
tickertape.onmessage do |msg|
|
37
|
+
incoming = JSON.parse(msg)
|
38
|
+
if not incoming["ok"]
|
39
|
+
raise "#{@account} tickertape websocket: Error response received: #{msg}"
|
40
|
+
end
|
41
|
+
if incoming.key?('quote')
|
42
|
+
quote = incoming['quote']
|
43
|
+
@quote_callbacks.each { |callback|
|
44
|
+
callback.call(quote)
|
45
|
+
}
|
46
|
+
else
|
47
|
+
raise "#{@account} tickertape websocket: TODO: Unhandled message type: #{msg}"
|
48
|
+
end
|
42
49
|
end
|
43
|
-
end
|
44
50
|
|
45
|
-
|
46
|
-
|
47
|
-
|
51
|
+
tickertape.onerror do |e|
|
52
|
+
puts "#{@account} tickertape websocket: Error #{e}"
|
53
|
+
end
|
48
54
|
|
49
|
-
|
50
|
-
|
51
|
-
|
55
|
+
tickertape.onping do |msg|
|
56
|
+
puts "#{@account} tickertape websocket: Received ping: #{msg}"
|
57
|
+
end
|
52
58
|
|
53
|
-
|
54
|
-
|
55
|
-
|
59
|
+
tickertape.onpong do |msg|
|
60
|
+
puts "#{@account} tickertape websocket: Received pong: #{msg}"
|
61
|
+
end
|
56
62
|
|
57
|
-
|
58
|
-
|
63
|
+
tickertape.onclose do |code, reason|
|
64
|
+
raise "#{@account} tickertape websocket: Client disconnected with status code: #{code} and reason: #{reason}"
|
65
|
+
end
|
59
66
|
end
|
60
67
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
68
|
+
if executions_enabled
|
69
|
+
executions_options = {
|
70
|
+
:uri => "#{WS_URL}/#{@account}/venues/#{@venue}/executions",
|
71
|
+
:ssl => true
|
72
|
+
}
|
65
73
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
raise "execution websocket: Error response received: #{msg}"
|
74
|
+
executions = WebSocket::EventMachine::Client.connect(executions_options)
|
75
|
+
executions.onopen do
|
76
|
+
puts "#{@account} executions websocket: connected"
|
70
77
|
end
|
71
|
-
@execution_callbacks.each { |callback|
|
72
|
-
callback.call(execution)
|
73
|
-
}
|
74
|
-
end
|
75
78
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
+
executions.onmessage do |msg|
|
80
|
+
execution = JSON.parse(msg)
|
81
|
+
if not execution["ok"]
|
82
|
+
raise "#{@account} execution websocket: Error response received: #{msg}"
|
83
|
+
end
|
84
|
+
@execution_callbacks.each { |callback|
|
85
|
+
callback.call(execution)
|
86
|
+
}
|
87
|
+
end
|
79
88
|
|
80
|
-
|
81
|
-
|
82
|
-
|
89
|
+
executions.onerror do |e|
|
90
|
+
puts "#{@account} executions websocket: Error: #{e}"
|
91
|
+
end
|
83
92
|
|
84
|
-
|
85
|
-
|
86
|
-
|
93
|
+
executions.onping do |msg|
|
94
|
+
puts "#{@account} executions websocket: Received ping: #{msg}"
|
95
|
+
end
|
87
96
|
|
88
|
-
|
89
|
-
|
97
|
+
executions.onpong do |msg|
|
98
|
+
puts "#{@account} executions websocket: Received pong: #{msg}"
|
99
|
+
end
|
100
|
+
|
101
|
+
executions.onclose do |code, reason|
|
102
|
+
raise "#{@account} executions websocket: Client disconnected with status code: #{code} and reason: #{reason}"
|
103
|
+
end
|
90
104
|
end
|
91
105
|
end
|
92
106
|
end
|
data/stockfighter.gemspec
CHANGED
@@ -23,4 +23,5 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.add_runtime_dependency "websocket-eventmachine-client", "~> 1.1"
|
24
24
|
spec.add_development_dependency "bundler", "~> 1.7"
|
25
25
|
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
spec.add_development_dependency "minitest", "~> 5.8.3"
|
26
27
|
end
|
data/test/test_api.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'stockfighter'
|
3
|
+
|
4
|
+
class ApiTest < Minitest::Unit::TestCase
|
5
|
+
def test_get_quote
|
6
|
+
api = get_api()
|
7
|
+
|
8
|
+
quote = api.get_quote()
|
9
|
+
assert_equal 'FOOBAR', quote['symbol']
|
10
|
+
assert_equal 'TESTEX', quote['venue']
|
11
|
+
assert quote.key?('quoteTime')
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_place_order_invalid_scenarios
|
15
|
+
api = get_api()
|
16
|
+
|
17
|
+
assert_raises(RuntimeError) {
|
18
|
+
api.place_order(price:10000000, quantity:100, direction:'invalid_direction', order_type:'limit')
|
19
|
+
}
|
20
|
+
assert_raises(RuntimeError) {
|
21
|
+
api.place_order(price:10000000, quantity:100, direction:'buy', order_type:'invalid_order_type')
|
22
|
+
}
|
23
|
+
assert_raises(RuntimeError) {
|
24
|
+
api.place_order(price:-1, quantity:100, direction:'buy', order_type:'limit')
|
25
|
+
}
|
26
|
+
assert_raises(RuntimeError) {
|
27
|
+
api.place_order(price:10000000, quantity:-1, direction:'buy', order_type:'limit')
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_place_order_happy_day
|
32
|
+
api = get_api()
|
33
|
+
|
34
|
+
order = api.place_order(price:10, quantity:100, direction:'sell', order_type:'limit')
|
35
|
+
assert_equal 'FOOBAR', order['symbol']
|
36
|
+
assert_equal 'TESTEX', order['venue']
|
37
|
+
assert_equal 'sell', order['direction']
|
38
|
+
assert_equal 10, order['price']
|
39
|
+
assert_equal 'limit', order['orderType']
|
40
|
+
assert_equal 'EXB123456', order['account']
|
41
|
+
|
42
|
+
assert order.key?('ts')
|
43
|
+
assert order.key?('fills')
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_cancel_order_invalid_scenarios
|
47
|
+
api = get_api()
|
48
|
+
|
49
|
+
assert_raises(RuntimeError) {
|
50
|
+
api.cancel_order(1)
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_cancel_order_happy_day
|
55
|
+
api = get_api()
|
56
|
+
|
57
|
+
order = api.place_order(price:1, quantity:1000000, direction:'buy', order_type:'limit')
|
58
|
+
assert order['open']
|
59
|
+
|
60
|
+
cancel_response = api.cancel_order(order['id'])
|
61
|
+
assert !cancel_response['open']
|
62
|
+
end
|
63
|
+
|
64
|
+
def get_api
|
65
|
+
api_key = ENV['API_KEY']
|
66
|
+
assert api_key.to_s != '', "export API_KEY='secret' before running these tests, where 'secret' is your API key"
|
67
|
+
|
68
|
+
config = {
|
69
|
+
:key => api_key,
|
70
|
+
:account => 'EXB123456',
|
71
|
+
:venue => 'TESTEX',
|
72
|
+
:symbol => 'FOOBAR',
|
73
|
+
}
|
74
|
+
Stockfighter::Api.new(config)
|
75
|
+
end
|
76
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stockfighter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert J Samson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-01-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httparty
|
@@ -80,6 +80,20 @@ dependencies:
|
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '10.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: minitest
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 5.8.3
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 5.8.3
|
83
97
|
description: An API wrapper for Starfighter's Stockfighter - see www.stockfighter.io
|
84
98
|
email:
|
85
99
|
- rjsamson@me.com
|
@@ -98,6 +112,7 @@ files:
|
|
98
112
|
- lib/stockfighter/version.rb
|
99
113
|
- lib/stockfighter/websockets.rb
|
100
114
|
- stockfighter.gemspec
|
115
|
+
- test/test_api.rb
|
101
116
|
homepage: https://github.com/rjsamson/stockfighter
|
102
117
|
licenses:
|
103
118
|
- MIT
|
@@ -122,4 +137,5 @@ rubygems_version: 2.4.5
|
|
122
137
|
signing_key:
|
123
138
|
specification_version: 4
|
124
139
|
summary: An API wrapper for Starfighter's Stockfighter
|
125
|
-
test_files:
|
140
|
+
test_files:
|
141
|
+
- test/test_api.rb
|