schwab_rb 0.3.2 → 0.3.3

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec_status +193 -193
  3. data/.rubocop.yml +2 -0
  4. data/examples/fetch_account_numbers.rb +1 -1
  5. data/examples/fetch_user_preferences.rb +1 -1
  6. data/lib/schwab_rb/auth/auth_context.rb +19 -15
  7. data/lib/schwab_rb/auth/init_client_easy.rb +36 -32
  8. data/lib/schwab_rb/auth/init_client_login.rb +165 -161
  9. data/lib/schwab_rb/auth/init_client_token_file.rb +27 -23
  10. data/lib/schwab_rb/auth/login_flow_server.rb +39 -36
  11. data/lib/schwab_rb/auth/token.rb +25 -21
  12. data/lib/schwab_rb/auth/token_manager.rb +88 -84
  13. data/lib/schwab_rb/clients/async_client.rb +2 -0
  14. data/lib/schwab_rb/clients/base_client.rb +2 -0
  15. data/lib/schwab_rb/configuration.rb +2 -0
  16. data/lib/schwab_rb/constants.rb +6 -4
  17. data/lib/schwab_rb/data_objects/account_numbers.rb +1 -0
  18. data/lib/schwab_rb/data_objects/market_hours.rb +1 -1
  19. data/lib/schwab_rb/data_objects/market_movers.rb +1 -0
  20. data/lib/schwab_rb/data_objects/option_expiration_chain.rb +1 -1
  21. data/lib/schwab_rb/data_objects/order_preview.rb +21 -49
  22. data/lib/schwab_rb/data_objects/price_history.rb +1 -1
  23. data/lib/schwab_rb/orders/builder.rb +162 -158
  24. data/lib/schwab_rb/orders/destination.rb +2 -0
  25. data/lib/schwab_rb/orders/duration.rb +14 -8
  26. data/lib/schwab_rb/orders/equity_instructions.rb +2 -0
  27. data/lib/schwab_rb/orders/errors.rb +2 -0
  28. data/lib/schwab_rb/orders/instruments.rb +2 -0
  29. data/lib/schwab_rb/orders/option_instructions.rb +2 -0
  30. data/lib/schwab_rb/orders/order.rb +2 -0
  31. data/lib/schwab_rb/orders/price_link_basis.rb +2 -0
  32. data/lib/schwab_rb/orders/price_link_type.rb +2 -0
  33. data/lib/schwab_rb/orders/session.rb +16 -10
  34. data/lib/schwab_rb/orders/special_instruction.rb +2 -0
  35. data/lib/schwab_rb/orders/stop_price_link_basis.rb +2 -0
  36. data/lib/schwab_rb/orders/stop_price_link_type.rb +2 -0
  37. data/lib/schwab_rb/orders/stop_type.rb +2 -0
  38. data/lib/schwab_rb/orders/tax_lot_method.rb +2 -0
  39. data/lib/schwab_rb/utils/enum_enforcer.rb +3 -4
  40. data/lib/schwab_rb/utils/logger.rb +3 -1
  41. data/lib/schwab_rb/utils/redactor.rb +3 -5
  42. data/lib/schwab_rb/version.rb +1 -1
  43. metadata +2 -2
@@ -1,201 +1,205 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "../utils/enum_enforcer"
2
4
 
3
- module SchwabRb::Orders
4
- class Builder
5
- # Helper class to create arbitrarily complex orders. Note this class simply
6
- # implements the order schema defined in the `documentation
7
- # <https://developer.schwabmeritrade.com/account-access/apis/post/accounts/
8
- # %7BaccountId%7D/orders-0>`__, with no attempts to validate the result.
9
- # Orders created using this class may be rejected or may never fill. Use at
10
- # your own risk.
11
-
12
- include EnumEnforcer
13
-
14
- class << self
15
- def build(obj)
16
- case obj
17
- when String, Integer, Float
18
- obj
19
- when Hash
20
- obj.each_with_object({}) do |(key, val), acc|
21
- acc[camel_case(key)] = build(val)
5
+ module SchwabRb
6
+ module Orders
7
+ class Builder
8
+ # Helper class to create arbitrarily complex orders. Note this class simply
9
+ # implements the order schema defined in the `documentation
10
+ # <https://developer.schwabmeritrade.com/account-access/apis/post/accounts/
11
+ # %7BaccountId%7D/orders-0>`__, with no attempts to validate the result.
12
+ # Orders created using this class may be rejected or may never fill. Use at
13
+ # your own risk.
14
+
15
+ include EnumEnforcer
16
+
17
+ class << self
18
+ def build(obj)
19
+ case obj
20
+ when String, Integer, Float
21
+ obj
22
+ when Hash
23
+ obj.each_with_object({}) do |(key, val), acc|
24
+ acc[camel_case(key)] = build(val)
25
+ end
26
+ when Array
27
+ obj.map { |i| build(i) }
28
+ else
29
+ ret = {}
30
+ obj.instance_variables.each do |var|
31
+ value = obj.instance_variable_get(var)
32
+ next if value.nil?
33
+
34
+ name = var.to_s[1..]
35
+ ret[camel_case(name)] = build(value)
36
+ end
37
+ ret
22
38
  end
23
- when Array
24
- obj.map { |i| build(i) }
25
- else
26
- ret = {}
27
- obj.instance_variables.each do |var|
28
- value = obj.instance_variable_get(var)
29
- next if value.nil?
39
+ end
30
40
 
31
- name = var.to_s[1..-1]
32
- ret[camel_case(name)] = build(value)
33
- end
34
- ret
41
+ def camel_case(snake_str)
42
+ camel_case_str = snake_str.split("_").map(&:capitalize).join
43
+ camel_case_str[0].downcase + camel_case_str[1..]
35
44
  end
36
45
  end
37
46
 
38
- def camel_case(snake_str)
39
- camel_case_str = snake_str.split("_").map(&:capitalize).join
40
- camel_case_str[0].downcase + camel_case_str[1..]
47
+ def initialize(enforce_enums: true)
48
+ @session = nil
49
+ @account_number = nil
50
+ @duration = nil
51
+ @order_type = nil
52
+ @complex_order_strategy_type = nil
53
+ @quantity = nil
54
+ @destination_link_name = nil
55
+ @stop_price = nil
56
+ @stop_price_link_basis = nil
57
+ @stop_price_link_type = nil
58
+ @stop_price_offset = nil
59
+ @stop_type = nil
60
+ @price_link_basis = nil
61
+ @price_link_type = nil
62
+ @price = nil
63
+ @order_leg_collection = nil
64
+ @activation_price = nil
65
+ @special_instruction = nil
66
+ @order_strategy_type = nil
67
+ @child_order_strategies = nil
41
68
  end
42
- end
43
69
 
44
- def initialize(enforce_enums: true)
45
- @session = nil
46
- @account_number = nil
47
- @duration = nil
48
- @order_type = nil
49
- @complex_order_strategy_type = nil
50
- @quantity = nil
51
- @destination_link_name = nil
52
- @stop_price = nil
53
- @stop_price_link_basis = nil
54
- @stop_price_link_type = nil
55
- @stop_price_offset = nil
56
- @stop_type = nil
57
- @price_link_basis = nil
58
- @price_link_type = nil
59
- @price = nil
60
- @order_leg_collection = nil
61
- @activation_price = nil
62
- @special_instruction = nil
63
- @order_strategy_type = nil
64
- @child_order_strategies = nil
65
- end
70
+ def set_session(session)
71
+ @session = convert_enum(session, SchwabRb::Orders::Session)
72
+ end
66
73
 
67
- def set_session(session)
68
- @session = convert_enum(session, SchwabRb::Orders::Session)
69
- end
74
+ def clear_session
75
+ @session = nil
76
+ end
70
77
 
71
- def clear_session
72
- @session = nil
73
- end
78
+ def set_account_number(account_number)
79
+ @account_number = account_number
80
+ end
74
81
 
75
- def set_account_number(account_number)
76
- @account_number = account_number
77
- end
82
+ def clear_account_number
83
+ @account_number = nil
84
+ end
78
85
 
79
- def clear_account_number
80
- @account_number = nil
81
- end
86
+ def set_duration(duration)
87
+ @duration = convert_enum(duration, SchwabRb::Orders::Duration)
88
+ end
82
89
 
83
- def set_duration(duration)
84
- @duration = convert_enum(duration, SchwabRb::Orders::Duration)
85
- end
90
+ def clear_duration
91
+ @duration = nil
92
+ end
86
93
 
87
- def clear_duration
88
- @duration = nil
89
- end
94
+ def set_order_type(order_type)
95
+ @order_type = convert_enum(order_type, SchwabRb::Order::Types)
96
+ end
90
97
 
91
- def set_order_type(order_type)
92
- @order_type = convert_enum(order_type, SchwabRb::Order::Types)
93
- end
98
+ def clear_order_type
99
+ @order_type = nil
100
+ end
94
101
 
95
- def clear_order_type
96
- @order_type = nil
97
- end
102
+ def set_quantity(quantity)
103
+ raise "quantity must be positive" if quantity <= 0
98
104
 
99
- def set_quantity(quantity)
100
- raise "quantity must be positive" if quantity <= 0
105
+ @quantity = quantity
106
+ end
101
107
 
102
- @quantity = quantity
103
- end
108
+ def clear_quantity
109
+ @quantity = nil
110
+ end
104
111
 
105
- def clear_quantity
106
- @quantity = nil
107
- end
112
+ def set_price(price)
113
+ @price = price
114
+ end
108
115
 
109
- def set_price(price)
110
- @price = price
111
- end
116
+ def clear_price
117
+ @price = nil
118
+ end
112
119
 
113
- def clear_price
114
- @price = nil
115
- end
120
+ def set_stop_price(stop_price)
121
+ @stop_price = stop_price.is_a?(String) ? stop_price : truncate_float(stop_price)
122
+ end
116
123
 
117
- def set_stop_price(stop_price)
118
- @stop_price = stop_price.is_a?(String) ? stop_price : truncate_float(stop_price)
119
- end
124
+ def copy_stop_price(stop_price)
125
+ @stop_price = stop_price
126
+ end
120
127
 
121
- def copy_stop_price(stop_price)
122
- @stop_price = stop_price
123
- end
128
+ def clear_stop_price
129
+ @stop_price = nil
130
+ end
124
131
 
125
- def clear_stop_price
126
- @stop_price = nil
127
- end
132
+ def set_order_strategy_type(order_strategy_type = "SINGLE")
133
+ @order_strategy_type = order_strategy_type
134
+ end
128
135
 
129
- def set_order_strategy_type(order_strategy_type = "SINGLE")
130
- @order_strategy_type = order_strategy_type
131
- end
136
+ def clear_order_strategy_type
137
+ @order_strategy_type = nil
138
+ end
132
139
 
133
- def clear_order_strategy_type
134
- @order_strategy_type = nil
135
- end
140
+ def set_complex_order_strategy_type(complex_order_strategy_type)
141
+ @complex_order_strategy_type = convert_enum(
142
+ complex_order_strategy_type,
143
+ SchwabRb::Order::ComplexOrderStrategyTypes
144
+ )
145
+ end
136
146
 
137
- def set_complex_order_strategy_type(complex_order_strategy_type)
138
- @complex_order_strategy_type = convert_enum(
139
- complex_order_strategy_type,
140
- SchwabRb::Order::ComplexOrderStrategyTypes
141
- )
142
- end
147
+ def clear_complex_order_strategy_type
148
+ @complex_order_strategy_type = nil
149
+ end
143
150
 
144
- def clear_complex_order_strategy_type
145
- @complex_order_strategy_type = nil
146
- end
151
+ def add_child_order_strategy(child_order_strategy)
152
+ raise "child order must be OrderBuilder or Hash" unless [Builder, Hash].any? do |type|
153
+ child_order_strategy.is_a? type
154
+ end
147
155
 
148
- def add_child_order_strategy(child_order_strategy)
149
- raise "child order must be OrderBuilder or Hash" unless [Builder, Hash].any? do |type|
150
- child_order_strategy.is_a? type
156
+ @child_order_strategies ||= []
157
+ @child_order_strategies << child_order_strategy
151
158
  end
152
159
 
153
- @child_order_strategies ||= []
154
- @child_order_strategies << child_order_strategy
155
- end
156
-
157
- def clear_child_order_strategies
158
- @child_order_strategies = nil
159
- end
160
+ def clear_child_order_strategies
161
+ @child_order_strategies = nil
162
+ end
160
163
 
161
- def add_option_leg(instruction, symbol, quantity)
162
- raise "quantity must be positive" if quantity <= 0
164
+ def add_option_leg(instruction, symbol, quantity)
165
+ raise "quantity must be positive" if quantity <= 0
163
166
 
164
- @order_leg_collection ||= []
165
- @order_leg_collection << {
166
- "instruction" => convert_enum(instruction, SchwabRb::Orders::OptionInstructions),
167
- "instrument" => SchwabRb::Orders::OptionInstrument.new(symbol),
168
- "quantity" => quantity
169
- }
170
- end
167
+ @order_leg_collection ||= []
168
+ @order_leg_collection << {
169
+ "instruction" => convert_enum(instruction, SchwabRb::Orders::OptionInstructions),
170
+ "instrument" => SchwabRb::Orders::OptionInstrument.new(symbol),
171
+ "quantity" => quantity
172
+ }
173
+ end
171
174
 
172
- def add_equity_leg(instruction, symbol, quantity)
173
- raise "quantity must be positive" if quantity <= 0
175
+ def add_equity_leg(instruction, symbol, quantity)
176
+ raise "quantity must be positive" if quantity <= 0
174
177
 
175
- @order_leg_collection ||= []
176
- @order_leg_collection << {
177
- "instruction" => convert_enum(instruction, SchwabRb::Orders::EquityInstructions),
178
- "instrument" => SchwabRb::Orders::EquityInstrument.new(symbol),
179
- "quantity" => quantity
180
- }
181
- end
178
+ @order_leg_collection ||= []
179
+ @order_leg_collection << {
180
+ "instruction" => convert_enum(instruction, SchwabRb::Orders::EquityInstructions),
181
+ "instrument" => SchwabRb::Orders::EquityInstrument.new(symbol),
182
+ "quantity" => quantity
183
+ }
184
+ end
182
185
 
183
- def clear_order_legs
184
- @order_leg_collection = nil
185
- self
186
- end
186
+ def clear_order_legs
187
+ @order_leg_collection = nil
188
+ self
189
+ end
187
190
 
188
- def build
189
- Builder.build(self)
190
- end
191
+ def build
192
+ Builder.build(self)
193
+ end
191
194
 
192
- private
195
+ private
193
196
 
194
- def truncate_float(flt)
195
- if flt.abs < 1 && flt != 0.0
196
- format("%.4f", (flt * 10_000).to_i / 10_000.0)
197
- else
198
- format("%.2f", (flt * 100).to_i / 100.0)
197
+ def truncate_float(flt)
198
+ if flt.abs < 1 && flt != 0.0
199
+ format("%.4f", (flt * 10_000).to_i / 10_000.0)
200
+ else
201
+ format("%.2f", (flt * 100).to_i / 100.0)
202
+ end
199
203
  end
200
204
  end
201
205
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SchwabRb
2
4
  module Orders
3
5
  module Destination
@@ -1,9 +1,15 @@
1
- module SchwabRb::Orders::Duration
2
- DAY = "DAY"
3
- GOOD_TILL_CANCEL = "GOOD_TILL_CANCEL"
4
- FILL_OR_KILL = "FILL_OR_KILL"
5
- IMMEDIATE_OR_CANCEL = "IMMEDIATE_OR_CANCEL"
6
- END_OF_WEEK = "END_OF_WEEK"
7
- END_OF_MONTH = "END_OF_MONTH"
8
- NEXT_END_OF_MONTH = "NEXT_END_OF_MONTH"
1
+ # frozen_string_literal: true
2
+
3
+ module SchwabRb
4
+ module Orders
5
+ module Duration
6
+ DAY = "DAY"
7
+ GOOD_TILL_CANCEL = "GOOD_TILL_CANCEL"
8
+ FILL_OR_KILL = "FILL_OR_KILL"
9
+ IMMEDIATE_OR_CANCEL = "IMMEDIATE_OR_CANCEL"
10
+ END_OF_WEEK = "END_OF_WEEK"
11
+ END_OF_MONTH = "END_OF_MONTH"
12
+ NEXT_END_OF_MONTH = "NEXT_END_OF_MONTH"
13
+ end
14
+ end
9
15
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SchwabRb
2
4
  module Orders
3
5
  module EquityInstructions
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SchwabRb
2
4
  module Orders
3
5
  class InvalidOrder < StandardError; end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SchwabRb
2
4
  module Orders
3
5
  class Instrument
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SchwabRb
2
4
  module Orders
3
5
  module OptionInstructions
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SchwabRb
2
4
  class Order
3
5
  module Statuses
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SchwabRb
2
4
  module Orders
3
5
  module PriceLinkBasis
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SchwabRb
2
4
  module Orders
3
5
  module PriceLinkType
@@ -1,14 +1,20 @@
1
- module SchwabRb::Orders::Session
2
- # Normal market hours, from 9:30am to 4:00pm Eastern.
3
- NORMAL = "NORMAL"
1
+ # frozen_string_literal: true
4
2
 
5
- # Premarket session, from 8:00am to 9:30am Eastern.
6
- AM = "AM"
3
+ module SchwabRb
4
+ module Orders
5
+ module Session
6
+ # Normal market hours, from 9:30am to 4:00pm Eastern.
7
+ NORMAL = "NORMAL"
7
8
 
8
- # After-market session, from 4:00pm to 8:00pm Eastern.
9
- PM = "PM"
9
+ # Premarket session, from 8:00am to 9:30am Eastern.
10
+ AM = "AM"
10
11
 
11
- # Orders are active during all trading sessions except the overnight
12
- # session. This is the union of ``NORMAL``, ``AM``, and ``PM``.
13
- SEAMLESS = "SEAMLESS"
12
+ # After-market session, from 4:00pm to 8:00pm Eastern.
13
+ PM = "PM"
14
+
15
+ # Orders are active during all trading sessions except the overnight
16
+ # session. This is the union of ``NORMAL``, ``AM``, and ``PM``.
17
+ SEAMLESS = "SEAMLESS"
18
+ end
19
+ end
14
20
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SchwabRb
2
4
  module Orders
3
5
  module SpecialInstruction
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SchwabRb
2
4
  module Orders
3
5
  module StopPriceLinkBasis
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SchwabRb
2
4
  module Orders
3
5
  module StopPriceLinkType
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SchwabRb
2
4
  module Orders
3
5
  module StopType
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SchwabRb
2
4
  module Orders
3
5
  module TaxLotMethod
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module EnumEnforcer
2
4
  def enforce_enums?
3
5
  @enforce_enums ||= false
@@ -17,10 +19,7 @@ module EnumEnforcer
17
19
  end
18
20
 
19
21
  if possible_members.any?
20
- possible_members_message = "Did you mean " +
21
- possible_members[0..-2].join(", ") +
22
- (possible_members.size > 1 ? " or " : "") +
23
- possible_members[-1].to_s + "? "
22
+ possible_members_message = "Did you mean #{possible_members[0..-2].join(', ')}#{possible_members.size > 1 ? ' or ' : ''}#{possible_members[-1]}? "
24
23
  end
25
24
  end
26
25
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "logger"
2
4
  require "fileutils"
3
5
 
@@ -25,7 +27,7 @@ module SchwabRb
25
27
  return null_logger if config.silence_output
26
28
  return null_logger unless config.should_create_logger?
27
29
 
28
- log_destination = config.effective_log_file || STDOUT
30
+ log_destination = config.effective_log_file || $stdout
29
31
 
30
32
  return null_logger if [:null, "/dev/null"].include?(log_destination)
31
33
 
@@ -4,9 +4,8 @@ require "json"
4
4
 
5
5
  module SchwabRb
6
6
  class Redactor
7
- # Patterns for account numbers and hashes that should be redacted
8
- ACCOUNT_NUMBER_PATTERN = /\b\d{8,12}\b/
9
- ACCOUNT_HASH_PATTERN = /\b[A-Z0-9]{32}\b/
7
+ ACCOUNT_NUMBER_PATTERN = /\b\d{8,12}\b/.freeze
8
+ ACCOUNT_HASH_PATTERN = /\b[A-Z0-9]{32}\b/.freeze
10
9
 
11
10
  # JSON keys that commonly contain sensitive account information
12
11
  SENSITIVE_KEYS = %w[
@@ -45,7 +44,7 @@ module SchwabRb
45
44
  end
46
45
 
47
46
  def self.redact_response_body(response)
48
- return unless response&.respond_to?(:body)
47
+ return unless response.respond_to?(:body)
49
48
 
50
49
  body = response.body
51
50
  return unless body
@@ -55,7 +54,6 @@ module SchwabRb
55
54
  parsed = JSON.parse(body)
56
55
  redact_data(parsed)
57
56
  elsif body.respond_to?(:read)
58
- # Handle IO-like objects
59
57
  content = body.read
60
58
  body.rewind if body.respond_to?(:rewind)
61
59
  parsed = JSON.parse(content)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SchwabRb
4
- VERSION = "0.3.2"
4
+ VERSION = "0.3.3"
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: schwab_rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joseph Platta
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-07-22 00:00:00.000000000 Z
10
+ date: 2025-07-28 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: async