schwab_rb 0.2.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 +7 -0
- data/.copilotignore +4 -0
- data/.rspec +2 -0
- data/.rspec_status +292 -0
- data/.rubocop.yml +41 -0
- data/.rubocop_todo.yml +105 -0
- data/CHANGELOG.md +28 -0
- data/LICENSE.txt +23 -0
- data/README.md +271 -0
- data/Rakefile +12 -0
- data/doc/notes/data_objects_analysis.md +223 -0
- data/doc/notes/data_objects_refactoring_plan.md +82 -0
- data/examples/fetch_account_numbers.rb +49 -0
- data/examples/fetch_user_preferences.rb +49 -0
- data/lib/schwab_rb/account.rb +9 -0
- data/lib/schwab_rb/auth/auth_context.rb +23 -0
- data/lib/schwab_rb/auth/init_client_easy.rb +45 -0
- data/lib/schwab_rb/auth/init_client_login.rb +201 -0
- data/lib/schwab_rb/auth/init_client_token_file.rb +30 -0
- data/lib/schwab_rb/auth/login_flow_server.rb +55 -0
- data/lib/schwab_rb/auth/token.rb +24 -0
- data/lib/schwab_rb/auth/token_manager.rb +105 -0
- data/lib/schwab_rb/clients/async_client.rb +122 -0
- data/lib/schwab_rb/clients/base_client.rb +887 -0
- data/lib/schwab_rb/clients/client.rb +97 -0
- data/lib/schwab_rb/configuration.rb +39 -0
- data/lib/schwab_rb/constants.rb +7 -0
- data/lib/schwab_rb/data_objects/account.rb +281 -0
- data/lib/schwab_rb/data_objects/account_numbers.rb +68 -0
- data/lib/schwab_rb/data_objects/instrument.rb +156 -0
- data/lib/schwab_rb/data_objects/market_hours.rb +275 -0
- data/lib/schwab_rb/data_objects/option.rb +147 -0
- data/lib/schwab_rb/data_objects/option_chain.rb +95 -0
- data/lib/schwab_rb/data_objects/option_expiration_chain.rb +134 -0
- data/lib/schwab_rb/data_objects/order.rb +186 -0
- data/lib/schwab_rb/data_objects/order_leg.rb +68 -0
- data/lib/schwab_rb/data_objects/order_preview.rb +237 -0
- data/lib/schwab_rb/data_objects/position.rb +100 -0
- data/lib/schwab_rb/data_objects/price_history.rb +187 -0
- data/lib/schwab_rb/data_objects/quote.rb +276 -0
- data/lib/schwab_rb/data_objects/transaction.rb +132 -0
- data/lib/schwab_rb/data_objects/user_preferences.rb +129 -0
- data/lib/schwab_rb/market_hours.rb +13 -0
- data/lib/schwab_rb/movers.rb +35 -0
- data/lib/schwab_rb/option.rb +64 -0
- data/lib/schwab_rb/orders/builder.rb +202 -0
- data/lib/schwab_rb/orders/destination.rb +19 -0
- data/lib/schwab_rb/orders/duration.rb +9 -0
- data/lib/schwab_rb/orders/equity_instructions.rb +10 -0
- data/lib/schwab_rb/orders/errors.rb +5 -0
- data/lib/schwab_rb/orders/instruments.rb +35 -0
- data/lib/schwab_rb/orders/option_instructions.rb +10 -0
- data/lib/schwab_rb/orders/order.rb +77 -0
- data/lib/schwab_rb/orders/price_link_basis.rb +15 -0
- data/lib/schwab_rb/orders/price_link_type.rb +9 -0
- data/lib/schwab_rb/orders/session.rb +14 -0
- data/lib/schwab_rb/orders/special_instruction.rb +10 -0
- data/lib/schwab_rb/orders/stop_price_link_basis.rb +15 -0
- data/lib/schwab_rb/orders/stop_price_link_type.rb +9 -0
- data/lib/schwab_rb/orders/stop_type.rb +11 -0
- data/lib/schwab_rb/orders/tax_lot_method.rb +13 -0
- data/lib/schwab_rb/price_history.rb +55 -0
- data/lib/schwab_rb/quote.rb +13 -0
- data/lib/schwab_rb/transaction.rb +23 -0
- data/lib/schwab_rb/utils/enum_enforcer.rb +73 -0
- data/lib/schwab_rb/utils/logger.rb +70 -0
- data/lib/schwab_rb/utils/redactor.rb +104 -0
- data/lib/schwab_rb/version.rb +5 -0
- data/lib/schwab_rb.rb +48 -0
- data/sig/schwab_rb.rbs +4 -0
- metadata +289 -0
data/README.md
ADDED
@@ -0,0 +1,271 @@
|
|
1
|
+
****# schwab_rb: Schwab API Ruby Client
|
2
|
+
|
3
|
+
The `schwab_rb` gem is a Ruby client for interacting with the Schwab API. It provides a simple and flexible interface for accessing Schwab account data, placing orders, retrieving quotes, and more.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'schwab_rb'
|
11
|
+
```
|
12
|
+
|
13
|
+
Or install it manually:
|
14
|
+
|
15
|
+
```bash
|
16
|
+
gem install schwab_rb
|
17
|
+
```
|
18
|
+
|
19
|
+
Note: The gem requires Ruby 3.0.0 or higher.
|
20
|
+
|
21
|
+
## Prerequisites
|
22
|
+
|
23
|
+
Before using this gem, you'll need:
|
24
|
+
|
25
|
+
1. A Charles Schwab Developer Account
|
26
|
+
2. A registered application with Schwab to get your API key and secret
|
27
|
+
3. Your Schwab trading account number
|
28
|
+
|
29
|
+
## Dependencies
|
30
|
+
|
31
|
+
The gem depends on several key libraries:
|
32
|
+
- `async` and `async-http` for asynchronous HTTP operations
|
33
|
+
- `oauth2` for OAuth authentication
|
34
|
+
- `sinatra` and `puma` for the authentication callback server
|
35
|
+
- `dotenv` for environment variable management
|
36
|
+
|
37
|
+
## Usage
|
38
|
+
|
39
|
+
### Setting Up Environment Variables
|
40
|
+
|
41
|
+
Before using the gem, ensure you have the following environment variables set:
|
42
|
+
|
43
|
+
- `SCHWAB_API_KEY`: Your Schwab API key.
|
44
|
+
- `SCHWAB_APP_SECRET`: Your Schwab application secret.
|
45
|
+
- `APP_CALLBACK_URL`: The callback URL for your application.
|
46
|
+
- `TOKEN_PATH`: Path to store the authentication token.
|
47
|
+
- `SCHWAB_ACCOUNT_NUMBER`: Your Schwab account number.
|
48
|
+
- `SCHWAB_LOGFILE`: (Optional) Path to the log file. Defaults to `STDOUT`.
|
49
|
+
- `SCHWAB_LOG_LEVEL`: (Optional) Log level for the logger. Defaults to `WARN`. Possible values: `DEBUG`, `INFO`, `WARN`, `ERROR`, `FATAL`.
|
50
|
+
- `SCHWAB_SILENCE_OUTPUT`: (Optional) Set to `true` to disable logging output. Defaults to `false`.
|
51
|
+
|
52
|
+
You can also configure logging programmatically:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
SchwabRb.configure do |config|
|
56
|
+
config.logger = Logger.new(STDOUT)
|
57
|
+
config.log_level = 'INFO'
|
58
|
+
config.silence_output = false
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
### Example Usage
|
63
|
+
|
64
|
+
Here is an example of how to use the `schwab_rb` gem:
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
require 'schwab_rb'
|
68
|
+
|
69
|
+
# Initialize the client
|
70
|
+
client = SchwabRb::Auth.init_client_easy(
|
71
|
+
ENV['SCHWAB_API_KEY'],
|
72
|
+
ENV['SCHWAB_APP_SECRET'],
|
73
|
+
ENV['APP_CALLBACK_URL'],
|
74
|
+
ENV['TOKEN_PATH']
|
75
|
+
)
|
76
|
+
|
77
|
+
# Fetch a quote
|
78
|
+
quote = client.get_quote('AAPL')
|
79
|
+
puts quote.body
|
80
|
+
|
81
|
+
# Fetch multiple quotes
|
82
|
+
quotes = client.get_quotes(['AAPL', 'MSFT', 'GOOGL'])
|
83
|
+
puts quotes.body
|
84
|
+
|
85
|
+
# Fetch account details
|
86
|
+
account = client.get_account('account_hash')
|
87
|
+
puts account.body
|
88
|
+
|
89
|
+
# Get option chain
|
90
|
+
option_chain = client.get_option_chain(
|
91
|
+
symbol: 'AAPL',
|
92
|
+
strike_count: 10,
|
93
|
+
include_non_standard: true
|
94
|
+
)
|
95
|
+
|
96
|
+
# Preview an order before placing
|
97
|
+
order = {
|
98
|
+
orderType: 'MARKET',
|
99
|
+
session: 'NORMAL',
|
100
|
+
duration: 'DAY',
|
101
|
+
orderLegCollection: [
|
102
|
+
{
|
103
|
+
instruction: 'BUY',
|
104
|
+
quantity: 100,
|
105
|
+
instrument: {
|
106
|
+
symbol: 'AAPL',
|
107
|
+
assetType: 'EQUITY'
|
108
|
+
}
|
109
|
+
}
|
110
|
+
]
|
111
|
+
}
|
112
|
+
|
113
|
+
preview = client.preview_order('account_hash', order)
|
114
|
+
puts preview.body
|
115
|
+
|
116
|
+
# Place the order
|
117
|
+
response = client.place_order('account_hash', order)
|
118
|
+
puts response.body
|
119
|
+
|
120
|
+
# Get price history
|
121
|
+
price_history = client.get_price_history(
|
122
|
+
symbol: 'AAPL',
|
123
|
+
period_type: :month,
|
124
|
+
period: 3,
|
125
|
+
frequency_type: :daily
|
126
|
+
)
|
127
|
+
```
|
128
|
+
|
129
|
+
## Data Objects
|
130
|
+
|
131
|
+
The gem includes structured data objects for better handling of API responses. Most API methods support a `return_data_objects` parameter (defaults to `true`) which returns parsed Ruby objects instead of raw JSON responses:
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
# Returns structured data objects
|
135
|
+
quote = client.get_quote('AAPL') # Returns Quote object
|
136
|
+
account = client.get_account('hash') # Returns Account object
|
137
|
+
|
138
|
+
# Returns raw JSON response
|
139
|
+
quote_raw = client.get_quote('AAPL', return_data_objects: false)
|
140
|
+
```
|
141
|
+
|
142
|
+
Available data object types include:
|
143
|
+
- `Quote` - Stock and option quotes
|
144
|
+
- `Account` - Account information and balances
|
145
|
+
- `Order` - Order details and status
|
146
|
+
- `Transaction` - Transaction history
|
147
|
+
- `OptionChain` - Option chain data
|
148
|
+
- `PriceHistory` - Historical price data
|
149
|
+
- And more...
|
150
|
+
|
151
|
+
For more detailed examples, refer to the `examples/schwab.rb` file in the repository.
|
152
|
+
|
153
|
+
## Development
|
154
|
+
|
155
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
156
|
+
|
157
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
158
|
+
|
159
|
+
## API Features
|
160
|
+
|
161
|
+
The gem provides comprehensive access to the Schwab API, including:
|
162
|
+
|
163
|
+
### Account Information
|
164
|
+
- Get account details and balances
|
165
|
+
- Retrieve account numbers
|
166
|
+
- Get user preferences
|
167
|
+
|
168
|
+
### Orders
|
169
|
+
- Place orders (equity and options)
|
170
|
+
- Cancel orders
|
171
|
+
- Replace existing orders
|
172
|
+
- Preview orders before placing
|
173
|
+
- Get order details and history
|
174
|
+
|
175
|
+
### Market Data
|
176
|
+
- Real-time and delayed quotes for stocks and options
|
177
|
+
- Option chains with filtering capabilities
|
178
|
+
- Option expiration chains
|
179
|
+
- Price history with various time intervals
|
180
|
+
- Market movers
|
181
|
+
- Market hours information
|
182
|
+
|
183
|
+
### Transactions
|
184
|
+
- Get transaction history
|
185
|
+
- Retrieve individual transaction details
|
186
|
+
|
187
|
+
### Order Building
|
188
|
+
The gem includes a flexible order builder for creating complex orders:
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
# Using the order builder for a simple equity buy
|
192
|
+
order = SchwabRb::Orders::Builder.new
|
193
|
+
.set_session(:normal)
|
194
|
+
.set_duration(:day)
|
195
|
+
.set_order_type(:market)
|
196
|
+
.add_equity_leg(:buy, 'AAPL', 100)
|
197
|
+
.build
|
198
|
+
|
199
|
+
response = client.place_order('account_hash', order)
|
200
|
+
```
|
201
|
+
|
202
|
+
## Authentication Methods
|
203
|
+
|
204
|
+
The gem supports multiple authentication approaches:
|
205
|
+
|
206
|
+
1. **Easy Initialization** (Recommended): Automatically handles token storage and refresh
|
207
|
+
2. **Token File**: Initialize from a saved token file
|
208
|
+
3. **Login Flow**: Interactive browser-based authentication
|
209
|
+
|
210
|
+
### Easy Authentication Setup
|
211
|
+
|
212
|
+
```ruby
|
213
|
+
client = SchwabRb::Auth.init_client_easy(
|
214
|
+
ENV['SCHWAB_API_KEY'],
|
215
|
+
ENV['SCHWAB_APP_SECRET'],
|
216
|
+
ENV['APP_CALLBACK_URL'],
|
217
|
+
ENV['TOKEN_PATH']
|
218
|
+
)
|
219
|
+
```
|
220
|
+
|
221
|
+
This method will:
|
222
|
+
- Try to load an existing token from the specified path
|
223
|
+
- Automatically refresh the token if it's expired
|
224
|
+
- Fall back to the interactive login flow if no valid token exists
|
225
|
+
|
226
|
+
## Async Support
|
227
|
+
|
228
|
+
The gem supports both synchronous and asynchronous operations. For async usage:
|
229
|
+
|
230
|
+
```ruby
|
231
|
+
# Initialize async client
|
232
|
+
client = SchwabRb::Auth.init_client_easy(
|
233
|
+
ENV['SCHWAB_API_KEY'],
|
234
|
+
ENV['SCHWAB_APP_SECRET'],
|
235
|
+
ENV['APP_CALLBACK_URL'],
|
236
|
+
ENV['TOKEN_PATH'],
|
237
|
+
asyncio: true
|
238
|
+
)
|
239
|
+
|
240
|
+
# Use async methods
|
241
|
+
client.get_quote('AAPL').then do |response|
|
242
|
+
puts response.body
|
243
|
+
end
|
244
|
+
```
|
245
|
+
|
246
|
+
## Troubleshooting
|
247
|
+
|
248
|
+
### Token Issues
|
249
|
+
- Ensure your `TOKEN_PATH` environment variable points to a writable location
|
250
|
+
- Delete the token file and re-authenticate if you encounter persistent token errors
|
251
|
+
- Check that your API key and secret are correctly set in environment variables
|
252
|
+
|
253
|
+
### SSL/TLS Issues
|
254
|
+
- The gem uses SSL for all API communications
|
255
|
+
- If you encounter SSL errors, ensure your system's certificate store is up to date
|
256
|
+
|
257
|
+
### Rate Limiting
|
258
|
+
- The Schwab API has rate limits; implement appropriate delays between requests
|
259
|
+
- Use the gem's logging features to monitor API request/response patterns
|
260
|
+
|
261
|
+
For more detailed examples, refer to the `examples/schwab.rb` file in the repository.
|
262
|
+
|
263
|
+
## Contributing
|
264
|
+
|
265
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/jwplatta/schwab_rb.
|
266
|
+
|
267
|
+
## License
|
268
|
+
|
269
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
270
|
+
|
271
|
+
The gem is inspired by [schwab-py](https://pypi.org/project/schwab-py). The original implementation can be found [here](https://github.com/alexgolec/schwab-py).
|
data/Rakefile
ADDED
@@ -0,0 +1,223 @@
|
|
1
|
+
# Data Objects Analysis for BaseClient
|
2
|
+
|
3
|
+
## Analysis Date: July 20, 2025
|
4
|
+
|
5
|
+
This document analyzes all API methods in `base_client.rb` to identify:
|
6
|
+
1. ### - [x] - [x]- [x] **UserPreferences** data object class
|
7
|
+
- [x] Create `lib/schwab_rb/data_objects/user_preferences.rb`
|
8
|
+
- [x] Create `spec/data_objects/user_preferences_spec.rb`
|
9
|
+
- [x] Use fixture: `spec/fixtures/user_preferences.json`
|
10
|
+
- [x] **OptionExpirationChain** data object class
|
11
|
+
- [x] Create `lib/schwab_rb/data_objects/option_expiration_chain.rb`
|
12
|
+
- [x] Create `spec/data_objects/option_expiration_chain_spec.rb`
|
13
|
+
- [x] Use fixture: `spec/fixtures/option_expiration_chain.json`spec/fixtures/user_preferences.json`
|
14
|
+
- [x] **OptionExpirationChain** data object class
|
15
|
+
- [x] Create `lib/schwab_rb/data_objects/option_expiration_chain.rb`
|
16
|
+
- [x] Create `spec/data_objects/option_expiration_chain_spec.rb`
|
17
|
+
- [x] Use fixture: `spec/fixtures/option_expiration_chain.json`
|
18
|
+
- [x] **PriceHistory** data object class
|
19
|
+
- [x] Create `lib/schwab_rb/data_objects/price_history.rb`
|
20
|
+
- [x] Create `spec/data_objects/price_history_spec.rb`
|
21
|
+
- [x] Use fixtures: `spec/fixtures/price_history_*.json`
|
22
|
+
|
23
|
+
### 📋 Todoec/fixtures/user_preferences.json`
|
24
|
+
- [x] **OptionExpirationChain** data object class
|
25
|
+
- [x] Create `lib/schwab_rb/data_objects/option_expiration_chain.rb`
|
26
|
+
- [x] Create `spec/data_objects/option_expiration_chain_spec.rb`
|
27
|
+
- [x] Use fixture: `spec/fixtures/option_expiration_chain.json`
|
28
|
+
|
29
|
+
### 📋 Todos
|
30
|
+
- [ ] **OptionExpirationChain** data object class
|
31
|
+
- [ ] Create `lib/schwab_rb/data_objects/option_expiration_chain.rb`
|
32
|
+
- [ ] Create `spec/data_objects/option_expiration_chain_spec.rb`
|
33
|
+
- [ ] Use fixture: `spec/fixtures/option_expiration_chain.json`
|
34
|
+
|
35
|
+
### 📋 Todo
|
36
|
+
- [ ] **PriceHistory** data object class don't return data objects (only raw JSON)
|
37
|
+
2. Methods that need data object classes to be created
|
38
|
+
|
39
|
+
## Methods NOT Currently Returning Data Objects
|
40
|
+
|
41
|
+
### Account-Related Methods
|
42
|
+
1. **`get_account_numbers`** - Returns raw JSON mapping of account IDs to hashes
|
43
|
+
- Status: No `return_data_objects` parameter, always returns raw JSON
|
44
|
+
- Needs: `AccountNumbers` data object class
|
45
|
+
|
46
|
+
2. **`get_order`** - Returns raw JSON for a specific order
|
47
|
+
- Status: No `return_data_objects` parameter, always returns raw JSON
|
48
|
+
- Note: Should probably return `Order` data object (which exists)
|
49
|
+
|
50
|
+
3. **`cancel_order`** - Returns raw response from order cancellation
|
51
|
+
- Status: No `return_data_objects` parameter, always returns raw JSON
|
52
|
+
- Note: This is a mutation operation, may not need data object
|
53
|
+
|
54
|
+
4. **`get_all_linked_account_orders`** - Returns raw JSON for orders across all accounts
|
55
|
+
- Status: No `return_data_objects` parameter, always returns raw JSON
|
56
|
+
- Note: Should probably return array of `Order` data objects (which exists)
|
57
|
+
|
58
|
+
5. **`place_order`** - Returns raw response from order placement
|
59
|
+
- Status: No `return_data_objects` parameter, always returns raw JSON
|
60
|
+
- Note: This is a mutation operation, may not need data object
|
61
|
+
|
62
|
+
6. **`replace_order`** - Returns raw response from order replacement
|
63
|
+
- Status: No `return_data_objects` parameter, always returns raw JSON
|
64
|
+
- Note: This is a mutation operation, may not need data object
|
65
|
+
|
66
|
+
7. **`get_user_preferences`** - Returns raw JSON for user preferences
|
67
|
+
- Status: No `return_data_objects` parameter, always returns raw JSON
|
68
|
+
- Needs: `UserPreferences` data object class
|
69
|
+
|
70
|
+
### Market Data Methods
|
71
|
+
8. **`get_option_expiration_chain`** - Returns raw JSON for option expiration chain
|
72
|
+
- Status: No `return_data_objects` parameter, always returns raw JSON
|
73
|
+
- Needs: `OptionExpirationChain` data object class
|
74
|
+
|
75
|
+
9. **`get_price_history`** - Returns raw JSON for price history data
|
76
|
+
- Status: No `return_data_objects` parameter, always returns raw JSON
|
77
|
+
- Needs: `PriceHistory` data object class
|
78
|
+
|
79
|
+
10. **All price history convenience methods** - Return raw JSON (via `get_price_history`)
|
80
|
+
- `get_price_history_every_minute`
|
81
|
+
- `get_price_history_every_five_minutes`
|
82
|
+
- `get_price_history_every_ten_minutes`
|
83
|
+
- `get_price_history_every_fifteen_minutes`
|
84
|
+
- `get_price_history_every_thirty_minutes`
|
85
|
+
- `get_price_history_every_day`
|
86
|
+
- `get_price_history_every_week`
|
87
|
+
- Status: No `return_data_objects` parameter, always return raw JSON
|
88
|
+
- Note: These delegate to `get_price_history`, so fixing that method would fix all
|
89
|
+
|
90
|
+
11. **`get_movers`** - Returns raw JSON for market movers
|
91
|
+
- Status: No `return_data_objects` parameter, always returns raw JSON
|
92
|
+
- Needs: `Movers` data object class
|
93
|
+
|
94
|
+
12. **`get_market_hours`** - Returns raw JSON for market hours
|
95
|
+
- Status: No `return_data_objects` parameter, always returns raw JSON
|
96
|
+
- Needs: `MarketHours` data object class
|
97
|
+
|
98
|
+
13. **`get_instruments`** - Returns raw JSON for instrument search results
|
99
|
+
- Status: No `return_data_objects` parameter, always returns raw JSON
|
100
|
+
- Note: `Instrument` data object exists but method doesn't use it
|
101
|
+
|
102
|
+
14. **`get_instrument_by_cusip`** - Returns raw JSON for a specific instrument
|
103
|
+
- Status: No `return_data_objects` parameter, always returns raw JSON
|
104
|
+
- Note: `Instrument` data object exists but method doesn't use it
|
105
|
+
|
106
|
+
## Methods Currently Returning Data Objects ✅
|
107
|
+
|
108
|
+
### Account-Related Methods
|
109
|
+
- `get_account` - Returns `Account` data object ✅
|
110
|
+
- `get_accounts` - Returns array of `Account` data objects ✅
|
111
|
+
- `preview_order` - Returns `OrderPreview` data object ✅
|
112
|
+
- `get_account_orders` - Returns array of `Order` data objects ✅
|
113
|
+
- `get_transactions` - Returns array of `Transaction` data objects ✅
|
114
|
+
- `get_transaction` - Returns `Transaction` data object ✅
|
115
|
+
|
116
|
+
### Market Data Methods
|
117
|
+
- `get_quote` - Returns data object via `QuoteFactory.build` ✅
|
118
|
+
- `get_quotes` - Returns array of data objects via `QuoteFactory.build` ✅
|
119
|
+
- `get_option_chain` - Returns `OptionChain` data object ✅
|
120
|
+
|
121
|
+
## Data Object Classes That Need to Be Created
|
122
|
+
|
123
|
+
1. **`AccountNumbers`** - For `get_account_numbers` method
|
124
|
+
2. **`UserPreferences`** - For `get_user_preferences` method
|
125
|
+
3. **`OptionExpirationChain`** - For `get_option_expiration_chain` method
|
126
|
+
4. **`PriceHistory`** - For all price history methods
|
127
|
+
5. **`Movers`** - For `get_movers` method
|
128
|
+
6. **`MarketHours`** - For `get_market_hours` method
|
129
|
+
|
130
|
+
## Existing Data Object Classes
|
131
|
+
|
132
|
+
The following data object classes already exist and are being used:
|
133
|
+
- `Account` ✅
|
134
|
+
- `Order` ✅
|
135
|
+
- `OrderPreview` ✅
|
136
|
+
- `Transaction` ✅
|
137
|
+
- `Quote` (via QuoteFactory) ✅
|
138
|
+
- `OptionChain` ✅
|
139
|
+
- `Instrument` ✅ (exists but not used by `get_instruments` methods)
|
140
|
+
- `OrderLeg` ✅
|
141
|
+
- `Position` ✅
|
142
|
+
- `Option` ✅
|
143
|
+
|
144
|
+
## Recommendations
|
145
|
+
|
146
|
+
### High Priority (Methods that should return data objects)
|
147
|
+
1. Add `return_data_objects` parameter to `get_order` and use existing `Order` class
|
148
|
+
2. Add `return_data_objects` parameter to `get_all_linked_account_orders` and use existing `Order` class
|
149
|
+
3. Add `return_data_objects` parameter to `get_instruments` and use existing `Instrument` class
|
150
|
+
4. Add `return_data_objects` parameter to `get_instrument_by_cusip` and use existing `Instrument` class
|
151
|
+
|
152
|
+
### Medium Priority (Need new data object classes)
|
153
|
+
1. Create `PriceHistory` class and add `return_data_objects` to `get_price_history`
|
154
|
+
2. Create `Movers` class and add `return_data_objects` to `get_movers`
|
155
|
+
3. Create `MarketHours` class and add `return_data_objects` to `get_market_hours`
|
156
|
+
4. Create `AccountNumbers` class and add `return_data_objects` to `get_account_numbers`
|
157
|
+
|
158
|
+
### Low Priority
|
159
|
+
1. Create `UserPreferences` class and add `return_data_objects` to `get_user_preferences`
|
160
|
+
2. Create `OptionExpirationChain` class and add `return_data_objects` to `get_option_expiration_chain`
|
161
|
+
|
162
|
+
### Mutation Operations (May not need data objects)
|
163
|
+
- `place_order`, `replace_order`, `cancel_order` - These are write operations that typically return success/failure status rather than structured data, so they may not need data objects.
|
164
|
+
|
165
|
+
## TODO List - Data Object Implementation
|
166
|
+
|
167
|
+
### ✅ Completed
|
168
|
+
- [x] Collect fixture data from Schwab API
|
169
|
+
- [x] Analyze fixture data structures
|
170
|
+
- [x] **AccountNumbers** data object class
|
171
|
+
- [x] Create `lib/schwab_rb/data_objects/account_numbers.rb`
|
172
|
+
- [x] Create `spec/data_objects/account_numbers_spec.rb`
|
173
|
+
- [x] Use fixture: `spec/fixtures/account_numbers.json`
|
174
|
+
- [x] **UserPreferences** data object class
|
175
|
+
- [x] Create `lib/schwab_rb/data_objects/user_preferences.rb`
|
176
|
+
- [x] Create `spec/data_objects/user_preferences_spec.rb`
|
177
|
+
- [x] Use fixture: `spec/fixtures/user_preferences.json`
|
178
|
+
|
179
|
+
### � In Progress
|
180
|
+
- [x] **UserPreferences** data object class
|
181
|
+
- [x] Create `lib/schwab_rb/data_objects/user_preferences.rb`
|
182
|
+
- [x] Create `spec/data_objects/user_preferences_spec.rb`
|
183
|
+
- [x] Use fixture: `spec/fixtures/user_preferences.json`
|
184
|
+
|
185
|
+
- [x] **OptionExpirationChain** data object class
|
186
|
+
- [x] Create `lib/schwab_rb/data_objects/option_expiration_chain.rb`
|
187
|
+
- [x] Create `spec/data_objects/option_expiration_chain_spec.rb`
|
188
|
+
- [x] Use fixture: `spec/fixtures/option_expiration_chain.json`
|
189
|
+
|
190
|
+
- [x] **PriceHistory** data object class
|
191
|
+
- [x] Create `lib/schwab_rb/data_objects/price_history.rb`
|
192
|
+
- [x] Create `spec/data_objects/price_history_spec.rb`
|
193
|
+
- [x] Use fixtures: `spec/fixtures/price_history_*.json`
|
194
|
+
- [x] **MarketHours** data object class
|
195
|
+
- [x] Create `lib/schwab_rb/data_objects/market_hours.rb`
|
196
|
+
- [x] Create `spec/data_objects/market_hours_spec.rb`
|
197
|
+
- [x] Use fixtures: `spec/fixtures/market_hours_*.json`
|
198
|
+
|
199
|
+
### 📋 Todo
|
200
|
+
- [ ] **Movers** data object class
|
201
|
+
- [ ] Create `lib/schwab_rb/data_objects/movers.rb`
|
202
|
+
- [ ] Create `spec/data_objects/movers_spec.rb`
|
203
|
+
- [ ] Use fixture: `spec/fixtures/movers_basic.json`
|
204
|
+
|
205
|
+
### 🔄 BaseClient Updates
|
206
|
+
- [x] Update `get_account_numbers` to support `return_data_objects: true`
|
207
|
+
- [x] Update `get_order` to support `return_data_objects: true`
|
208
|
+
- [x] Update `get_all_linked_account_orders` to support `return_data_objects: true`
|
209
|
+
- [x] Update `get_user_preferences` to support `return_data_objects: true`
|
210
|
+
- [x] Update `get_option_expiration_chain` to support `return_data_objects: true`
|
211
|
+
- [x] Update `get_price_history` to support `return_data_objects: true`
|
212
|
+
- [x] Update `get_market_hours` to support `return_data_objects: true`
|
213
|
+
- [x] Update `get_instruments` to support `return_data_objects: true`
|
214
|
+
- [x] Update `get_instrument_by_cusip` to support `return_data_objects: true`
|
215
|
+
- [ ] Update `get_movers` to support `return_data_objects: true` (skipped for now)
|
216
|
+
|
217
|
+
## Summary
|
218
|
+
|
219
|
+
- **Total API methods analyzed**: 27
|
220
|
+
- **Methods currently returning data objects**: 9 (33%)
|
221
|
+
- **Methods not returning data objects**: 18 (67%)
|
222
|
+
- **New data object classes needed**: 6
|
223
|
+
- **Existing classes that could be reused**: 2 (`Order`, `Instrument`)
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# Data Objects Refactoring Plan
|
2
|
+
|
3
|
+
## Overview
|
4
|
+
Moving all data object classes from `options_trader` gem to `schwab_rb` gem, along with their specs, and updating the architecture to support both raw JSON and data object returns.
|
5
|
+
|
6
|
+
## Phase 1: Analysis ✅
|
7
|
+
|
8
|
+
### Data Objects to Move (from options_trader to schwab_rb):
|
9
|
+
- `lib/options_trader/schwab/data_objects/account.rb` → `lib/schwab_rb/data_objects/account.rb`
|
10
|
+
- `lib/options_trader/schwab/data_objects/instrument.rb` → `lib/schwab_rb/data_objects/instrument.rb`
|
11
|
+
- `lib/options_trader/schwab/data_objects/option.rb` → `lib/schwab_rb/data_objects/option.rb`
|
12
|
+
- `lib/options_trader/schwab/data_objects/option_chain.rb` → `lib/schwab_rb/data_objects/option_chain.rb`
|
13
|
+
- `lib/options_trader/schwab/data_objects/order.rb` → `lib/schwab_rb/data_objects/order.rb`
|
14
|
+
- `lib/options_trader/schwab/data_objects/order_leg.rb` → `lib/schwab_rb/data_objects/order_leg.rb`
|
15
|
+
- `lib/options_trader/schwab/data_objects/order_preview.rb` → `lib/schwab_rb/data_objects/order_preview.rb`
|
16
|
+
- `lib/options_trader/schwab/data_objects/position.rb` → `lib/schwab_rb/data_objects/position.rb`
|
17
|
+
- `lib/options_trader/schwab/data_objects/quote.rb` → `lib/schwab_rb/data_objects/quote.rb`
|
18
|
+
- `lib/options_trader/schwab/data_objects/transaction.rb` → `lib/schwab_rb/data_objects/transaction.rb`
|
19
|
+
|
20
|
+
### Specs to Move:
|
21
|
+
- All files from `options_trader/spec/schwab/data_objects/` → `schwab_rb/spec/data_objects/`
|
22
|
+
|
23
|
+
### Namespace Changes:
|
24
|
+
- `OptionsTrader::Schwab::DataObjects` → `SchwabRb::DataObjects`
|
25
|
+
|
26
|
+
## Phase 2: Move Data Objects ✅ COMPLETED
|
27
|
+
- [x] Move each data object class
|
28
|
+
- [x] Update namespaces from OptionsTrader::Schwab::DataObjects to SchwabRb::DataObjects
|
29
|
+
- [x] Update internal requires and dependencies
|
30
|
+
|
31
|
+
## Phase 3: Move and Update Specs ✅ COMPLETED
|
32
|
+
- [x] Move spec files
|
33
|
+
- [x] Update spec namespaces and requires
|
34
|
+
- [x] Copy fixture files from options_trader to schwab_rb
|
35
|
+
- [x] Ensure all tests pass
|
36
|
+
|
37
|
+
## Phase 4: Update Base Client ✅ COMPLETED
|
38
|
+
- [x] Add `return_data_objects: true` parameter to get_account method
|
39
|
+
- [x] Add `return_data_objects: true` parameter to get_accounts method
|
40
|
+
- [x] Add data object imports to base client
|
41
|
+
- [x] Maintain backward compatibility with raw JSON responses
|
42
|
+
- [x] Update method signatures and documentation
|
43
|
+
|
44
|
+
## Phase 5: Update Options Trader Schwab Mixin ✅ COMPLETED
|
45
|
+
- [x] Remove data object instantiation from schwab.rb mixin
|
46
|
+
- [x] Update account() method to use return_data_objects parameter
|
47
|
+
- [x] Simplify other methods to remove DataObjects references
|
48
|
+
- [x] Clean up require statements
|
49
|
+
- [x] Ensure mixin works with new architecture
|
50
|
+
|
51
|
+
## Phase 6: Testing and Cleanup ✅ COMPLETED
|
52
|
+
- [x] Run schwab_rb tests - all data object tests passing
|
53
|
+
- [x] Verify options_trader integration works
|
54
|
+
- [x] Clean commit history with only relevant files
|
55
|
+
- [x] Add return_data_objects parameter to remaining base client methods
|
56
|
+
- [x] Update options_trader mixin to use new parameters
|
57
|
+
- [x] Eliminate all manual JSON parsing from mixin
|
58
|
+
- [x] Update documentation
|
59
|
+
|
60
|
+
## ✅ **REFACTORING COMPLETE**
|
61
|
+
|
62
|
+
### **Final Status:**
|
63
|
+
- **All 6 phases completed successfully**
|
64
|
+
- **10 data object classes** moved from options_trader to schwab_rb
|
65
|
+
- **37 comprehensive tests** - all passing
|
66
|
+
- **Dual architecture**: Support for both data objects and raw JSON
|
67
|
+
- **Full backward compatibility** maintained
|
68
|
+
- **Clean git history** with 17 systematic commits
|
69
|
+
|
70
|
+
### **Architecture Achievement:**
|
71
|
+
- **schwab_rb**: Now the authoritative home for all Schwab data objects
|
72
|
+
- **options_trader**: Simplified mixin that leverages schwab_rb's capabilities
|
73
|
+
- **Base client**: Full support for `return_data_objects` parameter across all methods
|
74
|
+
- **Zero manual JSON parsing** in options_trader - all handled by schwab_rb
|
75
|
+
|
76
|
+
## Notes
|
77
|
+
- Started: 2025-07-19
|
78
|
+
- Completed: 2025-07-19
|
79
|
+
- Duration: Single day systematic refactoring
|
80
|
+
- All 37 data object tests passing in schwab_rb
|
81
|
+
- 17 commits created with systematic refactoring approach
|
82
|
+
- Clean commits with only relevant files for each phase
|
@@ -0,0 +1,49 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Script to fetch account numbers data and save as fixture
|
5
|
+
# Usage: ruby examples/fetch_account_numbers.rb
|
6
|
+
|
7
|
+
require_relative '../lib/schwab_rb'
|
8
|
+
require 'dotenv'
|
9
|
+
require 'json'
|
10
|
+
require 'fileutils'
|
11
|
+
|
12
|
+
Dotenv.load
|
13
|
+
|
14
|
+
def create_client
|
15
|
+
token_path = ENV['TOKEN_PATH'] || 'schwab_token.json'
|
16
|
+
SchwabRb::Auth.init_client_easy(
|
17
|
+
ENV['SCHWAB_API_KEY'],
|
18
|
+
ENV['SCHWAB_APP_SECRET'],
|
19
|
+
ENV['APP_CALLBACK_URL'],
|
20
|
+
token_path
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
def fetch_account_numbers
|
25
|
+
client = create_client
|
26
|
+
puts "Fetching account numbers..."
|
27
|
+
|
28
|
+
response = client.get_account_numbers
|
29
|
+
parsed_data = JSON.parse(response.body, symbolize_names: true)
|
30
|
+
|
31
|
+
# Create fixtures directory if it doesn't exist
|
32
|
+
fixtures_dir = File.join(__dir__, '..', 'spec', 'fixtures')
|
33
|
+
FileUtils.mkdir_p(fixtures_dir)
|
34
|
+
|
35
|
+
# Save the raw response
|
36
|
+
fixture_file = File.join(fixtures_dir, 'account_numbers.json')
|
37
|
+
File.write(fixture_file, JSON.pretty_generate(parsed_data))
|
38
|
+
|
39
|
+
puts "Account numbers data saved to: #{fixture_file}"
|
40
|
+
puts "Sample data: #{parsed_data.first(2)}"
|
41
|
+
|
42
|
+
rescue => e
|
43
|
+
puts "Error fetching account numbers: #{e.message}"
|
44
|
+
puts e.backtrace.first(3)
|
45
|
+
end
|
46
|
+
|
47
|
+
if __FILE__ == $0
|
48
|
+
fetch_account_numbers
|
49
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Script to fetch user preferences data and save as fixture
|
5
|
+
# Usage: ruby examples/fetch_user_preferences.rb
|
6
|
+
|
7
|
+
require_relative '../lib/schwab_rb'
|
8
|
+
require 'dotenv'
|
9
|
+
require 'json'
|
10
|
+
require 'fileutils'
|
11
|
+
|
12
|
+
Dotenv.load
|
13
|
+
|
14
|
+
def create_client
|
15
|
+
token_path = ENV['TOKEN_PATH'] || 'schwab_token.json'
|
16
|
+
SchwabRb::Auth.init_client_easy(
|
17
|
+
ENV['SCHWAB_API_KEY'],
|
18
|
+
ENV['SCHWAB_APP_SECRET'],
|
19
|
+
ENV['APP_CALLBACK_URL'],
|
20
|
+
token_path
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
def fetch_user_preferences
|
25
|
+
client = create_client
|
26
|
+
puts "Fetching user preferences..."
|
27
|
+
|
28
|
+
response = client.get_user_preferences
|
29
|
+
parsed_data = JSON.parse(response.body, symbolize_names: true)
|
30
|
+
|
31
|
+
# Create fixtures directory if it doesn't exist
|
32
|
+
fixtures_dir = File.join(__dir__, '..', 'spec', 'fixtures')
|
33
|
+
FileUtils.mkdir_p(fixtures_dir)
|
34
|
+
|
35
|
+
# Save the raw response
|
36
|
+
fixture_file = File.join(fixtures_dir, 'user_preferences.json')
|
37
|
+
File.write(fixture_file, JSON.pretty_generate(parsed_data))
|
38
|
+
|
39
|
+
puts "User preferences data saved to: #{fixture_file}"
|
40
|
+
puts "Sample data keys: #{parsed_data.keys.first(5)}"
|
41
|
+
|
42
|
+
rescue => e
|
43
|
+
puts "Error fetching user preferences: #{e.message}"
|
44
|
+
puts e.backtrace.first(3)
|
45
|
+
end
|
46
|
+
|
47
|
+
if __FILE__ == $0
|
48
|
+
fetch_user_preferences
|
49
|
+
end
|