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.
- checksums.yaml +4 -4
- data/.claude/settings.local.json +16 -1
- data/CHANGELOG.md +20 -4
- data/examples/place_oco_order.rb +1 -4
- data/examples/stream_level_one.rb +65 -0
- data/examples/stream_order_book.rb +57 -0
- data/lib/schwab_rb/auth/init_client_easy.rb +19 -18
- data/lib/schwab_rb/auth/{init_client_token_file.rb → init_client_from_database.rb} +5 -6
- data/lib/schwab_rb/auth/init_client_login.rb +7 -8
- data/lib/schwab_rb/auth/token_manager.rb +25 -15
- data/lib/schwab_rb/cli/app.rb +11 -23
- data/lib/schwab_rb/clients/base_client.rb +51 -75
- data/lib/schwab_rb/configuration.rb +3 -3
- data/lib/schwab_rb/constants.rb +0 -1
- data/lib/schwab_rb/path_support.rb +2 -1
- data/lib/schwab_rb/storage/database.rb +148 -0
- data/lib/schwab_rb/stream/async_client.rb +30 -0
- data/lib/schwab_rb/stream/base.rb +240 -0
- data/lib/schwab_rb/stream/client.rb +44 -0
- data/lib/schwab_rb/stream/fields.rb +339 -0
- data/lib/schwab_rb/stream/message_builder.rb +66 -0
- data/lib/schwab_rb/stream/services.rb +52 -0
- data/lib/schwab_rb/version.rb +1 -1
- data/lib/schwab_rb.rb +8 -2
- metadata +40 -4
- data/lib/schwab_rb/account_hash_manager.rb +0 -119
|
@@ -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
|
data/lib/schwab_rb/version.rb
CHANGED
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/
|
|
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.
|
|
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-
|
|
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
|