schwab_rb 0.3.12 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b051569d86c70eaf117260fdc77f1926eb7c0bdf1141614903ac02f4bd834e6f
4
- data.tar.gz: 3d7f8e02b966002399f23faba75559561827e080d13257da36f219cef2019464
3
+ metadata.gz: 9855a77823d8f9039e402281cec2efba85bd1bdba9b7ff7404a8ad136a276631
4
+ data.tar.gz: fd0c1e0cc43e9c52c82e8f45fee14c919ea307b40c82195d8272ce921ca5defd
5
5
  SHA512:
6
- metadata.gz: 7076466bc502788b935e4fa239f44624a27c0a066c6b78be76af798efaa1181720710cd0b796b67a5a1003aac1d5b070739942a7cbed4b8daa0fe87b0c45bf01
7
- data.tar.gz: 272af18823441bc12f778213d9c2589e7f35b0001f349b72b8df47ac0ae2d9f65aa8c30e3088aadc78c774b479ceed72a474ccdd762c6486f4db99c447dfb746
6
+ metadata.gz: 1cd4e54dc2eace5a02ce328762f3ffaa4f23356684716d8d4987268c63d463b4cacbd51c8f278ec42cee8a73005bacd11a150db8e78beba3e34ae84c35e9e86e
7
+ data.tar.gz: 0b3f8920a5fd5c6256e5a3d69da6a65d54a781e8773b0a1a8981f88418444d2ee1243060f73b5c57dc4f63f61eea14aa420fcae57a70c98d5a0c0ed02929c7dd
data/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.4.0] - 2025-10-19
4
+
5
+ ### Added
6
+ - Account Management System: New `AccountHashManager` class for managing accounts via friendly names instead of encrypted account hashes
7
+ - One-Cancels-Other (OCO) Orders: Support for OCO order types with comprehensive examples
8
+ - Stop Limit Orders: Added support for stop limit order types
9
+ - Order type and duration as configurable parameters across all order types
10
+ - Quick Start Guide (doc/QUICK_START.md) for new users
11
+ - Account Management documentation (doc/ACCOUNT_MANAGEMENT.md) with detailed usage examples
12
+ - Place Order Samples documentation (doc/PLACE_ORDER_SAMPLES.md) with examples for all order types
13
+ - Example script for placing OCO orders (examples/place_oco_order.rb)
14
+ - Configuration options for account management paths
15
+
16
+ ### Changed
17
+ - Enhanced `BaseClient` with account name resolution - client methods can now accept account names or hashes
18
+ - Refactored order classes (IronCondorOrder, VerticalOrder, SingleOrder) to support order_type and duration parameters
19
+ - Improved `OrderFactory` to handle OCO and stop limit orders
20
+ - Updated bin/console with account hash manager initialization
21
+
22
+ ### Fixed
23
+ - Fixed parameter order in client method calls
24
+
3
25
  ## [0.2.0] - 2025-07-20
4
26
 
5
27
  ### Added
@@ -0,0 +1,297 @@
1
+ # Account Management Guide
2
+
3
+ The schwab_rb gem provides a secure and convenient way to manage multiple Schwab accounts using friendly account names instead of exposing account numbers or hashes in your code.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Overview](#overview)
8
+ - [Quick Start](#quick-start)
9
+ - [Configuration](#configuration)
10
+ - [Account Names Setup](#account-names-setup)
11
+ - [Using Account Names](#using-account-names)
12
+ - [Security Best Practices](#security-best-practices)
13
+ - [Troubleshooting](#troubleshooting)
14
+ - [API Reference](#api-reference)
15
+
16
+ ## Overview
17
+
18
+ Schwab's API requires account hashes for most operations, which:
19
+ - Change periodically (accounts get "rehashed")
20
+ - Are inconvenient to look up and copy
21
+ - Expose sensitive account numbers in code
22
+
23
+ schwab_rb provides an account name mapping system that:
24
+ - **Increases Security**: Account numbers never appear in your code
25
+ - **Improves Usability**: Reference accounts by friendly names like "my_trading_account"
26
+ - **Auto-Updates**: Account hashes refresh automatically
27
+ - **Handles Staleness**: Retries failed requests with fresh hashes
28
+
29
+ ## Quick Start
30
+
31
+ ### 1. Create Account Names File
32
+
33
+ Create `~/.schwab_rb/account_names.json` with your account mappings:
34
+
35
+ ```json
36
+ {
37
+ "my_trading_account": "12345678",
38
+ "my_ira": "87654321",
39
+ "my_roth_ira": "11223344"
40
+ }
41
+ ```
42
+
43
+ ### 2. Initialize Client and Fetch Hashes
44
+
45
+ ```ruby
46
+ require "schwab_rb"
47
+
48
+ # Initialize client
49
+ client = SchwabRb::Auth.init_client_easy(
50
+ ENV['SCHWAB_API_KEY'],
51
+ ENV['SCHWAB_APP_SECRET'],
52
+ ENV['SCHWAB_APP_CALLBACK_URL'],
53
+ ENV['SCHWAB_TOKEN_PATH']
54
+ )
55
+
56
+ # Fetch account numbers and populate hashes
57
+ client.get_account_numbers
58
+ ```
59
+
60
+ This automatically creates `~/.schwab_rb/account_hashes.json` with the name-to-hash mappings.
61
+
62
+ ### 3. Use Account Names in API Calls
63
+
64
+ ```ruby
65
+ # Get account by name
66
+ account = client.get_account(account_name: "my_trading_account")
67
+
68
+ # Place order using account name
69
+ order = client.place_order(order_spec, account_name: "my_ira")
70
+
71
+ # Get transactions
72
+ transactions = client.get_transactions(account_name: "my_roth_ira")
73
+ ```
74
+
75
+ ## Configuration
76
+
77
+ ### Default Paths
78
+
79
+ By default, schwab_rb uses:
80
+ - `schwab_home`: `~/.schwab_rb`
81
+ - `account_hashes_path`: `~/.schwab_rb/account_hashes.json`
82
+ - `account_names_path`: `~/.schwab_rb/account_names.json`
83
+
84
+ ### Custom Paths
85
+
86
+ #### Via Environment Variables
87
+
88
+ ```bash
89
+ export SCHWAB_HOME="/custom/path"
90
+ export SCHWAB_ACCOUNT_HASHES_PATH="/custom/path/hashes.json"
91
+ export SCHWAB_ACCOUNT_NAMES_PATH="/custom/path/names.json"
92
+ ```
93
+
94
+ #### Via Ruby Configuration
95
+
96
+ ```ruby
97
+ SchwabRb.configure do |config|
98
+ config.schwab_home = "/custom/path"
99
+ config.account_hashes_path = "/custom/path/hashes.json"
100
+ config.account_names_path = "/custom/path/names.json"
101
+ end
102
+ ```
103
+
104
+ ## Account Names Setup
105
+
106
+ ### File Format
107
+
108
+ `account_names.json` is a simple JSON object mapping friendly names to 8-digit account numbers:
109
+
110
+ ```json
111
+ {
112
+ "descriptive_name": "account_number"
113
+ }
114
+ ```
115
+
116
+ ## Using Account Names
117
+
118
+ ### Methods Supporting Account Names
119
+
120
+ All account-specific methods support both `account_hash` and `account_name`:
121
+
122
+ - `get_account(account_name:)` or `get_account(account_hash)`
123
+ - `get_order(order_id, account_name:)`
124
+ - `cancel_order(order_id, account_name:)`
125
+ - `get_account_orders(account_name:)`
126
+ - `place_order(order_spec, account_name:)`
127
+ - `replace_order(order_id, order_spec, account_name:)`
128
+ - `preview_order(order_spec, account_name:)`
129
+ - `get_transactions(account_name:)`
130
+ - `get_transaction(activity_id, account_name:)`
131
+
132
+ ### Calling Patterns
133
+
134
+ ```ruby
135
+ # Using account name (keyword argument) - RECOMMENDED
136
+ client.get_account(account_name: "my_trading_account")
137
+
138
+ # Using account hash (positional argument) - still works
139
+ client.get_account("ABC123HASH")
140
+
141
+ # Using account hash (keyword argument)
142
+ client.get_account(account_hash: "ABC123HASH")
143
+
144
+ # Priority: account_name always takes precedence
145
+ client.get_account("HASH1", account_name: "my_ira") # Uses "my_ira"
146
+ ```
147
+
148
+ ### List Available Accounts
149
+
150
+ ```ruby
151
+ # Get list of configured account names
152
+ names = client.available_account_names
153
+ # => ["my_trading_account", "my_ira", "my_roth_ira"]
154
+
155
+ puts "Available accounts:"
156
+ names.each { |name| puts " - #{name}" }
157
+ ```
158
+
159
+ ## Security
160
+
161
+ ### 1. Protect Your Files
162
+
163
+ The account files contain sensitive information:
164
+
165
+ ```bash
166
+ # Set restrictive permissions
167
+ chmod 600 ~/.schwab_rb/account_names.json
168
+ chmod 600 ~/.schwab_rb/account_hashes.json
169
+
170
+ # Verify permissions
171
+ ls -l ~/.schwab_rb/
172
+ ```
173
+
174
+ ### 2. Don't Commit to Version Control
175
+
176
+ Add to `.gitignore`:
177
+
178
+ ```
179
+ .schwab_rb/
180
+ **/account_names.json
181
+ **/account_hashes.json
182
+ ```
183
+
184
+ ### 3. Environment-Specific Configs
185
+
186
+ Use different configs per environment:
187
+
188
+ ```ruby
189
+ # In development
190
+ SchwabRb.configure do |config|
191
+ config.schwab_home = "~/.schwab_rb/development"
192
+ end
193
+
194
+ # In production
195
+ SchwabRb.configure do |config|
196
+ config.schwab_home = ENV['SCHWAB_CONFIG_PATH']
197
+ end
198
+ ```
199
+
200
+ ## Troubleshooting
201
+
202
+ ### Account Name Not Found
203
+
204
+ **Error**: `Account name 'my_account' not found in account hashes.`
205
+
206
+ **Solutions**:
207
+ 1. Run `client.get_account_numbers` to populate hashes
208
+ 2. Check spelling in `account_names.json`
209
+ 3. Verify account number is correct
210
+
211
+ ### Account Numbers File Missing
212
+
213
+ **Error**: `Account names file not found at ~/.schwab_rb/account_names.json`
214
+
215
+ **Solutions**:
216
+ 1. Create the file with your account mappings (see [Quick Start](#quick-start))
217
+ 2. Set custom path via config if file is elsewhere
218
+
219
+ ### Account Not in API Response
220
+
221
+ **Warning**: `Account 'old_account' (98765432) not found in API response.`
222
+
223
+ This warning means an account in your `account_names.json` wasn't returned by the API.
224
+
225
+ **Possible Causes**:
226
+ - Account was closed
227
+ - Wrong account number in `account_names.json`
228
+ - Account not accessible with current API credentials
229
+
230
+ **Actions**:
231
+ 1. Verify account status on Schwab website
232
+ 2. Check account number in `account_names.json`
233
+ 3. Remove closed accounts from config
234
+
235
+ ### Stale Account Hashes
236
+
237
+ The gem automatically handles stale hashes:
238
+ 1. Request fails with stale hash
239
+ 2. Calls `get_account_numbers` to refresh
240
+ 3. Retries request with new hash
241
+ 4. If still fails, raises original error
242
+
243
+ ## API Reference
244
+
245
+ ### Client Methods
246
+
247
+ #### `available_account_names`
248
+
249
+ Returns list of configured account names.
250
+
251
+ ```ruby
252
+ client.available_account_names
253
+ # => ["my_trading_account", "my_ira"]
254
+ ```
255
+
256
+ **Returns**: `Array<String>` - List of account names from `account_names.json`
257
+ **Returns**: `[]` - Empty array if file doesn't exist
258
+
259
+ #### Account-Specific Methods
260
+
261
+ All methods accept either `account_name:` or `account_hash`:
262
+
263
+ ```ruby
264
+ # Get account
265
+ client.get_account(account_name: "my_trading_account")
266
+ client.get_account("ABC123HASH")
267
+
268
+ # Get orders
269
+ client.get_account_orders(account_name: "my_ira", max_results: 10)
270
+
271
+ # Place order
272
+ client.place_order(order_spec, account_name: "my_trading_account")
273
+ ```
274
+
275
+ ### AccountHashManager
276
+
277
+ Direct access to the account hash manager:
278
+
279
+ ```ruby
280
+ manager = SchwabRb::AccountHashManager.new
281
+
282
+ # Get available names
283
+ manager.available_account_names
284
+ # => ["my_trading_account", "my_ira"]
285
+
286
+ # Get hash by name
287
+ manager.get_hash_by_name("my_trading_account")
288
+ # => "ABC123HASH"
289
+
290
+ # Get all hashes
291
+ manager.get_all_hashes
292
+ # => {"my_trading_account" => "ABC123HASH", "my_ira" => "XYZ789HASH"}
293
+ ```
294
+
295
+ ---
296
+
297
+ For more information, see the [main README](../README.md) or visit [github.com/jwplatta/schwab_rb](https://github.com/jwplatta/schwab_rb).
@@ -0,0 +1,301 @@
1
+ # Place Order Samples
2
+
3
+ Below, you will find examples specific to orders for use in the Schwab Trader API POST and PUT Order endpoints. Order entry will only be available for the assetType 'EQUIT' and 'OPTION as of this time.
4
+
5
+ Trader API applications (Individual and Commercial) are limited in the number of PUT/POST/DELETE order requests per minute per account based on the properties of the application specified during registration or follow-up process. Throttle limits for orders can be set from zero (0) to 120 requests per minute per account. Get order requests are unthrottled. Contact TraderAPI@schwab.com for further information.
6
+
7
+ Options and their Symbology:
8
+ Options symbols are broken down as:
9
+ Underlying Symbol (6 characters including spaces) | Expiration (6 characters) | Call/Put (1 character) | Strike Price (5+3=8 characters)
10
+
11
+ ```
12
+ Option Symbol: XYZ 210115C00050000
13
+ Stock Symbol: XYZ
14
+ Expiration: 2021/01/15
15
+ Type: Call
16
+ Strike Price: $50.00
17
+
18
+ Option Symbol: XYZ 210115C00055000
19
+ Stock Symbol: XYZ
20
+ Expiration: 2021/01/15
21
+ Type: Call
22
+ Strike Price: $55.00
23
+
24
+ Option Symbol: XYZ 210115C00062500
25
+ Stock Symbol: XYZ
26
+ Expiration: 2021/01/15
27
+ Type: Call
28
+ Strike Price: $62.50
29
+ ```
30
+
31
+ Instruction for EQUITY and OPTION
32
+
33
+ Instruction EQUITY (Stocks and ETFs) Option
34
+ BUY ACCEPTED REJECT
35
+ SELL ACCEPTED REJECT
36
+ BUY_TO_OPEN REJECT ACCEPTED
37
+ BUY_TO_COVER ACCEPTED REJECT
38
+ BUY_TO_CLOSE REJECT ACCEPTED
39
+ SELL_TO_OPEN REJECT ACCEPTED
40
+ SELL_SHORT ACCEPTED REJECT
41
+ SELL_TO_CLOSE REJECT ACCEPTED
42
+ Buy Market: Stock
43
+ Buy 15 shares of XYZ at the Market good for the Day.
44
+
45
+ ```json
46
+ {
47
+ "orderType": "MARKET",
48
+ "session": "NORMAL",
49
+ "duration": "DAY",
50
+ "orderStrategyType": "SINGLE",
51
+ "orderLegCollection": [
52
+ {
53
+ "instruction": "BUY",
54
+ "quantity": 15,
55
+ "instrument": {
56
+ "symbol": "XYZ",
57
+ "assetType": "EQUITY"
58
+ }
59
+ }
60
+ ]
61
+ }
62
+ ```
63
+
64
+ ## Buy Limit: Single Option
65
+
66
+ Buy to open 10 contracts of the XYZ March 15, 2024 $50 CALL at a Limit of $6.45 good for the Day.
67
+
68
+ ```json
69
+ {
70
+ "complexOrderStrategyType": "NONE",
71
+ "orderType": "LIMIT",
72
+ "session": "NORMAL",
73
+ "price": "6.45",
74
+ "duration": "DAY",
75
+ "orderStrategyType": "SINGLE",
76
+ "orderLegCollection": [
77
+ {
78
+ "instruction": "BUY_TO_OPEN",
79
+ "quantity": 10,
80
+ "instrument": {
81
+ "symbol": "XYZ 240315C00500000",
82
+ "assetType": "OPTION"
83
+ }
84
+ }
85
+ ]
86
+ }
87
+ ```
88
+
89
+ ## Buy Limit: Vertical Call Spread
90
+ Buy to open 2 contracts of the XYZ March 15, 2024 $45 Put and Sell to open 2 contract of the XYZ March 15, 2024 $43 Put at a LIMIT price of $0.10 good for the Day.
91
+
92
+ ```json
93
+ {
94
+ "orderType": "NET_DEBIT",
95
+ "session": "NORMAL",
96
+ "price": "0.10",
97
+ "duration": "DAY",
98
+ "orderStrategyType": "SINGLE",
99
+ "orderLegCollection": [
100
+ {
101
+ "instruction": "BUY_TO_OPEN",
102
+ "quantity": 2,
103
+ "instrument": {
104
+ "symbol": "XYZ 240315P00045000",
105
+ "assetType": "OPTION"
106
+ }
107
+ },
108
+ {
109
+ "instruction": "SELL_TO_OPEN",
110
+ "quantity": 2,
111
+ "instrument": {
112
+ "symbol": "XYZ 240315P00043000",
113
+ "assetType": "OPTION"
114
+ }
115
+ }
116
+ ]
117
+ }
118
+ ```
119
+
120
+ ## Conditional Order: One Triggers Another
121
+ Buy 10 shares of XYZ at a Limit price of $34.97 good for the Day. If filled, immediately submit an order to Sell 10 shares of XYZ with a Limit price of $42.03 good for the Day. Also known as 1st Trigger Sequence.
122
+
123
+ ```json
124
+ {
125
+ "orderType": "LIMIT",
126
+ "session": "NORMAL",
127
+ "price": "34.97",
128
+ "duration": "DAY",
129
+ "orderStrategyType": "TRIGGER",
130
+ "orderLegCollection": [
131
+ {
132
+ "instruction": "BUY",
133
+ "quantity": 10,
134
+ "instrument": {
135
+ "symbol": "XYZ",
136
+ "assetType": "EQUITY"
137
+ }
138
+ }
139
+ ],
140
+ "childOrderStrategies": [
141
+ {
142
+ "orderType": "LIMIT",
143
+ "session": "NORMAL",
144
+ "price": "42.03",
145
+ "duration": "DAY",
146
+ "orderStrategyType": "SINGLE",
147
+ "orderLegCollection": [
148
+ {
149
+ "instruction": "SELL",
150
+ "quantity": 10,
151
+ "instrument": {
152
+ "symbol": "XYZ",
153
+ "assetType": "EQUITY"
154
+ }
155
+ }
156
+ ]
157
+ }
158
+ ]
159
+ }
160
+ ```
161
+
162
+
163
+ ## Conditional Order: One Cancels Another
164
+
165
+ Sell 2 shares of XYZ at a Limit price of $45.97 and Sell 2 shares of XYZ with a Stop Limit order where the stop price is $37.03 and limit is $37.00. Both orders are sent at the same time. If one order fills, the other order is immediately cancelled. Both orders are good for the Day. Also known as an OCO order.
166
+
167
+ ```json
168
+ {
169
+ "orderStrategyType": "OCO",
170
+ "childOrderStrategies": [
171
+ {
172
+ "orderType": "LIMIT",
173
+ "session": "NORMAL",
174
+ "price": "45.97",
175
+ "duration": "DAY",
176
+ "orderStrategyType": "SINGLE",
177
+ "orderLegCollection": [
178
+ {
179
+ "instruction": "SELL",
180
+ "quantity": 2,
181
+ "instrument": {
182
+ "symbol": "XYZ",
183
+ "assetType": "EQUITY"
184
+ }
185
+ }
186
+ ]
187
+ },
188
+ {
189
+ "orderType": "STOP_LIMIT",
190
+ "session": "NORMAL",
191
+ "price": "37.00",
192
+ "stopPrice": "37.03",
193
+ "duration": "DAY",
194
+ "orderStrategyType": "SINGLE",
195
+ "orderLegCollection": [
196
+ {
197
+ "instruction": "SELL",
198
+ "quantity": 2,
199
+ "instrument": {
200
+ "symbol": "XYZ",
201
+ "assetType": "EQUITY"
202
+ }
203
+ }
204
+ ]
205
+ }
206
+ ]
207
+ }
208
+ ```
209
+
210
+
211
+ ## Conditional Order: One Triggers A One Cancels Another
212
+
213
+ Buy 5 shares of XYZ at a Limit price of $14.97 good for the Day. Once filled, 2 sell orders are immediately sent: Sell 5 shares of XYZ at a Limit price of $15.27 and Sell 5 shares of XYZ with a Stop order where the stop price is $11.27. If one of the sell orders fill, the other order is immediately cancelled. Both Sell orders are Good till Cancel. Also known as a 1st Trigger OCO order.
214
+
215
+ ```json
216
+ {
217
+ "orderStrategyType": "TRIGGER",
218
+ "session": "NORMAL",
219
+ "duration": "DAY",
220
+ "orderType": "LIMIT",
221
+ "price": 14.97,
222
+ "orderLegCollection": [
223
+ {
224
+ "instruction": "BUY",
225
+ "quantity": 5,
226
+ "instrument": {
227
+ "assetType": "EQUITY",
228
+ "symbol": "XYZ"
229
+ }
230
+ }
231
+ ],
232
+ "childOrderStrategies": [
233
+ {
234
+ "orderStrategyType": "OCO",
235
+ "childOrderStrategies": [
236
+ {
237
+ "orderStrategyType": "SINGLE",
238
+ "session": "NORMAL",
239
+ "duration": "GOOD_TILL_CANCEL",
240
+ "orderType": "LIMIT",
241
+ "price": 15.27,
242
+ "orderLegCollection": [
243
+ {
244
+ "instruction": "SELL",
245
+ "quantity": 5,
246
+ "instrument": {
247
+ "assetType": "EQUITY",
248
+ "symbol": "XYZ"
249
+ }
250
+ }
251
+ ]
252
+ },
253
+ {
254
+ "orderStrategyType": "SINGLE",
255
+ "session": "NORMAL",
256
+ "duration": "GOOD_TILL_CANCEL",
257
+ "orderType": "STOP",
258
+ "stopPrice": 11.27,
259
+ "orderLegCollection": [
260
+ {
261
+ "instruction": "SELL",
262
+ "quantity": 5,
263
+ "instrument": {
264
+ "assetType": "EQUITY",
265
+ "symbol": "XYZ"
266
+ }
267
+ }
268
+ ]
269
+ }
270
+ ]
271
+ }
272
+ ]
273
+ }
274
+ ```
275
+
276
+ ## Sell Trailing Stop: Stock
277
+
278
+ Sell 10 shares of XYZ with a Trailing Stop where the trail is a -$10 offset from the time the order is submitted. As the stock price goes up, the -$10 trailing offset will follow. If stock XYZ goes from $110 to $130, your trail will automatically be adjusted to $120. If XYZ falls to $120 or below, a Market order is submitted. This order is good for the Day.
279
+
280
+ ```json
281
+ {
282
+ "complexOrderStrategyType": "NONE",
283
+ "orderType": "TRAILING_STOP",
284
+ "session": "NORMAL",
285
+ "stopPriceLinkBasis": "BID",
286
+ "stopPriceLinkType": "VALUE",
287
+ "stopPriceOffset": 10,
288
+ "duration": "DAY",
289
+ "orderStrategyType": "SINGLE",
290
+ "orderLegCollection": [
291
+ {
292
+ "instruction": "SELL",
293
+ "quantity": 10,
294
+ "instrument": {
295
+ "symbol": "XYZ",
296
+ "assetType": "EQUITY"
297
+ }
298
+ }
299
+ ]
300
+ }
301
+ ```