tradier 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +8 -0
- data/LICENSE.md +20 -0
- data/README.md +114 -0
- data/Rakefile +19 -0
- data/lib/tradier.rb +31 -0
- data/lib/tradier/account.rb +30 -0
- data/lib/tradier/api/accounts.rb +122 -0
- data/lib/tradier/api/markets.rb +113 -0
- data/lib/tradier/api/orders.rb +84 -0
- data/lib/tradier/api/utils.rb +47 -0
- data/lib/tradier/api/utils/account.rb +15 -0
- data/lib/tradier/api/utils/balance.rb +15 -0
- data/lib/tradier/api/utils/base.rb +46 -0
- data/lib/tradier/api/utils/event.rb +15 -0
- data/lib/tradier/api/utils/expiration.rb +19 -0
- data/lib/tradier/api/utils/gainloss.rb +15 -0
- data/lib/tradier/api/utils/history.rb +15 -0
- data/lib/tradier/api/utils/option_quote.rb +15 -0
- data/lib/tradier/api/utils/order.rb +15 -0
- data/lib/tradier/api/utils/position.rb +15 -0
- data/lib/tradier/api/utils/quote.rb +15 -0
- data/lib/tradier/api/utils/strike.rb +15 -0
- data/lib/tradier/api/utils/timesales.rb +15 -0
- data/lib/tradier/api/utils/watchlist.rb +15 -0
- data/lib/tradier/api/watchlists.rb +139 -0
- data/lib/tradier/balance.rb +17 -0
- data/lib/tradier/base.rb +76 -0
- data/lib/tradier/calendar.rb +23 -0
- data/lib/tradier/client.rb +89 -0
- data/lib/tradier/clock.rb +21 -0
- data/lib/tradier/configurable.rb +76 -0
- data/lib/tradier/core_ext/enumerable.rb +9 -0
- data/lib/tradier/default.rb +75 -0
- data/lib/tradier/error.rb +34 -0
- data/lib/tradier/error/bad_gateway.rb +11 -0
- data/lib/tradier/error/bad_request.rb +10 -0
- data/lib/tradier/error/client_error.rb +28 -0
- data/lib/tradier/error/configuration_error.rb +6 -0
- data/lib/tradier/error/decode_error.rb +9 -0
- data/lib/tradier/error/forbidden.rb +10 -0
- data/lib/tradier/error/gateway_timeout.rb +11 -0
- data/lib/tradier/error/internal_server_error.rb +11 -0
- data/lib/tradier/error/not_acceptable.rb +10 -0
- data/lib/tradier/error/not_found.rb +10 -0
- data/lib/tradier/error/raise_error.rb +31 -0
- data/lib/tradier/error/server_error.rb +28 -0
- data/lib/tradier/error/service_unavailable.rb +11 -0
- data/lib/tradier/error/too_many_requests.rb +12 -0
- data/lib/tradier/error/unauthorized.rb +10 -0
- data/lib/tradier/error/unprocessable_entity.rb +10 -0
- data/lib/tradier/event.rb +8 -0
- data/lib/tradier/history.rb +20 -0
- data/lib/tradier/option_quote.rb +37 -0
- data/lib/tradier/order.rb +14 -0
- data/lib/tradier/position.rb +7 -0
- data/lib/tradier/profile.rb +14 -0
- data/lib/tradier/quote.rb +12 -0
- data/lib/tradier/response/parse_json.rb +25 -0
- data/lib/tradier/response/raise_error.rb +31 -0
- data/lib/tradier/symbol.rb +147 -0
- data/lib/tradier/timesales.rb +11 -0
- data/lib/tradier/version.rb +3 -0
- data/lib/tradier/watchlist.rb +16 -0
- data/lib/tradier/watchlist_item.rb +17 -0
- data/spec/fixtures/account_balances.json +28 -0
- data/spec/fixtures/account_gainloss.json +18 -0
- data/spec/fixtures/account_history.json +96 -0
- data/spec/fixtures/account_orders.json +833 -0
- data/spec/fixtures/account_positions.json +22 -0
- data/spec/fixtures/calendar.json +400 -0
- data/spec/fixtures/chain.json +2972 -0
- data/spec/fixtures/clock.json +10 -0
- data/spec/fixtures/expirations.json +18 -0
- data/spec/fixtures/history.json +2086 -0
- data/spec/fixtures/option_quote.json +14 -0
- data/spec/fixtures/option_quotes.json +26 -0
- data/spec/fixtures/order.json +1 -0
- data/spec/fixtures/order_with_warnings.json +17 -0
- data/spec/fixtures/placed_order.json +6 -0
- data/spec/fixtures/quote.json +45 -0
- data/spec/fixtures/quotes.json +88 -0
- data/spec/fixtures/session.json +6 -0
- data/spec/fixtures/strikes.json +173 -0
- data/spec/fixtures/timesales.json +2956 -0
- data/spec/fixtures/user_balances.json +118 -0
- data/spec/fixtures/user_gainloss.json +6775 -0
- data/spec/fixtures/user_history.json +101 -0
- data/spec/fixtures/user_orders.json +57 -0
- data/spec/fixtures/user_positions.json +50 -0
- data/spec/fixtures/user_profile.json +103 -0
- data/spec/fixtures/watchlist.json +30 -0
- data/spec/fixtures/watchlist_item.json +6 -0
- data/spec/fixtures/watchlists.json +18 -0
- data/spec/spec_helper.rb +64 -0
- data/spec/tradier/account_spec.rb +76 -0
- data/spec/tradier/api/accounts_spec.rb +195 -0
- data/spec/tradier/api/markets_spec.rb +219 -0
- data/spec/tradier/api/orders_spec.rb +94 -0
- data/spec/tradier/api/utils/expiration_spec.rb +8 -0
- data/spec/tradier/api/watchlists_spec.rb +164 -0
- data/spec/tradier/balance_spec.rb +4 -0
- data/spec/tradier/base_spec.rb +11 -0
- data/spec/tradier/calendar_spec.rb +27 -0
- data/spec/tradier/client_spec.rb +125 -0
- data/spec/tradier/clock_spec.rb +73 -0
- data/spec/tradier/error/client_error_spec.rb +22 -0
- data/spec/tradier/error/server_error_spec.rb +22 -0
- data/spec/tradier/error_spec.rb +26 -0
- data/spec/tradier/option_quote_spec.rb +61 -0
- data/spec/tradier/order_spec.rb +43 -0
- data/spec/tradier/position_spec.rb +4 -0
- data/spec/tradier/profile_spec.rb +28 -0
- data/spec/tradier/quote_spec.rb +29 -0
- data/spec/tradier/symbol_spec.rb +54 -0
- data/spec/tradier/watchlist_item_spec.rb +48 -0
- data/spec/tradier/watchlist_spec.rb +35 -0
- data/spec/tradier_spec.rb +105 -0
- data/tradier.gemspec +30 -0
- metadata +302 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'tradier/error'
|
2
|
+
|
3
|
+
module Tradier
|
4
|
+
class Error
|
5
|
+
# Raised when Tradier returns a 4xx HTTP status code or there's an error in Faraday
|
6
|
+
class ClientError < Tradier::Error
|
7
|
+
MESSAGE = "Client Error"
|
8
|
+
|
9
|
+
# Create a new error from an HTTP environment
|
10
|
+
#
|
11
|
+
# @param response [Hash]
|
12
|
+
# @return [Tradier::Error]
|
13
|
+
def self.from_response(response={})
|
14
|
+
new(response[:body], response[:response_headers])
|
15
|
+
end
|
16
|
+
|
17
|
+
# Initializes a new ServerError object
|
18
|
+
#
|
19
|
+
# @param message [String]
|
20
|
+
# @param response_headers [Hash]
|
21
|
+
# @return [Tradier::Error::ServerError]
|
22
|
+
def initialize(message=nil, response_headers={})
|
23
|
+
super((message || self.class.const_get(:MESSAGE)), response_headers)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'tradier/error/server_error'
|
2
|
+
|
3
|
+
module Tradier
|
4
|
+
class Error
|
5
|
+
# Raised when Tradier returns the HTTP status code 504
|
6
|
+
class GatewayTimeout < Tradier::Error::ServerError
|
7
|
+
HTTP_STATUS_CODE = 504
|
8
|
+
MESSAGE = "The Tradier servers are up, but the request couldn't be serviced due to some failure within our stack. Try again later."
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'tradier/error/server_error'
|
2
|
+
|
3
|
+
module Tradier
|
4
|
+
class Error
|
5
|
+
# Raised when Tradier returns the HTTP status code 500
|
6
|
+
class InternalServerError < Tradier::Error::ServerError
|
7
|
+
HTTP_STATUS_CODE = 500
|
8
|
+
MESSAGE = "Something is technically wrong."
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'tradier/error/bad_gateway'
|
3
|
+
require 'tradier/error/bad_request'
|
4
|
+
require 'tradier/error/forbidden'
|
5
|
+
require 'tradier/error/gateway_timeout'
|
6
|
+
require 'tradier/error/internal_server_error'
|
7
|
+
require 'tradier/error/not_acceptable'
|
8
|
+
require 'tradier/error/not_found'
|
9
|
+
require 'tradier/error/service_unavailable'
|
10
|
+
require 'tradier/error/too_many_requests'
|
11
|
+
require 'tradier/error/unauthorized'
|
12
|
+
require 'tradier/error/unprocessable_entity'
|
13
|
+
|
14
|
+
module Tradier
|
15
|
+
module Response
|
16
|
+
class RaiseError < Faraday::Response::Middleware
|
17
|
+
|
18
|
+
def on_complete(env)
|
19
|
+
status_code = env[:status].to_i
|
20
|
+
error_class = @klass.errors[status_code]
|
21
|
+
raise error_class.from_response(env) if error_class
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(app, klass)
|
25
|
+
@klass = klass
|
26
|
+
super(app)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'tradier/error'
|
2
|
+
|
3
|
+
module Tradier
|
4
|
+
class Error
|
5
|
+
# Raised when Tradier returns a 5xx HTTP status code
|
6
|
+
class ServerError < Tradier::Error
|
7
|
+
MESSAGE = "Server Error"
|
8
|
+
|
9
|
+
# Create a new error from an HTTP environment
|
10
|
+
#
|
11
|
+
# @param response [Hash]
|
12
|
+
# @return [Tradier::Error]
|
13
|
+
def self.from_response(response={})
|
14
|
+
new(response[:body], response[:response_headers])
|
15
|
+
end
|
16
|
+
|
17
|
+
# Initializes a new ServerError object
|
18
|
+
#
|
19
|
+
# @param message [String]
|
20
|
+
# @param response_headers [Hash]
|
21
|
+
# @return [Tradier::Error::ServerError]
|
22
|
+
def initialize(message=nil, response_headers={})
|
23
|
+
super((message || self.class.const_get(:MESSAGE)), response_headers)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'tradier/error/server_error'
|
2
|
+
|
3
|
+
module Tradier
|
4
|
+
class Error
|
5
|
+
# Raised when Tradier returns the HTTP status code 503
|
6
|
+
class ServiceUnavailable < Tradier::Error::ServerError
|
7
|
+
HTTP_STATUS_CODE = 503
|
8
|
+
MESSAGE = "Tradier is over capacity."
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'tradier/error/client_error'
|
2
|
+
|
3
|
+
module Tradier
|
4
|
+
class Error
|
5
|
+
# Raised when Tradier returns the HTTP status code 429
|
6
|
+
class TooManyRequests < Tradier::Error::ClientError
|
7
|
+
HTTP_STATUS_CODE = 429
|
8
|
+
end
|
9
|
+
EnhanceYourCalm = TooManyRequests
|
10
|
+
RateLimited = TooManyRequests
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'tradier/base'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
module Tradier
|
5
|
+
class History < Tradier::Base
|
6
|
+
|
7
|
+
attr_reader :open , :close, :high, :low, :volume
|
8
|
+
|
9
|
+
def date
|
10
|
+
return unless @date || @attrs[:date]
|
11
|
+
|
12
|
+
@date ||= Date.parse(@attrs[:date])
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.from_response(body={})
|
16
|
+
new(body)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'tradier/base'
|
2
|
+
require 'tradier/symbol'
|
3
|
+
|
4
|
+
module Tradier
|
5
|
+
class OptionQuote < Tradier::Base
|
6
|
+
|
7
|
+
attr_reader :symbol, :strike, :last, :bid, :ask, :change, :open_interest, :bid_size, :ask_size, :volume
|
8
|
+
|
9
|
+
def put?
|
10
|
+
parsed_symbol.put?
|
11
|
+
end
|
12
|
+
|
13
|
+
def call?
|
14
|
+
parsed_symbol.call?
|
15
|
+
end
|
16
|
+
|
17
|
+
def underlier
|
18
|
+
parsed_symbol.underlier
|
19
|
+
end
|
20
|
+
|
21
|
+
def expiration_date
|
22
|
+
return unless @expiration_date || @attrs[:expiration_date]
|
23
|
+
|
24
|
+
@expiration_date ||= Date.parse(@attrs[:expiration_date])
|
25
|
+
end
|
26
|
+
|
27
|
+
def ==(option_quote)
|
28
|
+
self.symbol == option_quote.symbol
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def parsed_symbol
|
34
|
+
@parsed ||= Tradier::Symbol.parse(@attrs[:symbol])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'tradier/base'
|
2
|
+
|
3
|
+
module Tradier
|
4
|
+
class Order < Tradier::Base
|
5
|
+
attr_reader :id, :order_id, :type, :price, :symbol, :side, :quantity, \
|
6
|
+
:status, :time_in_force, :option_type, :exec_inst, :exec_status, :extended_hours, \
|
7
|
+
:trailing_limit_type, :trailing_stop_type, :request_date, :response_date, :num_legs, \
|
8
|
+
:errors, :result
|
9
|
+
|
10
|
+
def self.from_response(body={})
|
11
|
+
new(body[:order] || body)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'tradier/base'
|
2
|
+
|
3
|
+
module Tradier
|
4
|
+
class Profile < Tradier::Base
|
5
|
+
|
6
|
+
def name
|
7
|
+
@name ||= @attrs[:profile] && @attrs[:profile][:name]
|
8
|
+
end
|
9
|
+
|
10
|
+
def accounts
|
11
|
+
@accounts ||= @attrs[:profile] && [@attrs[:profile][:account]].flatten.map { |a| Tradier::Account.new(a); }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'multi_json'
|
3
|
+
|
4
|
+
module Tradier
|
5
|
+
module Response
|
6
|
+
class ParseJson < Faraday::Response::Middleware
|
7
|
+
|
8
|
+
def parse(body)
|
9
|
+
case body
|
10
|
+
when /\A^\s*$\z/, nil
|
11
|
+
nil
|
12
|
+
else
|
13
|
+
MultiJson.decode(body, :symbolize_keys => true)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def on_complete(env)
|
18
|
+
if respond_to?(:parse)
|
19
|
+
env[:body] = parse(env[:body]) unless [204, 301, 302, 304].include?(env[:status])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'tradier/error/bad_gateway'
|
3
|
+
require 'tradier/error/bad_request'
|
4
|
+
require 'tradier/error/forbidden'
|
5
|
+
require 'tradier/error/gateway_timeout'
|
6
|
+
require 'tradier/error/internal_server_error'
|
7
|
+
require 'tradier/error/not_acceptable'
|
8
|
+
require 'tradier/error/not_found'
|
9
|
+
require 'tradier/error/service_unavailable'
|
10
|
+
require 'tradier/error/too_many_requests'
|
11
|
+
require 'tradier/error/unauthorized'
|
12
|
+
require 'tradier/error/unprocessable_entity'
|
13
|
+
|
14
|
+
module Tradier
|
15
|
+
module Response
|
16
|
+
class RaiseError < Faraday::Response::Middleware
|
17
|
+
|
18
|
+
def on_complete(env)
|
19
|
+
status_code = env[:status].to_i
|
20
|
+
error_class = @klass.errors[status_code]
|
21
|
+
raise error_class.from_response(env) if error_class
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(app, klass)
|
25
|
+
@klass = klass
|
26
|
+
super(app)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
module Tradier
|
2
|
+
class Symbol
|
3
|
+
|
4
|
+
OPTION_SYMBOL_REGEXP = /([a-z]+)(\d?)(\d{2})(\d{2})(\d{2})(C|P)(\d{5})(\d{3})/ix
|
5
|
+
|
6
|
+
MONTHS = {
|
7
|
+
1 => 'Jan',
|
8
|
+
2 => 'Feb',
|
9
|
+
3 => 'Mar',
|
10
|
+
4 => 'Apr',
|
11
|
+
5 => 'May',
|
12
|
+
6 => 'Jun',
|
13
|
+
7 => 'Jul',
|
14
|
+
8 => 'Aug',
|
15
|
+
9 => 'Sep',
|
16
|
+
10 => 'Oct',
|
17
|
+
11 => 'Nov',
|
18
|
+
12 => 'Dec'
|
19
|
+
}
|
20
|
+
|
21
|
+
attr_accessor :symbol, :adjustment, :month, :day, :year, :option_type, :dollars, :cents
|
22
|
+
|
23
|
+
def initialize(symbol)
|
24
|
+
@symbol = symbol
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.parse(symbol)
|
28
|
+
symbol.strip!
|
29
|
+
|
30
|
+
sym = if match = symbol.match(OPTION_SYMBOL_REGEXP)
|
31
|
+
match = match.to_a
|
32
|
+
match.shift
|
33
|
+
|
34
|
+
s = Tradier::Symbol.new(match[0])
|
35
|
+
s.adjustment = match[1]
|
36
|
+
s.year = match[2]
|
37
|
+
s.month = match[3]
|
38
|
+
s.day = match[4]
|
39
|
+
s.option_type = match[5]
|
40
|
+
s.dollars = match[6]
|
41
|
+
s.cents = match[7]
|
42
|
+
s
|
43
|
+
elsif symbol =~ /\d/
|
44
|
+
nil
|
45
|
+
elsif symbol =~ /[a-zA-Z]+/
|
46
|
+
Tradier::Symbol.new(symbol)
|
47
|
+
else
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
|
51
|
+
raise ArgumentError.new("Invalid symbol: #{symbol}") unless sym && sym.valid?
|
52
|
+
sym
|
53
|
+
end
|
54
|
+
|
55
|
+
def adjustment?
|
56
|
+
adjustment && adjustment.to_i >= 1
|
57
|
+
end
|
58
|
+
|
59
|
+
def put?
|
60
|
+
return unless option_type
|
61
|
+
option_type.upcase == 'P'
|
62
|
+
end
|
63
|
+
|
64
|
+
def call?
|
65
|
+
return unless option_type
|
66
|
+
option_type.upcase == 'C'
|
67
|
+
end
|
68
|
+
|
69
|
+
def option?
|
70
|
+
put? || call?
|
71
|
+
end
|
72
|
+
|
73
|
+
def equity?
|
74
|
+
!option?
|
75
|
+
end
|
76
|
+
|
77
|
+
def valid?
|
78
|
+
equity? || (option? && valid_month?)
|
79
|
+
end
|
80
|
+
|
81
|
+
def to_s
|
82
|
+
s = equity? ? underlier : "#{underlier}#{@adjustment if adjustment?}#{@year}#{occ_month}#{occ_day}#{@option_type}#{occ_dollars}#{occ_cents}"
|
83
|
+
s.upcase
|
84
|
+
end
|
85
|
+
|
86
|
+
def description
|
87
|
+
equity? ? underlier : "#{underlier} #{expiration_description} $#{strike_description} #{option_type_description}"
|
88
|
+
end
|
89
|
+
|
90
|
+
def underlier
|
91
|
+
@symbol
|
92
|
+
end
|
93
|
+
|
94
|
+
def occ_month
|
95
|
+
@month.to_i >= 10 ? @month : "0#{@month.to_i}"
|
96
|
+
end
|
97
|
+
|
98
|
+
def occ_day
|
99
|
+
@day.to_i >= 10 ? @day : "0#{@day.to_i}"
|
100
|
+
end
|
101
|
+
|
102
|
+
def occ_cents
|
103
|
+
case @cents.size
|
104
|
+
when 3 then @cents
|
105
|
+
when 2 then "#{@cents}0"
|
106
|
+
when 1 then "#{@cents}00"
|
107
|
+
when 0 then "000"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def occ_dollars
|
112
|
+
return @dollars if @dollars.size == 5
|
113
|
+
('0' * (5 - @dollars.size)) + @dollars.to_s
|
114
|
+
end
|
115
|
+
|
116
|
+
def expiration_description
|
117
|
+
"#{MONTHS[@month.to_i]} #{@day} #{year_description}"
|
118
|
+
end
|
119
|
+
|
120
|
+
def year_description
|
121
|
+
"20#{@year}"
|
122
|
+
end
|
123
|
+
|
124
|
+
def strike_description
|
125
|
+
"#{@dollars.to_i}.#{occ_cents[0,2]}"
|
126
|
+
end
|
127
|
+
|
128
|
+
def option_type_description
|
129
|
+
put? ? 'PUT' : 'CALL'
|
130
|
+
end
|
131
|
+
|
132
|
+
def expiration
|
133
|
+
Date.new(year_description.to_i, @month.to_i, @day.to_i)
|
134
|
+
end
|
135
|
+
|
136
|
+
def strike_price
|
137
|
+
"#{@dollars}.#{@cents}".to_f
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
def valid_month?
|
143
|
+
@month.to_i <= 12 && @month.to_i >= 1
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|