schwab_rb 0.9.2 → 1.0.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.
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SchwabRb
4
+ module Stream
5
+ module Services
6
+ LEVELONE_EQUITIES = "LEVELONE_EQUITIES"
7
+ LEVELONE_OPTIONS = "LEVELONE_OPTIONS"
8
+ LEVELONE_FUTURES = "LEVELONE_FUTURES"
9
+ LEVELONE_FUTURES_OPTIONS = "LEVELONE_FUTURES_OPTIONS"
10
+ LEVELONE_FOREX = "LEVELONE_FOREX"
11
+ NYSE_BOOK = "NYSE_BOOK"
12
+ NASDAQ_BOOK = "NASDAQ_BOOK"
13
+ OPTIONS_BOOK = "OPTIONS_BOOK"
14
+ CHART_EQUITY = "CHART_EQUITY"
15
+ CHART_FUTURES = "CHART_FUTURES"
16
+ SCREENER_EQUITY = "SCREENER_EQUITY"
17
+ SCREENER_OPTION = "SCREENER_OPTION"
18
+ ACCT_ACTIVITY = "ACCT_ACTIVITY"
19
+
20
+ ALL = constants.map { |c| const_get(c) }.freeze
21
+
22
+ SYMBOL_TO_SERVICE = {
23
+ level_one_equities: LEVELONE_EQUITIES,
24
+ level_one_options: LEVELONE_OPTIONS,
25
+ level_one_futures: LEVELONE_FUTURES,
26
+ level_one_futures_options: LEVELONE_FUTURES_OPTIONS,
27
+ level_one_forex: LEVELONE_FOREX,
28
+ nyse_book: NYSE_BOOK,
29
+ nasdaq_book: NASDAQ_BOOK,
30
+ options_book: OPTIONS_BOOK,
31
+ chart_equity: CHART_EQUITY,
32
+ chart_futures: CHART_FUTURES,
33
+ screener_equity: SCREENER_EQUITY,
34
+ screener_option: SCREENER_OPTION,
35
+ acct_activity: ACCT_ACTIVITY
36
+ }.freeze
37
+
38
+ def self.lookup(symbol)
39
+ SYMBOL_TO_SERVICE[symbol] || raise(ArgumentError, "Unknown service: #{symbol.inspect}")
40
+ end
41
+ end
42
+
43
+ module Commands
44
+ LOGIN = "LOGIN"
45
+ LOGOUT = "LOGOUT"
46
+ SUBS = "SUBS"
47
+ ADD = "ADD"
48
+ UNSUBS = "UNSUBS"
49
+ VIEW = "VIEW"
50
+ end
51
+ end
52
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SchwabRb
4
- VERSION = "0.9.2"
4
+ VERSION = "1.0.0"
5
5
  end
data/lib/schwab_rb.rb CHANGED
@@ -2,12 +2,11 @@
2
2
 
3
3
  require_relative "schwab_rb/version"
4
4
  require_relative "schwab_rb/configuration"
5
- require_relative "schwab_rb/account_hash_manager"
6
5
  require_relative "schwab_rb/path_support"
7
6
  require_relative "schwab_rb/auth/token_manager"
8
7
  require_relative "schwab_rb/auth/token"
9
8
  require_relative "schwab_rb/auth/init_client_login"
10
- require_relative "schwab_rb/auth/init_client_token_file"
9
+ require_relative "schwab_rb/auth/init_client_from_database"
11
10
  require_relative "schwab_rb/auth/init_client_easy"
12
11
  require_relative "schwab_rb/auth/auth_context"
13
12
  require_relative "schwab_rb/clients/client"
@@ -54,6 +53,13 @@ require_relative "schwab_rb/orders/vertical_order"
54
53
  require_relative "schwab_rb/orders/vertical_roll_order"
55
54
  require_relative "schwab_rb/orders/single_order"
56
55
  require_relative "schwab_rb/orders/oco_order"
56
+ require_relative "schwab_rb/storage/database"
57
+ require_relative "schwab_rb/stream/services"
58
+ require_relative "schwab_rb/stream/fields"
59
+ require_relative "schwab_rb/stream/message_builder"
60
+ require_relative "schwab_rb/stream/base"
61
+ require_relative "schwab_rb/stream/client"
62
+ require_relative "schwab_rb/stream/async_client"
57
63
  require_relative "schwab_rb/cli"
58
64
 
59
65
  module SchwabRb
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.9.2
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joseph Platta
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-04-13 00:00:00.000000000 Z
10
+ date: 2026-09-06 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: async
@@ -127,6 +127,34 @@ dependencies:
127
127
  - - ">="
128
128
  - !ruby/object:Gem::Version
129
129
  version: '0'
130
+ - !ruby/object:Gem::Dependency
131
+ name: sqlite3
132
+ requirement: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - "~>"
135
+ - !ruby/object:Gem::Version
136
+ version: '2.0'
137
+ type: :runtime
138
+ prerelease: false
139
+ version_requirements: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - "~>"
142
+ - !ruby/object:Gem::Version
143
+ version: '2.0'
144
+ - !ruby/object:Gem::Dependency
145
+ name: async-websocket
146
+ requirement: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - "~>"
149
+ - !ruby/object:Gem::Version
150
+ version: '0.26'
151
+ type: :runtime
152
+ prerelease: false
153
+ version_requirements: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - "~>"
156
+ - !ruby/object:Gem::Version
157
+ version: '0.26'
130
158
  - !ruby/object:Gem::Dependency
131
159
  name: rspec
132
160
  requirement: !ruby/object:Gem::Requirement
@@ -219,14 +247,15 @@ files:
219
247
  - examples/fetch_price_history.rb
220
248
  - examples/fetch_user_preferences.rb
221
249
  - examples/place_oco_order.rb
250
+ - examples/stream_level_one.rb
251
+ - examples/stream_order_book.rb
222
252
  - exe/schwab_rb
223
253
  - lib/schwab_rb.rb
224
254
  - lib/schwab_rb/account.rb
225
- - lib/schwab_rb/account_hash_manager.rb
226
255
  - lib/schwab_rb/auth/auth_context.rb
227
256
  - lib/schwab_rb/auth/init_client_easy.rb
257
+ - lib/schwab_rb/auth/init_client_from_database.rb
228
258
  - lib/schwab_rb/auth/init_client_login.rb
229
- - lib/schwab_rb/auth/init_client_token_file.rb
230
259
  - lib/schwab_rb/auth/login_flow_server.rb
231
260
  - lib/schwab_rb/auth/token.rb
232
261
  - lib/schwab_rb/auth/token_manager.rb
@@ -284,6 +313,13 @@ files:
284
313
  - lib/schwab_rb/price_history.rb
285
314
  - lib/schwab_rb/price_history/downloader.rb
286
315
  - lib/schwab_rb/quote.rb
316
+ - lib/schwab_rb/storage/database.rb
317
+ - lib/schwab_rb/stream/async_client.rb
318
+ - lib/schwab_rb/stream/base.rb
319
+ - lib/schwab_rb/stream/client.rb
320
+ - lib/schwab_rb/stream/fields.rb
321
+ - lib/schwab_rb/stream/message_builder.rb
322
+ - lib/schwab_rb/stream/services.rb
287
323
  - lib/schwab_rb/transaction.rb
288
324
  - lib/schwab_rb/utils/enum_enforcer.rb
289
325
  - lib/schwab_rb/utils/logger.rb
@@ -1,119 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
- require "fileutils"
5
-
6
- module SchwabRb
7
- class AccountHashManager
8
- class AccountNamesFileNotFoundError < StandardError; end
9
- class InvalidAccountNamesFileError < StandardError; end
10
-
11
- attr_reader :account_names_path, :account_hashes_path, :account_names
12
-
13
- def initialize(account_names_path = nil, account_hashes_path = nil)
14
- @account_names_path = account_names_path || SchwabRb.configuration.account_names_path
15
- @account_hashes_path = account_hashes_path || SchwabRb.configuration.account_hashes_path
16
- @account_names_path = File.expand_path(@account_names_path)
17
- @account_hashes_path = File.expand_path(@account_hashes_path)
18
- @account_names = []
19
- end
20
-
21
- def update_hashes_from_api_response(account_numbers_response)
22
- account_names = load_account_names
23
- current_hashes = load_account_hashes
24
-
25
- number_to_hash = {}
26
- account_numbers_response.each do |account_data|
27
- account_number = account_data[:accountNumber]
28
- hash_value = account_data[:hashValue]
29
- number_to_hash[account_number] = hash_value
30
- end
31
-
32
- updated_hashes = {}
33
- missing_accounts = []
34
-
35
- account_names.each do |name, account_number|
36
- if number_to_hash.key?(account_number)
37
- updated_hashes[name] = number_to_hash[account_number]
38
- elsif current_hashes.key?(name)
39
- # Keep existing hash but warn that account wasn't in API response
40
- updated_hashes[name] = current_hashes[name]
41
- missing_accounts << { name: name, number: account_number }
42
- else
43
- # Account name exists but no hash found (new or invalid account)
44
- missing_accounts << { name: name, number: account_number }
45
- end
46
- end
47
-
48
- # Log warnings for accounts that weren't found in API response
49
- if missing_accounts.any?
50
- missing_accounts.each do |account|
51
- SchwabRb::Logger.logger.warn(
52
- "Account '#{account[:name]}' not found in API response. " \
53
- "This may indicate a closed account or incorrect account number in account_names.json"
54
- )
55
- end
56
- end
57
-
58
- save_account_hashes(updated_hashes)
59
- updated_hashes
60
- end
61
-
62
- def get_hash_by_name(account_name)
63
- hashes = load_account_hashes
64
- hashes[account_name]
65
- end
66
-
67
- def get_all_hashes
68
- load_account_hashes
69
- end
70
-
71
- def available_account_names
72
- begin
73
- load_account_names.keys
74
- rescue AccountNamesFileNotFoundError
75
- []
76
- end
77
- end
78
-
79
- private
80
-
81
- def load_account_hashes
82
- return {} unless File.exist?(@account_hashes_path)
83
-
84
- begin
85
- json_content = File.read(@account_hashes_path)
86
- JSON.parse(json_content)
87
- rescue JSON::ParserError
88
- {}
89
- end
90
- end
91
-
92
- def save_account_hashes(hashes_map)
93
- FileUtils.mkdir_p(File.dirname(@account_hashes_path))
94
-
95
- File.write(@account_hashes_path, JSON.pretty_generate(hashes_map))
96
- end
97
-
98
- def load_account_names
99
- unless File.exist?(@account_names_path)
100
- raise AccountNamesFileNotFoundError,
101
- "Account names file not found at #{@account_names_path}. " \
102
- "Please create a JSON file mapping account names to account numbers. " \
103
- "Example: {\"my_trading_account\": \"12345678\", \"my_ira\": \"87654321\"}"
104
- end
105
-
106
- begin
107
- json_content = File.read(@account_names_path)
108
- return {} if json_content.strip.empty?
109
-
110
- account_names_hash = JSON.parse(json_content)
111
- @account_names = account_names_hash.keys
112
- account_names_hash
113
- rescue JSON::ParserError => e
114
- raise InvalidAccountNamesFileError,
115
- "Invalid JSON in account names file at #{@account_names_path}: #{e.message}"
116
- end
117
- end
118
- end
119
- end