schwab_rb 0.2.0 → 0.3.2

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/settings.local.json +9 -0
  3. data/.rspec_status +232 -203
  4. data/CLAUDE.md +137 -0
  5. data/README.md +3 -3
  6. data/examples/fetch_account_numbers.rb +12 -15
  7. data/examples/fetch_user_preferences.rb +16 -19
  8. data/lib/schwab_rb/account.rb +1 -1
  9. data/lib/schwab_rb/auth/auth_context.rb +1 -1
  10. data/lib/schwab_rb/auth/init_client_easy.rb +29 -31
  11. data/lib/schwab_rb/auth/init_client_login.rb +24 -21
  12. data/lib/schwab_rb/auth/login_flow_server.rb +2 -2
  13. data/lib/schwab_rb/auth/token.rb +1 -1
  14. data/lib/schwab_rb/auth/token_manager.rb +5 -7
  15. data/lib/schwab_rb/clients/async_client.rb +25 -27
  16. data/lib/schwab_rb/clients/base_client.rb +22 -16
  17. data/lib/schwab_rb/clients/client.rb +14 -14
  18. data/lib/schwab_rb/configuration.rb +4 -4
  19. data/lib/schwab_rb/data_objects/account.rb +3 -3
  20. data/lib/schwab_rb/data_objects/instrument.rb +1 -1
  21. data/lib/schwab_rb/data_objects/market_hours.rb +43 -33
  22. data/lib/schwab_rb/data_objects/market_movers.rb +98 -0
  23. data/lib/schwab_rb/data_objects/option.rb +2 -2
  24. data/lib/schwab_rb/data_objects/option_chain.rb +7 -7
  25. data/lib/schwab_rb/data_objects/option_expiration_chain.rb +26 -25
  26. data/lib/schwab_rb/data_objects/order.rb +7 -6
  27. data/lib/schwab_rb/data_objects/order_leg.rb +5 -5
  28. data/lib/schwab_rb/data_objects/order_preview.rb +13 -16
  29. data/lib/schwab_rb/data_objects/position.rb +4 -4
  30. data/lib/schwab_rb/data_objects/price_history.rb +27 -19
  31. data/lib/schwab_rb/data_objects/quote.rb +6 -6
  32. data/lib/schwab_rb/data_objects/transaction.rb +6 -6
  33. data/lib/schwab_rb/data_objects/user_preferences.rb +3 -3
  34. data/lib/schwab_rb/market_hours.rb +5 -5
  35. data/lib/schwab_rb/movers.rb +16 -16
  36. data/lib/schwab_rb/orders/builder.rb +5 -5
  37. data/lib/schwab_rb/orders/destination.rb +12 -12
  38. data/lib/schwab_rb/orders/duration.rb +7 -7
  39. data/lib/schwab_rb/orders/equity_instructions.rb +4 -4
  40. data/lib/schwab_rb/orders/instruments.rb +8 -8
  41. data/lib/schwab_rb/orders/price_link_basis.rb +9 -9
  42. data/lib/schwab_rb/orders/price_link_type.rb +3 -3
  43. data/lib/schwab_rb/orders/session.rb +4 -4
  44. data/lib/schwab_rb/orders/special_instruction.rb +3 -3
  45. data/lib/schwab_rb/orders/stop_price_link_basis.rb +9 -9
  46. data/lib/schwab_rb/orders/stop_price_link_type.rb +3 -3
  47. data/lib/schwab_rb/orders/stop_type.rb +5 -5
  48. data/lib/schwab_rb/orders/tax_lot_method.rb +7 -7
  49. data/lib/schwab_rb/price_history.rb +8 -8
  50. data/lib/schwab_rb/quote.rb +5 -5
  51. data/lib/schwab_rb/transaction.rb +15 -15
  52. data/lib/schwab_rb/utils/logger.rb +11 -15
  53. data/lib/schwab_rb/utils/redactor.rb +23 -25
  54. data/lib/schwab_rb/version.rb +1 -1
  55. data/lib/schwab_rb.rb +1 -0
  56. metadata +6 -2
@@ -10,6 +10,7 @@ require_relative "../data_objects/market_hours"
10
10
  require_relative "../data_objects/quote"
11
11
  require_relative "../data_objects/transaction"
12
12
  require_relative "../data_objects/order"
13
+ require_relative "../data_objects/market_movers"
13
14
 
14
15
  module SchwabRb
15
16
  class BaseClient
@@ -161,13 +162,9 @@ module SchwabRb
161
162
  # @param return_data_objects [Boolean] Whether to return data objects or raw JSON
162
163
  refresh_token_if_needed
163
164
 
164
- if from_entered_datetime.nil?
165
- from_entered_datetime = DateTime.now.new_offset(0) - 60
166
- end
165
+ from_entered_datetime = DateTime.now.new_offset(0) - 60 if from_entered_datetime.nil?
167
166
 
168
- if to_entered_datetime.nil?
169
- to_entered_datetime = DateTime.now
170
- end
167
+ to_entered_datetime = DateTime.now if to_entered_datetime.nil?
171
168
 
172
169
  status = convert_enum(status, SchwabRb::Order::Statuses) if status
173
170
 
@@ -287,22 +284,22 @@ module SchwabRb
287
284
  refresh_token_if_needed
288
285
 
289
286
  transaction_types = if transaction_types
290
- convert_enum_iterable(transaction_types, SchwabRb::Transaction::Types)
287
+ convert_enum_iterable(transaction_types, SchwabRb::Transaction::Types)
291
288
  else
292
289
  get_valid_enum_values(SchwabRb::Transaction::Types)
293
- end
290
+ end
294
291
 
295
292
  start_date = if start_date.nil?
296
- format_date_as_iso("start_date", DateTime.now.new_offset(0) - 60)
293
+ format_date_as_iso("start_date", DateTime.now.new_offset(0) - 60)
297
294
  else
298
295
  format_date_as_iso("start_date", start_date)
299
- end
296
+ end
300
297
 
301
298
  end_date = if end_date.nil?
302
- format_date_as_iso("end_date", DateTime.now.new_offset(0))
299
+ format_date_as_iso("end_date", DateTime.now.new_offset(0))
303
300
  else
304
301
  format_date_as_iso("end_date", end_date)
305
- end
302
+ end
306
303
 
307
304
  params = {
308
305
  "types" => transaction_types.sort.join(","),
@@ -411,7 +408,7 @@ module SchwabRb
411
408
  if return_data_objects
412
409
  quotes_data = JSON.parse(response.body, symbolize_names: true)
413
410
  quotes_data.map do |symbol, quote_data|
414
- SchwabRb::DataObjects::QuoteFactory.build({symbol => quote_data})
411
+ SchwabRb::DataObjects::QuoteFactory.build({ symbol => quote_data })
415
412
  end
416
413
  else
417
414
  response
@@ -724,12 +721,14 @@ module SchwabRb
724
721
  )
725
722
  end
726
723
 
727
- def get_movers(index, sort_order: nil, frequency: nil)
724
+ def get_movers(index, sort_order: nil, frequency: nil, return_data_objects: true)
728
725
  # Get a list of the top ten movers for a given index.
729
726
  #
730
727
  # @param index [String] Category of mover. See Movers::Index for valid values.
731
728
  # @param sort_order [String] Order in which to return values. See Movers::SortOrder for valid values.
732
- # @param frequency [String] Only return movers that saw this magnitude or greater. See Movers::Frequency for valid values.
729
+ # @param frequency [String] Only return movers that saw this magnitude or greater.
730
+ # See Movers::Frequency for valid values.
731
+ # @param return_data_objects [Boolean] Whether to return data objects or raw JSON
733
732
  refresh_token_if_needed
734
733
 
735
734
  index = convert_enum(index, SchwabRb::Movers::Indexes)
@@ -742,7 +741,14 @@ module SchwabRb
742
741
  params["sort"] = sort_order if sort_order
743
742
  params["frequency"] = frequency.to_s if frequency
744
743
 
745
- get(path, params)
744
+ response = get(path, params)
745
+
746
+ if return_data_objects
747
+ movers_data = JSON.parse(response.body, symbolize_names: true)
748
+ SchwabRb::DataObjects::MarketMoversFactory.build(movers_data)
749
+ else
750
+ response
751
+ end
746
752
  end
747
753
 
748
754
  def get_market_hours(markets, date: nil, return_data_objects: true)
@@ -34,8 +34,8 @@ module SchwabRb
34
34
  response = session.post(
35
35
  dest,
36
36
  {
37
- :body => data.to_json,
38
- :headers => { "Content-Type" => "application/json" }
37
+ body: data.to_json,
38
+ headers: { "Content-Type" => "application/json" }
39
39
  }
40
40
  )
41
41
  log_response(response, req_num)
@@ -51,8 +51,8 @@ module SchwabRb
51
51
  response = session.put(
52
52
  dest,
53
53
  {
54
- :body => data.to_json,
55
- :headers => { "Content-Type" => "application/json" }
54
+ body: data.to_json,
55
+ headers: { "Content-Type" => "application/json" }
56
56
  }
57
57
  )
58
58
  log_response(response, req_num)
@@ -73,20 +73,20 @@ module SchwabRb
73
73
  def log_request(method, req_num, dest, data = nil)
74
74
  redacted_dest = SchwabRb::Redactor.redact_url(dest.to_s)
75
75
  SchwabRb::Logger.logger.info("Req #{req_num}: #{method} to #{redacted_dest}")
76
-
77
- if data
78
- redacted_data = SchwabRb::Redactor.redact_data(data)
79
- SchwabRb::Logger.logger.debug("Payload: #{JSON.pretty_generate(redacted_data)}")
80
- end
76
+
77
+ return unless data
78
+
79
+ redacted_data = SchwabRb::Redactor.redact_data(data)
80
+ SchwabRb::Logger.logger.debug("Payload: #{JSON.pretty_generate(redacted_data)}")
81
81
  end
82
82
 
83
83
  def log_response(response, req_num)
84
84
  SchwabRb::Logger.logger.info("Resp #{req_num}: Status #{response.status}")
85
-
86
- if SchwabRb::Logger.logger.level == ::Logger::DEBUG
87
- redacted_body = SchwabRb::Redactor.redact_response_body(response)
88
- SchwabRb::Logger.logger.debug("Response body: #{JSON.pretty_generate(redacted_body)}") if redacted_body
89
- end
85
+
86
+ return unless SchwabRb::Logger.logger.level == ::Logger::DEBUG
87
+
88
+ redacted_body = SchwabRb::Redactor.redact_response_body(response)
89
+ SchwabRb::Logger.logger.debug("Response body: #{JSON.pretty_generate(redacted_body)}") if redacted_body
90
90
  end
91
91
 
92
92
  def req_num
@@ -4,9 +4,9 @@ module SchwabRb
4
4
 
5
5
  def initialize
6
6
  @logger = nil
7
- @log_file = ENV['SCHWAB_LOGFILE']
8
- @log_level = ENV.fetch('SCHWAB_LOG_LEVEL', 'WARN').upcase
9
- @silence_output = ENV.fetch('SCHWAB_SILENCE_OUTPUT', 'false').downcase == 'true'
7
+ @log_file = ENV.fetch("SCHWAB_LOGFILE", nil)
8
+ @log_level = ENV.fetch("SCHWAB_LOG_LEVEL", "WARN").upcase
9
+ @silence_output = ENV.fetch("SCHWAB_SILENCE_OUTPUT", "false").downcase == "true"
10
10
  end
11
11
 
12
12
  def has_external_logger?
@@ -18,7 +18,7 @@ module SchwabRb
18
18
  end
19
19
 
20
20
  def effective_log_file
21
- @log_file || (ENV['LOGFILE'] if ENV['LOGFILE'] && !ENV['LOGFILE'].empty?)
21
+ @log_file || (ENV["LOGFILE"] if ENV["LOGFILE"] && !ENV["LOGFILE"].empty?)
22
22
  end
23
23
  end
24
24
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'instrument'
4
- require_relative 'position'
3
+ require_relative "instrument"
4
+ require_relative "position"
5
5
 
6
6
  module SchwabRb
7
7
  module DataObjects
@@ -252,7 +252,7 @@ module SchwabRb
252
252
  is_day_trader: data.fetch(:isDayTrader),
253
253
  is_closing_only_restricted: data.fetch(:isClosingOnlyRestricted),
254
254
  pfcb_flag: data.fetch(:pfcbFlag),
255
- positions: data.fetch(:positions).map { |position| Position.build(position) },
255
+ positions: data.fetch(:positions, []).map { |position| Position.build(position) },
256
256
  initial_balances: InitialBalances.build(data.fetch(:initialBalances)),
257
257
  current_balances: CurrentBalances.build(data.fetch(:currentBalances)),
258
258
  projected_balances: ProjectedBalances.build(data.fetch(:projectedBalances))
@@ -132,7 +132,7 @@ module SchwabRb
132
132
  end
133
133
 
134
134
  def option?
135
- asset_type == 'OPTION'
135
+ asset_type == "OPTION"
136
136
  end
137
137
 
138
138
  def to_h
@@ -33,23 +33,23 @@ module SchwabRb
33
33
  end
34
34
 
35
35
  def equity
36
- @markets['equity'] || {}
36
+ @markets["equity"] || {}
37
37
  end
38
38
 
39
39
  def option
40
- @markets['option'] || {}
40
+ @markets["option"] || {}
41
41
  end
42
42
 
43
43
  def future
44
- @markets['future'] || {}
44
+ @markets["future"] || {}
45
45
  end
46
46
 
47
47
  def forex
48
- @markets['forex'] || {}
48
+ @markets["forex"] || {}
49
49
  end
50
50
 
51
51
  def bond
52
- @markets['bond'] || {}
52
+ @markets["bond"] || {}
53
53
  end
54
54
 
55
55
  def market_types
@@ -63,6 +63,7 @@ module SchwabRb
63
63
  def find_market_info(market_type, product_key)
64
64
  market_data = find_by_market_type(market_type)
65
65
  return nil unless market_data
66
+
66
67
  market_data[product_key.to_s]
67
68
  end
68
69
 
@@ -102,8 +103,9 @@ module SchwabRb
102
103
  !any_open?
103
104
  end
104
105
 
105
- def each_market(&block)
106
+ def each_market
106
107
  return enum_for(:each_market) unless block_given?
108
+
107
109
  @markets.each do |market_type, market_data|
108
110
  market_data.each do |product_key, market_info|
109
111
  yield(market_type, product_key, market_info)
@@ -121,23 +123,23 @@ module SchwabRb
121
123
  attr_reader :date, :market_type, :product, :product_name, :is_open, :session_hours
122
124
 
123
125
  def initialize(data)
124
- @date = data['date']
125
- @market_type = data['marketType']
126
- @product = data['product']
127
- @product_name = data['productName']
128
- @is_open = data['isOpen']
129
- @session_hours = data['sessionHours'] ? SessionHours.new(data['sessionHours']) : nil
126
+ @date = data["date"]
127
+ @market_type = data["marketType"]
128
+ @product = data["product"]
129
+ @product_name = data["productName"]
130
+ @is_open = data["isOpen"]
131
+ @session_hours = data["sessionHours"] ? SessionHours.new(data["sessionHours"]) : nil
130
132
  end
131
133
 
132
134
  def to_h
133
135
  result = {
134
- 'date' => @date,
135
- 'marketType' => @market_type,
136
- 'product' => @product,
137
- 'isOpen' => @is_open
136
+ "date" => @date,
137
+ "marketType" => @market_type,
138
+ "product" => @product,
139
+ "isOpen" => @is_open
138
140
  }
139
- result['productName'] = @product_name if @product_name
140
- result['sessionHours'] = @session_hours.to_h if @session_hours
141
+ result["productName"] = @product_name if @product_name
142
+ result["sessionHours"] = @session_hours.to_h if @session_hours
141
143
  result
142
144
  end
143
145
 
@@ -159,37 +161,40 @@ module SchwabRb
159
161
 
160
162
  def regular_market_hours
161
163
  return nil unless @session_hours
164
+
162
165
  @session_hours.regular_market
163
166
  end
164
167
 
165
168
  def pre_market_hours
166
169
  return nil unless @session_hours
170
+
167
171
  @session_hours.pre_market
168
172
  end
169
173
 
170
174
  def post_market_hours
171
175
  return nil unless @session_hours
176
+
172
177
  @session_hours.post_market
173
178
  end
174
179
 
175
180
  def equity?
176
- @market_type == 'EQUITY'
181
+ @market_type == "EQUITY"
177
182
  end
178
183
 
179
184
  def option?
180
- @market_type == 'OPTION'
185
+ @market_type == "OPTION"
181
186
  end
182
187
 
183
188
  def future?
184
- @market_type == 'FUTURE'
189
+ @market_type == "FUTURE"
185
190
  end
186
191
 
187
192
  def forex?
188
- @market_type == 'FOREX'
193
+ @market_type == "FOREX"
189
194
  end
190
195
 
191
196
  def bond?
192
- @market_type == 'BOND'
197
+ @market_type == "BOND"
193
198
  end
194
199
  end
195
200
 
@@ -197,16 +202,16 @@ module SchwabRb
197
202
  attr_reader :regular_market, :pre_market, :post_market
198
203
 
199
204
  def initialize(data)
200
- @regular_market = parse_session_periods(data['regularMarket'])
201
- @pre_market = parse_session_periods(data['preMarket'])
202
- @post_market = parse_session_periods(data['postMarket'])
205
+ @regular_market = parse_session_periods(data["regularMarket"])
206
+ @pre_market = parse_session_periods(data["preMarket"])
207
+ @post_market = parse_session_periods(data["postMarket"])
203
208
  end
204
209
 
205
210
  def to_h
206
211
  result = {}
207
- result['regularMarket'] = @regular_market.map(&:to_h) if @regular_market
208
- result['preMarket'] = @pre_market.map(&:to_h) if @pre_market
209
- result['postMarket'] = @post_market.map(&:to_h) if @post_market
212
+ result["regularMarket"] = @regular_market.map(&:to_h) if @regular_market
213
+ result["preMarket"] = @pre_market.map(&:to_h) if @pre_market
214
+ result["postMarket"] = @post_market.map(&:to_h) if @post_market
210
215
  result
211
216
  end
212
217
 
@@ -226,6 +231,7 @@ module SchwabRb
226
231
 
227
232
  def parse_session_periods(periods_data)
228
233
  return nil unless periods_data && periods_data.is_a?(Array)
234
+
229
235
  periods_data.map { |period_data| SessionPeriod.new(period_data) }
230
236
  end
231
237
  end
@@ -234,14 +240,14 @@ module SchwabRb
234
240
  attr_reader :start_time, :end_time
235
241
 
236
242
  def initialize(data)
237
- @start_time = data['start']
238
- @end_time = data['end']
243
+ @start_time = data["start"]
244
+ @end_time = data["end"]
239
245
  end
240
246
 
241
247
  def to_h
242
248
  {
243
- 'start' => @start_time,
244
- 'end' => @end_time
249
+ "start" => @start_time,
250
+ "end" => @end_time
245
251
  }
246
252
  end
247
253
 
@@ -255,18 +261,22 @@ module SchwabRb
255
261
 
256
262
  def duration_minutes
257
263
  return nil unless @start_time && @end_time
264
+
258
265
  start_obj = start_time_object
259
266
  end_obj = end_time_object
260
267
  return nil unless start_obj && end_obj
268
+
261
269
  ((end_obj - start_obj) / 60).to_i
262
270
  end
263
271
 
264
272
  def active_now?
265
273
  return false unless @start_time && @end_time
274
+
266
275
  now = Time.now
267
276
  start_obj = start_time_object
268
277
  end_obj = end_time_object
269
278
  return false unless start_obj && end_obj
279
+
270
280
  now >= start_obj && now <= end_obj
271
281
  end
272
282
  end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SchwabRb
4
+ module DataObjects
5
+ class MarketMoversFactory
6
+ def self.build(movers_data)
7
+ screeners = movers_data[:screeners] || []
8
+ movers = screeners.map { |screener| Mover.new(screener) }
9
+ MarketMovers.new(movers)
10
+ end
11
+ end
12
+
13
+ class MarketMovers
14
+ attr_reader :movers
15
+
16
+ def initialize(movers)
17
+ @movers = movers
18
+ end
19
+
20
+ def count
21
+ @movers.size
22
+ end
23
+
24
+ def symbols
25
+ @movers.map(&:symbol)
26
+ end
27
+
28
+ def find_by_symbol(symbol)
29
+ @movers.find { |mover| mover.symbol == symbol }
30
+ end
31
+
32
+ def top(num = 5)
33
+ @movers.first(num)
34
+ end
35
+
36
+ def each(&block)
37
+ @movers.each(&block)
38
+ end
39
+
40
+ def to_a
41
+ @movers
42
+ end
43
+ end
44
+
45
+ class Mover
46
+ attr_reader :description, :volume, :last_price, :net_change, :market_share,
47
+ :total_volume, :trades, :net_percent_change, :symbol
48
+
49
+ def initialize(data)
50
+ @description = data[:description]
51
+ @volume = data[:volume]
52
+ @last_price = data[:lastPrice]
53
+ @net_change = data[:netChange]
54
+ @market_share = data[:marketShare]
55
+ @total_volume = data[:totalVolume]
56
+ @trades = data[:trades]
57
+ @net_percent_change = data[:netPercentChange]
58
+ @symbol = data[:symbol]
59
+ end
60
+
61
+ def percentage_of_total_volume
62
+ return 0.0 if @total_volume.nil? || @total_volume.zero?
63
+
64
+ (@volume / @total_volume.to_f) * 100.0
65
+ end
66
+
67
+ def positive_change?
68
+ @net_change&.positive?
69
+ end
70
+
71
+ def negative_change?
72
+ @net_change&.negative?
73
+ end
74
+
75
+ def net_change_percentage
76
+ (@net_percent_change * 100).round(2) if @net_percent_change
77
+ end
78
+
79
+ def to_h
80
+ {
81
+ symbol: @symbol,
82
+ description: @description,
83
+ volume: @volume,
84
+ last_price: @last_price,
85
+ net_change: @net_change,
86
+ market_share: @market_share,
87
+ total_volume: @total_volume,
88
+ trades: @trades,
89
+ net_percent_change: @net_percent_change
90
+ }
91
+ end
92
+
93
+ def to_s
94
+ "<Mover symbol: #{@symbol}, volume: #{@volume}, last_price: #{@last_price}, net_change: #{@net_change}>"
95
+ end
96
+ end
97
+ end
98
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
4
- require 'date'
3
+ require "json"
4
+ require "date"
5
5
 
6
6
  module SchwabRb
7
7
  module DataObjects
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
4
- require 'date'
5
- require_relative 'option'
3
+ require "json"
4
+ require "date"
5
+ require_relative "option"
6
6
 
7
7
  module SchwabRb
8
8
  module DataObjects
@@ -14,7 +14,7 @@ module SchwabRb
14
14
  call_dates = []
15
15
  call_opts = []
16
16
  data.fetch(:callExpDateMap).each do |exp_date, options|
17
- call_dates << Date.strptime(exp_date.to_s.split(':').first, '%Y-%m-%d')
17
+ call_dates << Date.strptime(exp_date.to_s.split(":").first, "%Y-%m-%d")
18
18
  options.each_value do |opts|
19
19
  opts.each do |option_data|
20
20
  call_opts << Option.build(underlying_symbol, option_data)
@@ -25,7 +25,7 @@ module SchwabRb
25
25
  put_dates = []
26
26
  put_opts = []
27
27
  data.fetch(:putExpDateMap).each do |exp_date, options|
28
- put_dates << Date.strptime(exp_date.to_s.split(':').first, '%Y-%m-%d')
28
+ put_dates << Date.strptime(exp_date.to_s.split(":").first, "%Y-%m-%d")
29
29
 
30
30
  options.each_value do |opts|
31
31
  opts.each do |option_data|
@@ -83,10 +83,10 @@ module SchwabRb
83
83
 
84
84
  def to_a(_date = nil)
85
85
  call_opts.map do |copt|
86
- [copt.expiration_date.strftime('%Y-%m-%d'), copt.put_call, copt.strike, copt.delta, copt.bid, copt.ask,
86
+ [copt.expiration_date.strftime("%Y-%m-%d"), copt.put_call, copt.strike, copt.delta, copt.bid, copt.ask,
87
87
  copt.mark]
88
88
  end + put_opts.map do |popt|
89
- [popt.expiration_date.strftime('%Y-%m-%d'), popt.put_call, popt.strike, popt.delta, popt.bid, popt.ask,
89
+ [popt.expiration_date.strftime("%Y-%m-%d"), popt.put_call, popt.strike, popt.delta, popt.bid, popt.ask,
90
90
  popt.mark]
91
91
  end
92
92
  end
@@ -12,19 +12,19 @@ module SchwabRb
12
12
  end
13
13
 
14
14
  def initialize(data)
15
- @expiration_list = data['expirationList']&.map { |expiration_data| Expiration.new(expiration_data) } || []
16
- @status = data['status']
15
+ @expiration_list = data["expirationList"]&.map { |expiration_data| Expiration.new(expiration_data) } || []
16
+ @status = data["status"]
17
17
  end
18
18
 
19
19
  def to_h
20
20
  {
21
- 'expirationList' => @expiration_list.map(&:to_h),
22
- 'status' => @status
21
+ "expirationList" => @expiration_list.map(&:to_h),
22
+ "status" => @status
23
23
  }
24
24
  end
25
25
 
26
26
  def find_by_date(date)
27
- date_str = date.is_a?(Date) ? date.strftime('%Y-%m-%d') : date.to_s
27
+ date_str = date.is_a?(Date) ? date.strftime("%Y-%m-%d") : date.to_s
28
28
  @expiration_list.find { |exp| exp.expiration_date == date_str }
29
29
  end
30
30
 
@@ -33,15 +33,15 @@ module SchwabRb
33
33
  end
34
34
 
35
35
  def weekly_expirations
36
- @expiration_list.select { |exp| exp.expiration_type == 'W' }
36
+ @expiration_list.select { |exp| exp.expiration_type == "W" }
37
37
  end
38
38
 
39
39
  def monthly_expirations
40
- @expiration_list.select { |exp| exp.expiration_type == 'M' }
40
+ @expiration_list.select { |exp| exp.expiration_type == "M" }
41
41
  end
42
42
 
43
43
  def quarterly_expirations
44
- @expiration_list.select { |exp| exp.expiration_type == 'Q' }
44
+ @expiration_list.select { |exp| exp.expiration_type == "Q" }
45
45
  end
46
46
 
47
47
  def standard_expirations
@@ -64,32 +64,33 @@ module SchwabRb
64
64
 
65
65
  def each(&block)
66
66
  return enum_for(:each) unless block_given?
67
+
67
68
  @expiration_list.each(&block)
68
69
  end
69
70
 
70
71
  include Enumerable
71
72
 
72
73
  class Expiration
73
- attr_reader :expiration_date, :days_to_expiration, :expiration_type,
74
+ attr_reader :expiration_date, :days_to_expiration, :expiration_type,
74
75
  :settlement_type, :option_roots, :standard
75
76
 
76
77
  def initialize(data)
77
- @expiration_date = data['expirationDate']
78
- @days_to_expiration = data['daysToExpiration']
79
- @expiration_type = data['expirationType']
80
- @settlement_type = data['settlementType']
81
- @option_roots = data['optionRoots']
82
- @standard = data['standard']
78
+ @expiration_date = data["expirationDate"]
79
+ @days_to_expiration = data["daysToExpiration"]
80
+ @expiration_type = data["expirationType"]
81
+ @settlement_type = data["settlementType"]
82
+ @option_roots = data["optionRoots"]
83
+ @standard = data["standard"]
83
84
  end
84
85
 
85
86
  def to_h
86
87
  {
87
- 'expirationDate' => @expiration_date,
88
- 'daysToExpiration' => @days_to_expiration,
89
- 'expirationType' => @expiration_type,
90
- 'settlementType' => @settlement_type,
91
- 'optionRoots' => @option_roots,
92
- 'standard' => @standard
88
+ "expirationDate" => @expiration_date,
89
+ "daysToExpiration" => @days_to_expiration,
90
+ "expirationType" => @expiration_type,
91
+ "settlementType" => @settlement_type,
92
+ "optionRoots" => @option_roots,
93
+ "standard" => @standard
93
94
  }
94
95
  end
95
96
 
@@ -98,19 +99,19 @@ module SchwabRb
98
99
  end
99
100
 
100
101
  def weekly?
101
- @expiration_type == 'W'
102
+ @expiration_type == "W"
102
103
  end
103
104
 
104
105
  def monthly?
105
- @expiration_type == 'M'
106
+ @expiration_type == "M"
106
107
  end
107
108
 
108
109
  def quarterly?
109
- @expiration_type == 'Q'
110
+ @expiration_type == "Q"
110
111
  end
111
112
 
112
113
  def special?
113
- @expiration_type == 'S'
114
+ @expiration_type == "S"
114
115
  end
115
116
 
116
117
  def date_object