schwab_rb 0.2.0 → 0.3.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/.claude/settings.local.json +9 -0
- data/.rspec_status +209 -180
- data/CLAUDE.md +137 -0
- data/README.md +3 -3
- data/examples/fetch_account_numbers.rb +12 -15
- data/examples/fetch_user_preferences.rb +16 -19
- data/lib/schwab_rb/account.rb +1 -1
- data/lib/schwab_rb/auth/auth_context.rb +1 -1
- data/lib/schwab_rb/auth/init_client_easy.rb +29 -31
- data/lib/schwab_rb/auth/init_client_login.rb +24 -21
- data/lib/schwab_rb/auth/login_flow_server.rb +2 -2
- data/lib/schwab_rb/auth/token.rb +1 -1
- data/lib/schwab_rb/auth/token_manager.rb +5 -7
- data/lib/schwab_rb/clients/async_client.rb +25 -27
- data/lib/schwab_rb/clients/base_client.rb +22 -16
- data/lib/schwab_rb/clients/client.rb +14 -14
- data/lib/schwab_rb/configuration.rb +4 -4
- data/lib/schwab_rb/data_objects/account.rb +2 -2
- data/lib/schwab_rb/data_objects/instrument.rb +1 -1
- data/lib/schwab_rb/data_objects/market_hours.rb +43 -33
- data/lib/schwab_rb/data_objects/market_movers.rb +98 -0
- data/lib/schwab_rb/data_objects/option.rb +2 -2
- data/lib/schwab_rb/data_objects/option_chain.rb +7 -7
- data/lib/schwab_rb/data_objects/option_expiration_chain.rb +26 -25
- data/lib/schwab_rb/data_objects/order.rb +7 -6
- data/lib/schwab_rb/data_objects/order_leg.rb +5 -5
- data/lib/schwab_rb/data_objects/order_preview.rb +13 -16
- data/lib/schwab_rb/data_objects/position.rb +4 -4
- data/lib/schwab_rb/data_objects/price_history.rb +27 -19
- data/lib/schwab_rb/data_objects/quote.rb +6 -6
- data/lib/schwab_rb/data_objects/transaction.rb +6 -6
- data/lib/schwab_rb/data_objects/user_preferences.rb +3 -3
- data/lib/schwab_rb/market_hours.rb +5 -5
- data/lib/schwab_rb/movers.rb +16 -16
- data/lib/schwab_rb/orders/builder.rb +5 -5
- data/lib/schwab_rb/orders/destination.rb +12 -12
- data/lib/schwab_rb/orders/duration.rb +7 -7
- data/lib/schwab_rb/orders/equity_instructions.rb +4 -4
- data/lib/schwab_rb/orders/instruments.rb +8 -8
- data/lib/schwab_rb/orders/price_link_basis.rb +9 -9
- data/lib/schwab_rb/orders/price_link_type.rb +3 -3
- data/lib/schwab_rb/orders/session.rb +4 -4
- data/lib/schwab_rb/orders/special_instruction.rb +3 -3
- data/lib/schwab_rb/orders/stop_price_link_basis.rb +9 -9
- data/lib/schwab_rb/orders/stop_price_link_type.rb +3 -3
- data/lib/schwab_rb/orders/stop_type.rb +5 -5
- data/lib/schwab_rb/orders/tax_lot_method.rb +7 -7
- data/lib/schwab_rb/price_history.rb +8 -8
- data/lib/schwab_rb/quote.rb +5 -5
- data/lib/schwab_rb/transaction.rb +15 -15
- data/lib/schwab_rb/utils/logger.rb +11 -15
- data/lib/schwab_rb/utils/redactor.rb +23 -25
- data/lib/schwab_rb/version.rb +1 -1
- data/lib/schwab_rb.rb +1 -0
- 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
|
-
|
287
|
+
convert_enum_iterable(transaction_types, SchwabRb::Transaction::Types)
|
291
288
|
else
|
292
289
|
get_valid_enum_values(SchwabRb::Transaction::Types)
|
293
|
-
|
290
|
+
end
|
294
291
|
|
295
292
|
start_date = if start_date.nil?
|
296
|
-
|
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
|
-
|
296
|
+
end
|
300
297
|
|
301
298
|
end_date = if end_date.nil?
|
302
|
-
|
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
|
-
|
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.
|
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
|
-
:
|
38
|
-
:
|
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
|
-
:
|
55
|
-
:
|
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
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
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
|
8
|
-
@log_level = ENV.fetch(
|
9
|
-
@silence_output = ENV.fetch(
|
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[
|
21
|
+
@log_file || (ENV["LOGFILE"] if ENV["LOGFILE"] && !ENV["LOGFILE"].empty?)
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
@@ -33,23 +33,23 @@ module SchwabRb
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def equity
|
36
|
-
@markets[
|
36
|
+
@markets["equity"] || {}
|
37
37
|
end
|
38
38
|
|
39
39
|
def option
|
40
|
-
@markets[
|
40
|
+
@markets["option"] || {}
|
41
41
|
end
|
42
42
|
|
43
43
|
def future
|
44
|
-
@markets[
|
44
|
+
@markets["future"] || {}
|
45
45
|
end
|
46
46
|
|
47
47
|
def forex
|
48
|
-
@markets[
|
48
|
+
@markets["forex"] || {}
|
49
49
|
end
|
50
50
|
|
51
51
|
def bond
|
52
|
-
@markets[
|
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
|
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[
|
125
|
-
@market_type = data[
|
126
|
-
@product = data[
|
127
|
-
@product_name = data[
|
128
|
-
@is_open = data[
|
129
|
-
@session_hours = data[
|
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
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
136
|
+
"date" => @date,
|
137
|
+
"marketType" => @market_type,
|
138
|
+
"product" => @product,
|
139
|
+
"isOpen" => @is_open
|
138
140
|
}
|
139
|
-
result[
|
140
|
-
result[
|
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 ==
|
181
|
+
@market_type == "EQUITY"
|
177
182
|
end
|
178
183
|
|
179
184
|
def option?
|
180
|
-
@market_type ==
|
185
|
+
@market_type == "OPTION"
|
181
186
|
end
|
182
187
|
|
183
188
|
def future?
|
184
|
-
@market_type ==
|
189
|
+
@market_type == "FUTURE"
|
185
190
|
end
|
186
191
|
|
187
192
|
def forex?
|
188
|
-
@market_type ==
|
193
|
+
@market_type == "FOREX"
|
189
194
|
end
|
190
195
|
|
191
196
|
def bond?
|
192
|
-
@market_type ==
|
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[
|
201
|
-
@pre_market = parse_session_periods(data[
|
202
|
-
@post_market = parse_session_periods(data[
|
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[
|
208
|
-
result[
|
209
|
-
result[
|
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[
|
238
|
-
@end_time = data[
|
243
|
+
@start_time = data["start"]
|
244
|
+
@end_time = data["end"]
|
239
245
|
end
|
240
246
|
|
241
247
|
def to_h
|
242
248
|
{
|
243
|
-
|
244
|
-
|
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,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require_relative
|
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(
|
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(
|
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(
|
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(
|
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[
|
16
|
-
@status = data[
|
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
|
-
|
22
|
-
|
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(
|
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 ==
|
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 ==
|
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 ==
|
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[
|
78
|
-
@days_to_expiration = data[
|
79
|
-
@expiration_type = data[
|
80
|
-
@settlement_type = data[
|
81
|
-
@option_roots = data[
|
82
|
-
@standard = data[
|
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
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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 ==
|
102
|
+
@expiration_type == "W"
|
102
103
|
end
|
103
104
|
|
104
105
|
def monthly?
|
105
|
-
@expiration_type ==
|
106
|
+
@expiration_type == "M"
|
106
107
|
end
|
107
108
|
|
108
109
|
def quarterly?
|
109
|
-
@expiration_type ==
|
110
|
+
@expiration_type == "Q"
|
110
111
|
end
|
111
112
|
|
112
113
|
def special?
|
113
|
-
@expiration_type ==
|
114
|
+
@expiration_type == "S"
|
114
115
|
end
|
115
116
|
|
116
117
|
def date_object
|