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.
- checksums.yaml +4 -4
- data/.rspec_status +193 -193
- data/.rubocop.yml +2 -0
- data/examples/fetch_account_numbers.rb +1 -1
- data/examples/fetch_user_preferences.rb +1 -1
- data/lib/schwab_rb/auth/auth_context.rb +19 -15
- data/lib/schwab_rb/auth/init_client_easy.rb +36 -32
- data/lib/schwab_rb/auth/init_client_login.rb +165 -161
- data/lib/schwab_rb/auth/init_client_token_file.rb +27 -23
- data/lib/schwab_rb/auth/login_flow_server.rb +39 -36
- data/lib/schwab_rb/auth/token.rb +25 -21
- data/lib/schwab_rb/auth/token_manager.rb +88 -84
- data/lib/schwab_rb/clients/async_client.rb +2 -0
- data/lib/schwab_rb/clients/base_client.rb +2 -0
- data/lib/schwab_rb/configuration.rb +2 -0
- data/lib/schwab_rb/constants.rb +6 -4
- data/lib/schwab_rb/data_objects/account_numbers.rb +1 -0
- data/lib/schwab_rb/data_objects/market_hours.rb +1 -1
- data/lib/schwab_rb/data_objects/market_movers.rb +1 -0
- data/lib/schwab_rb/data_objects/option_expiration_chain.rb +1 -1
- data/lib/schwab_rb/data_objects/order_preview.rb +21 -49
- data/lib/schwab_rb/data_objects/price_history.rb +1 -1
- data/lib/schwab_rb/orders/builder.rb +162 -158
- data/lib/schwab_rb/orders/destination.rb +2 -0
- data/lib/schwab_rb/orders/duration.rb +14 -8
- data/lib/schwab_rb/orders/equity_instructions.rb +2 -0
- data/lib/schwab_rb/orders/errors.rb +2 -0
- data/lib/schwab_rb/orders/instruments.rb +2 -0
- data/lib/schwab_rb/orders/option_instructions.rb +2 -0
- data/lib/schwab_rb/orders/order.rb +2 -0
- data/lib/schwab_rb/orders/price_link_basis.rb +2 -0
- data/lib/schwab_rb/orders/price_link_type.rb +2 -0
- data/lib/schwab_rb/orders/session.rb +16 -10
- data/lib/schwab_rb/orders/special_instruction.rb +2 -0
- data/lib/schwab_rb/orders/stop_price_link_basis.rb +2 -0
- data/lib/schwab_rb/orders/stop_price_link_type.rb +2 -0
- data/lib/schwab_rb/orders/stop_type.rb +2 -0
- data/lib/schwab_rb/orders/tax_lot_method.rb +2 -0
- data/lib/schwab_rb/utils/enum_enforcer.rb +3 -4
- data/lib/schwab_rb/utils/logger.rb +3 -1
- data/lib/schwab_rb/utils/redactor.rb +3 -5
- data/lib/schwab_rb/version.rb +1 -1
- metadata +2 -2
data/lib/schwab_rb/auth/token.rb
CHANGED
@@ -1,24 +1,28 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SchwabRb
|
4
|
+
module Auth
|
5
|
+
class Token
|
6
|
+
def initialize(
|
7
|
+
token: nil,
|
8
|
+
expires_in: nil,
|
9
|
+
token_type: "Bearer",
|
10
|
+
scope: nil,
|
11
|
+
refresh_token: nil,
|
12
|
+
id_token: nil,
|
13
|
+
expires_at: nil
|
14
|
+
)
|
15
|
+
@token = token
|
16
|
+
@expires_in = expires_in
|
17
|
+
@token_type = token_type
|
18
|
+
@scope = scope
|
19
|
+
@refresh_token = refresh_token
|
20
|
+
@id_token = id_token
|
21
|
+
@expires_at = expires_at
|
22
|
+
end
|
20
23
|
|
21
|
-
|
22
|
-
|
24
|
+
attr_reader :token, :expires_in, :token_type, :scope,
|
25
|
+
:refresh_token, :id_token, :expires_at
|
26
|
+
end
|
23
27
|
end
|
24
28
|
end
|
@@ -1,103 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "oauth2"
|
2
4
|
require "json"
|
3
5
|
|
4
|
-
module SchwabRb
|
5
|
-
|
6
|
-
class
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
token
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
6
|
+
module SchwabRb
|
7
|
+
module Auth
|
8
|
+
class TokenManager
|
9
|
+
class << self
|
10
|
+
def from_file(token_path)
|
11
|
+
token_data = JSON.parse(File.read(token_path))
|
12
|
+
token = SchwabRb::Auth::Token.new(
|
13
|
+
token: token_data["token"]["access_token"],
|
14
|
+
expires_in: token_data["token"]["expires_in"],
|
15
|
+
token_type: token_data["token"]["token_type"],
|
16
|
+
scope: token_data["token"]["scope"],
|
17
|
+
refresh_token: token_data["token"]["refresh_token"],
|
18
|
+
id_token: token_data["token"]["id_token"],
|
19
|
+
expires_at: token_data["token"]["expires_at"]
|
20
|
+
)
|
18
21
|
|
19
|
-
|
20
|
-
|
22
|
+
TokenManager.new(token, token_data["timestamp"], token_path: token_path)
|
23
|
+
end
|
21
24
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
25
|
+
def from_oauth2_token(oauth2_token, timestamp, token_path: SchwabRb::Constants::DEFAULT_TOKEN_PATH)
|
26
|
+
token = SchwabRb::Auth::Token.new(
|
27
|
+
token: oauth2_token.token,
|
28
|
+
expires_in: oauth2_token.expires_in,
|
29
|
+
token_type: oauth2_token.params["token_type"] || "Bearer",
|
30
|
+
scope: oauth2_token.params["scope"],
|
31
|
+
refresh_token: oauth2_token.refresh_token,
|
32
|
+
id_token: oauth2_token.params["id_token"],
|
33
|
+
expires_at: oauth2_token.expires_at
|
34
|
+
)
|
32
35
|
|
33
|
-
|
36
|
+
TokenManager.new(token, timestamp, token_path: token_path)
|
37
|
+
end
|
34
38
|
end
|
35
|
-
end
|
36
39
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
40
|
+
def initialize(token, timestamp, token_path: SchwabRb::Constants::DEFAULT_TOKEN_PATH)
|
41
|
+
@token = token
|
42
|
+
@timestamp = timestamp
|
43
|
+
@token_path = token_path
|
44
|
+
end
|
42
45
|
|
43
|
-
|
44
|
-
|
45
|
-
def refresh_token(client)
|
46
|
-
new_token = client.session.refresh!
|
47
|
-
|
48
|
-
@token = SchwabRb::Auth::Token.new(
|
49
|
-
token: new_token.token,
|
50
|
-
expires_in: new_token.expires_in,
|
51
|
-
token_type: new_token.params["token_type"] || "Bearer",
|
52
|
-
scope: new_token.params["scope"],
|
53
|
-
refresh_token: new_token.refresh_token,
|
54
|
-
id_token: new_token.params["id_token"],
|
55
|
-
expires_at: new_token.expires_at
|
56
|
-
)
|
57
|
-
@timestamp = Time.now.to_i
|
58
|
-
|
59
|
-
to_file
|
60
|
-
|
61
|
-
oauth = OAuth2::Client.new(
|
62
|
-
client.api_key,
|
63
|
-
client.app_secret,
|
64
|
-
site: SchwabRb::Constants::SCHWAB_BASE_URL,
|
65
|
-
token_url: "/v1/oauth/token"
|
66
|
-
)
|
67
|
-
|
68
|
-
OAuth2::AccessToken.new(
|
69
|
-
oauth,
|
70
|
-
token.token,
|
71
|
-
refresh_token: token.refresh_token,
|
72
|
-
expires_at: token.expires_at
|
73
|
-
)
|
74
|
-
end
|
46
|
+
attr_reader :token, :timestamp, :token_path
|
75
47
|
|
76
|
-
|
77
|
-
|
78
|
-
end
|
48
|
+
def refresh_token(client)
|
49
|
+
new_token = client.session.refresh!
|
79
50
|
|
80
|
-
|
81
|
-
|
82
|
-
|
51
|
+
@token = SchwabRb::Auth::Token.new(
|
52
|
+
token: new_token.token,
|
53
|
+
expires_in: new_token.expires_in,
|
54
|
+
token_type: new_token.params["token_type"] || "Bearer",
|
55
|
+
scope: new_token.params["scope"],
|
56
|
+
refresh_token: new_token.refresh_token,
|
57
|
+
id_token: new_token.params["id_token"],
|
58
|
+
expires_at: new_token.expires_at
|
59
|
+
)
|
60
|
+
@timestamp = Time.now.to_i
|
61
|
+
|
62
|
+
to_file
|
83
63
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
64
|
+
oauth = OAuth2::Client.new(
|
65
|
+
client.api_key,
|
66
|
+
client.app_secret,
|
67
|
+
site: SchwabRb::Constants::SCHWAB_BASE_URL,
|
68
|
+
token_url: "/v1/oauth/token"
|
69
|
+
)
|
70
|
+
|
71
|
+
OAuth2::AccessToken.new(
|
72
|
+
oauth,
|
73
|
+
token.token,
|
91
74
|
refresh_token: token.refresh_token,
|
92
|
-
access_token: token.token,
|
93
|
-
id_token: token.id_token,
|
94
75
|
expires_at: token.expires_at
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_file
|
80
|
+
File.write(token_path, to_json)
|
81
|
+
end
|
82
|
+
|
83
|
+
def token_age
|
84
|
+
Time.now.to_i - timestamp
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_h
|
88
|
+
{
|
89
|
+
timestamp: timestamp,
|
90
|
+
token: {
|
91
|
+
expires_in: token.expires_in,
|
92
|
+
token_type: token.token_type,
|
93
|
+
scope: token.scope,
|
94
|
+
refresh_token: token.refresh_token,
|
95
|
+
access_token: token.token,
|
96
|
+
id_token: token.id_token,
|
97
|
+
expires_at: token.expires_at
|
98
|
+
}
|
95
99
|
}
|
96
|
-
|
97
|
-
end
|
100
|
+
end
|
98
101
|
|
99
|
-
|
100
|
-
|
102
|
+
def to_json(*_args)
|
103
|
+
to_h.to_json
|
104
|
+
end
|
101
105
|
end
|
102
106
|
end
|
103
107
|
end
|
data/lib/schwab_rb/constants.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module SchwabRb
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
module SchwabRb
|
4
|
+
module Constants
|
5
|
+
SCHWAB_BASE_URL="https://api.schwabapi.com"
|
6
|
+
TOKEN_ENDPOINT="https://api.schwabapi.com/v1/oauth/token"
|
7
|
+
DEFAULT_TOKEN_PATH="./token.json"
|
8
|
+
end
|
7
9
|
end
|
@@ -230,7 +230,7 @@ module SchwabRb
|
|
230
230
|
private
|
231
231
|
|
232
232
|
def parse_session_periods(periods_data)
|
233
|
-
return nil unless periods_data
|
233
|
+
return nil unless periods_data.is_a?(Array)
|
234
234
|
|
235
235
|
periods_data.map { |period_data| SessionPeriod.new(period_data) }
|
236
236
|
end
|
@@ -42,13 +42,13 @@ module SchwabRb
|
|
42
42
|
def commission
|
43
43
|
return 0.0 unless @projected_commission
|
44
44
|
|
45
|
-
@projected_commission.
|
45
|
+
@projected_commission.commission_value
|
46
46
|
end
|
47
47
|
|
48
48
|
def fees
|
49
49
|
return 0.0 unless @projected_commission
|
50
50
|
|
51
|
-
@projected_commission.
|
51
|
+
@projected_commission.fee_value
|
52
52
|
end
|
53
53
|
|
54
54
|
def to_h
|
@@ -145,71 +145,43 @@ module SchwabRb
|
|
145
145
|
end
|
146
146
|
|
147
147
|
class CommissionAndFee
|
148
|
-
attr_reader :
|
148
|
+
attr_reader :commission, :commissions, :fee, :fees, :true_commission
|
149
149
|
|
150
150
|
def initialize(attrs)
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
@direct_commission = attrs[:commission]
|
157
|
-
@direct_fee = attrs[:fee]
|
158
|
-
@direct_true_commission = attrs[:trueCommission]
|
159
|
-
# Handle the nested structure (from test data)
|
160
|
-
else
|
161
|
-
@commission_data = attrs.dig(:commission, :commissionLegs) || []
|
162
|
-
@fee_data = attrs.dig(:fee, :feeLegs) || []
|
163
|
-
@true_commission_data = attrs.dig(:trueCommission, :commissionLegs) || []
|
164
|
-
@direct_commission = nil
|
165
|
-
@direct_fee = nil
|
166
|
-
@direct_true_commission = nil
|
167
|
-
end
|
151
|
+
@commission = attrs[:commission]&.to_f
|
152
|
+
@commissions = attrs[:commissions] || []
|
153
|
+
@fee = attrs[:fee]&.to_f
|
154
|
+
@fees = attrs[:fees] || []
|
155
|
+
@true_commission = attrs[:trueCommission]&.to_f
|
168
156
|
end
|
169
157
|
|
170
158
|
def commission_total
|
171
|
-
calculate_total_from_legs(@
|
159
|
+
calculate_total_from_legs(@commissions, "COMMISSION")
|
172
160
|
end
|
173
161
|
|
174
|
-
def
|
175
|
-
@
|
162
|
+
def commission_value
|
163
|
+
@commission || commission_total
|
176
164
|
end
|
177
165
|
|
178
166
|
def fee_total
|
179
|
-
calculate_total_from_legs(@
|
180
|
-
end
|
181
|
-
|
182
|
-
def fee
|
183
|
-
@direct_fee || format("%.2f", fee_total)
|
184
|
-
end
|
185
|
-
|
186
|
-
def true_commission
|
187
|
-
if @direct_true_commission
|
188
|
-
@direct_true_commission
|
189
|
-
elsif @true_commission_data.any?
|
190
|
-
# For nested structure, calculate from true commission legs
|
191
|
-
true_commission_total = calculate_total_from_legs(@true_commission_data, "COMMISSION")
|
192
|
-
format("%.2f", true_commission_total * 2)
|
193
|
-
else
|
194
|
-
format("%.2f", commission_total * 2)
|
195
|
-
end
|
167
|
+
calculate_total_from_legs(@fees, %w[OPT_REG_FEE INDEX_OPTION_FEE])
|
196
168
|
end
|
197
169
|
|
198
|
-
def
|
199
|
-
@
|
170
|
+
def fee_value
|
171
|
+
@fee || fee_total
|
200
172
|
end
|
201
173
|
|
202
|
-
def
|
203
|
-
@
|
174
|
+
def true_commission_value
|
175
|
+
@true_commission || (commission_total * 2)
|
204
176
|
end
|
205
177
|
|
206
178
|
def to_h
|
207
179
|
{
|
208
|
-
commission: commission,
|
209
|
-
fee: fee,
|
210
|
-
trueCommission: true_commission,
|
211
|
-
commissions: commissions,
|
212
|
-
fees: fees
|
180
|
+
commission: @commission,
|
181
|
+
fee: @fee,
|
182
|
+
trueCommission: @true_commission,
|
183
|
+
commissions: @commissions,
|
184
|
+
fees: @fees
|
213
185
|
}
|
214
186
|
end
|
215
187
|
|