schwab_rb 0.6.0 → 0.8.1
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 +9 -0
- data/.codex/skills/schwab-rb-cli/SKILL.md +59 -0
- data/.codex/skills/schwab-rb-cli/agents/openai.yaml +3 -0
- data/.codex/skills/schwab-rb-cli/references/cli_examples.md +100 -0
- data/.codex/skills/schwab-rb-cli/scripts/run_schwab_rb.sh +9 -0
- data/AGENTS.md +137 -0
- data/CHANGELOG.md +8 -0
- data/README.md +39 -1
- data/Rakefile +26 -0
- data/doc/LOGGING.md +141 -0
- data/doc/QUICK_START.md +15 -0
- data/exe/schwab_rb +7 -0
- data/lib/schwab_rb/auth/init_client_easy.rb +2 -0
- data/lib/schwab_rb/auth/init_client_login.rb +2 -0
- data/lib/schwab_rb/auth/init_client_token_file.rb +3 -0
- data/lib/schwab_rb/auth/token_manager.rb +5 -1
- data/lib/schwab_rb/cli/app.rb +579 -0
- data/lib/schwab_rb/cli.rb +8 -0
- data/lib/schwab_rb/constants.rb +4 -4
- data/lib/schwab_rb/path_support.rb +19 -0
- data/lib/schwab_rb/price_history/downloader.rb +246 -0
- data/lib/schwab_rb/version.rb +1 -1
- data/lib/schwab_rb.rb +3 -0
- metadata +16 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e92f8e69c61f92f34a54eb5ae7b5b07a8734dbe71a221006972a0988aedc5fd5
|
|
4
|
+
data.tar.gz: a0635b9f44605a22ee31140f8b8601d7f0cf64d28dcdf41e5f711d680efa7dfd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c30b898de7b8e933960f38c62b82364210ec89abe603b773bf7e0b012e74ce8e3230f6f28c015fc100a46a2a1a7596e6896758f3a695dd9707c63ef92eb1df8d
|
|
7
|
+
data.tar.gz: 8f0e880a9a0227ad9b02c1d8ed95f9b66d49e2303ac5ad955f8cd9cc7fa0cabd893e72a5091f6990c352eacf5939e9a75eee292cf0f592dbe7c74b53baff897c
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: schwab-rb-cli
|
|
3
|
+
description: Use the installed schwab_rb CLI for authentication and price history downloads. Activate when a user wants to run the schwab_rb command, log in to Schwab, fetch market data, troubleshoot CLI installation or environment variables, or save price history from the command line.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Schwab RB CLI Skill
|
|
7
|
+
|
|
8
|
+
Use this skill when the task should be completed through the installed `schwab_rb` command rather than by editing Ruby code directly.
|
|
9
|
+
|
|
10
|
+
## When to use
|
|
11
|
+
|
|
12
|
+
- The user wants to run `schwab_rb help`, `schwab_rb login`, or `schwab_rb price-history`
|
|
13
|
+
- The user wants to download price history data to JSON or CSV
|
|
14
|
+
- The user needs help with CLI installation, `asdf` shims, or missing `SCHWAB_*` environment variables
|
|
15
|
+
- The user wants the agent to verify the CLI works on the local machine
|
|
16
|
+
|
|
17
|
+
## Quick workflow
|
|
18
|
+
|
|
19
|
+
1. Prefer the bundled wrapper script:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
skills/schwab-rb-cli/scripts/run_schwab_rb.sh help
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The wrapper only attempts to execute the globally installed `schwab_rb` command from `PATH`. It does not use repo-relative paths or Bundler fallbacks.
|
|
26
|
+
|
|
27
|
+
2. If the wrapper reports that `schwab_rb` is missing:
|
|
28
|
+
- Report that the host machine does not have the CLI available in `PATH`
|
|
29
|
+
- Tell the user to install the gem globally and fix shell/shim configuration before retrying
|
|
30
|
+
|
|
31
|
+
3. Before running auth or data commands, verify these exported variables exist in the shell environment:
|
|
32
|
+
- `SCHWAB_API_KEY`
|
|
33
|
+
- `SCHWAB_APP_SECRET`
|
|
34
|
+
- `SCHWAB_APP_CALLBACK_URL`
|
|
35
|
+
|
|
36
|
+
4. For first-time auth, run:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
skills/schwab-rb-cli/scripts/run_schwab_rb.sh login
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
5. For data downloads, run:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
skills/schwab-rb-cli/scripts/run_schwab_rb.sh price-history --symbol AAPL --start-date 2026-03-01
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Operating guidance
|
|
49
|
+
|
|
50
|
+
- Do not ask users to paste secrets into chat when the task can be completed using already-exported environment variables.
|
|
51
|
+
- Assume the CLI token is shared at `~/.schwab_rb/token.json`.
|
|
52
|
+
- Assume downloaded data goes to `~/.schwab_rb/data` unless `--dir` is passed.
|
|
53
|
+
- Use `--format csv` only when the user specifically wants flat files; otherwise prefer JSON because it matches the API response shape.
|
|
54
|
+
- If the wrapper says the executable is missing, stop and report that the host install is unavailable. Do not substitute repo-local execution.
|
|
55
|
+
- If the CLI reports missing environment variables, check exported shell envs first. Do not rely on a repo-local `.env` unless the user explicitly wants that setup.
|
|
56
|
+
|
|
57
|
+
## References
|
|
58
|
+
|
|
59
|
+
- For concrete commands and troubleshooting steps, read [references/cli_examples.md](references/cli_examples.md).
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# schwab_rb CLI Examples
|
|
2
|
+
|
|
3
|
+
## Basic checks
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
skills/schwab-rb-cli/scripts/run_schwab_rb.sh help
|
|
7
|
+
skills/schwab-rb-cli/scripts/run_schwab_rb.sh help price-history
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Authentication
|
|
11
|
+
|
|
12
|
+
Expected exported variables:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
env | rg '^SCHWAB_'
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Login flow:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
skills/schwab-rb-cli/scripts/run_schwab_rb.sh login
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Price history downloads
|
|
25
|
+
|
|
26
|
+
Daily JSON output:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
skills/schwab-rb-cli/scripts/run_schwab_rb.sh price-history --symbol AAPL --start-date 2026-03-01
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Minute CSV output:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
skills/schwab-rb-cli/scripts/run_schwab_rb.sh price-history \
|
|
36
|
+
--symbol VIX \
|
|
37
|
+
--start-date 2026-03-01 \
|
|
38
|
+
--end-date 2026-03-24 \
|
|
39
|
+
--freq 1min \
|
|
40
|
+
--format csv
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Custom output directory:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
skills/schwab-rb-cli/scripts/run_schwab_rb.sh price-history \
|
|
47
|
+
--symbol SPY \
|
|
48
|
+
--start-date 2026-03-01 \
|
|
49
|
+
--dir /tmp/schwab_rb_data
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Installation troubleshooting
|
|
53
|
+
|
|
54
|
+
Install the gem from the repo:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
bundle exec rake install
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
If the host uses `asdf`, regenerate shims:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
asdf reshim ruby 3.2.2
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Verify the executable:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
which schwab_rb
|
|
70
|
+
ls -l ~/.asdf/shims/schwab_rb
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
If `schwab_rb` is still not on `PATH`, the wrapper script will fail immediately and the agent should report that the host install is not usable yet.
|
|
74
|
+
|
|
75
|
+
## Environment troubleshooting
|
|
76
|
+
|
|
77
|
+
Check that the variables are exported, not just set in the shell:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
env | rg '^SCHWAB_'
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Portable shell setup:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# ~/.zshrc
|
|
87
|
+
if [ -f "$HOME/.env" ]; then
|
|
88
|
+
set -a
|
|
89
|
+
source "$HOME/.env"
|
|
90
|
+
set +a
|
|
91
|
+
fi
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Then in `~/.env`:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
SCHWAB_API_KEY=...
|
|
98
|
+
SCHWAB_APP_SECRET=...
|
|
99
|
+
SCHWAB_APP_CALLBACK_URL=https://127.0.0.1:8182
|
|
100
|
+
```
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
if command -v schwab_rb >/dev/null 2>&1; then
|
|
5
|
+
exec "$(command -v schwab_rb)" "$@"
|
|
6
|
+
fi
|
|
7
|
+
|
|
8
|
+
echo "schwab_rb executable not found in PATH. Install the gem globally on the host machine and ensure the executable is available before using this skill." >&2
|
|
9
|
+
exit 1
|
data/AGENTS.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Development Commands
|
|
6
|
+
|
|
7
|
+
### Setup and Installation
|
|
8
|
+
- `bin/setup` - Install dependencies and set up the development environment
|
|
9
|
+
- `bundle install` - Install gem dependencies
|
|
10
|
+
- `bin/console` - Start an interactive Ruby console with the gem loaded
|
|
11
|
+
|
|
12
|
+
### Testing
|
|
13
|
+
- `bundle exec rake spec` - Run all RSpec tests
|
|
14
|
+
- `bundle exec rspec` - Run RSpec tests directly
|
|
15
|
+
- `bundle exec rspec spec/path/to/specific_spec.rb` - Run a specific test file
|
|
16
|
+
- `bundle exec rspec spec/path/to/specific_spec.rb:line_number` - Run a specific test
|
|
17
|
+
|
|
18
|
+
### Linting and Code Quality
|
|
19
|
+
- `bundle exec rubocop` - Run RuboCop linter
|
|
20
|
+
- `bundle exec rubocop -a` - Auto-correct RuboCop violations where possible
|
|
21
|
+
- `bundle exec rake` - Run both tests and linting (default task)
|
|
22
|
+
|
|
23
|
+
### Gem Management
|
|
24
|
+
- `bundle exec rake build` - Build the gem
|
|
25
|
+
- `bundle exec rake install` - Install the gem locally
|
|
26
|
+
- `bundle exec rake release` - Release a new version (creates git tag and pushes to RubyGems)
|
|
27
|
+
|
|
28
|
+
## Architecture Overview
|
|
29
|
+
|
|
30
|
+
### Core Structure
|
|
31
|
+
This is a Ruby gem (`schwab_rb`) that provides a client library for the Charles Schwab API. The architecture follows a modular design:
|
|
32
|
+
|
|
33
|
+
**Authentication Layer** (`lib/schwab_rb/auth/`):
|
|
34
|
+
- `init_client_easy.rb` - Recommended initialization method that handles token management automatically
|
|
35
|
+
- `init_client_login.rb` - Interactive browser-based authentication flow
|
|
36
|
+
- `init_client_token_file.rb` - Initialize from existing token file
|
|
37
|
+
- `token_manager.rb` - Handles token refresh and persistence
|
|
38
|
+
- `login_flow_server.rb` - Temporary server for OAuth callback handling
|
|
39
|
+
|
|
40
|
+
**Client Layer** (`lib/schwab_rb/clients/`):
|
|
41
|
+
- `client.rb` - Synchronous HTTP client for API calls
|
|
42
|
+
- `async_client.rb` - Asynchronous client using the `async` gem
|
|
43
|
+
- `base_client.rb` - Shared client functionality
|
|
44
|
+
|
|
45
|
+
**Data Objects** (`lib/schwab_rb/data_objects/`):
|
|
46
|
+
- Structured Ruby objects for API responses (Account, Quote, Order, etc.)
|
|
47
|
+
- Replaces raw JSON responses with typed objects for better developer experience
|
|
48
|
+
- All API methods support `return_data_objects: false` to get raw JSON if needed
|
|
49
|
+
|
|
50
|
+
**Order Management** (`lib/schwab_rb/orders/`):
|
|
51
|
+
- `builder.rb` - Fluent interface for constructing complex orders
|
|
52
|
+
- Various enum classes for order parameters (duration, session, instructions, etc.)
|
|
53
|
+
|
|
54
|
+
### Key Design Patterns
|
|
55
|
+
|
|
56
|
+
**Three-Tier Client Initialization**:
|
|
57
|
+
1. `init_client_easy()` - Handles everything automatically (recommended)
|
|
58
|
+
2. `init_client_token_file()` - For existing tokens
|
|
59
|
+
3. `init_client_login()` - For interactive authentication
|
|
60
|
+
|
|
61
|
+
**Data Object Strategy**:
|
|
62
|
+
- All API methods return structured Ruby objects by default
|
|
63
|
+
- Use `return_data_objects: false` for raw JSON responses
|
|
64
|
+
- Data objects are built from JSON using factory patterns
|
|
65
|
+
|
|
66
|
+
**Async Support**:
|
|
67
|
+
- Both sync and async clients available
|
|
68
|
+
- Async operations use the `async` gem with promises
|
|
69
|
+
- Set `asyncio: true` during client initialization
|
|
70
|
+
|
|
71
|
+
### Configuration
|
|
72
|
+
|
|
73
|
+
Environment variables (loaded via `dotenv`):
|
|
74
|
+
- `SCHWAB_API_KEY` - Your Schwab API key
|
|
75
|
+
- `SCHWAB_APP_SECRET` - Your Schwab application secret
|
|
76
|
+
- `APP_CALLBACK_URL` - OAuth callback URL
|
|
77
|
+
- `TOKEN_PATH` - Path for token storage
|
|
78
|
+
- `SCHWAB_ACCOUNT_NUMBER` - Your account number
|
|
79
|
+
- `SCHWAB_LOGFILE` - Log file path (optional, defaults to STDOUT)
|
|
80
|
+
- `SCHWAB_LOG_LEVEL` - Log level (DEBUG, INFO, WARN, ERROR, FATAL)
|
|
81
|
+
- `SCHWAB_SILENCE_OUTPUT` - Set to 'true' to disable logging
|
|
82
|
+
|
|
83
|
+
Programmatic configuration:
|
|
84
|
+
```ruby
|
|
85
|
+
SchwabRb.configure do |config|
|
|
86
|
+
config.logger = Logger.new(STDOUT)
|
|
87
|
+
config.log_level = 'INFO'
|
|
88
|
+
config.silence_output = false
|
|
89
|
+
end
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Testing Infrastructure
|
|
93
|
+
|
|
94
|
+
**Fixtures and Factories**:
|
|
95
|
+
- JSON fixtures in `spec/fixtures/` for realistic API responses
|
|
96
|
+
- Factory classes in `spec/factories/` for creating test objects
|
|
97
|
+
- Extensive mocking of HTTP responses for unit tests
|
|
98
|
+
|
|
99
|
+
**Test Organization**:
|
|
100
|
+
- Unit tests mirror the `lib/` structure
|
|
101
|
+
- Separate specs for data objects, clients, auth, and orders
|
|
102
|
+
- Uses `async-rspec` for testing async functionality
|
|
103
|
+
|
|
104
|
+
### Key Dependencies
|
|
105
|
+
- `oauth2` - OAuth2 authentication
|
|
106
|
+
- `async` + `async-http` - Asynchronous HTTP operations
|
|
107
|
+
- `sinatra` + `puma` - Temporary server for OAuth callbacks
|
|
108
|
+
- `dotenv` - Environment variable management
|
|
109
|
+
- `rspec` + `async-rspec` - Testing framework
|
|
110
|
+
- `rubocop` - Code linting
|
|
111
|
+
|
|
112
|
+
## Common Patterns
|
|
113
|
+
|
|
114
|
+
### Client Initialization
|
|
115
|
+
Always use `init_client_easy()` for new development:
|
|
116
|
+
```ruby
|
|
117
|
+
client = SchwabRb::Auth.init_client_easy(
|
|
118
|
+
ENV['SCHWAB_API_KEY'],
|
|
119
|
+
ENV['SCHWAB_APP_SECRET'],
|
|
120
|
+
ENV['APP_CALLBACK_URL'],
|
|
121
|
+
ENV['TOKEN_PATH']
|
|
122
|
+
)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Order Building
|
|
126
|
+
Use the fluent builder pattern for complex orders:
|
|
127
|
+
```ruby
|
|
128
|
+
order = SchwabRb::Orders::Builder.new
|
|
129
|
+
.set_session(:normal)
|
|
130
|
+
.set_duration(:day)
|
|
131
|
+
.set_order_type(:market)
|
|
132
|
+
.add_equity_leg(:buy, 'AAPL', 100)
|
|
133
|
+
.build
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Error Handling
|
|
137
|
+
Token expiration is handled automatically by `init_client_easy()`. Custom error classes are defined in `lib/schwab_rb/orders/errors.rb`.
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
### Added
|
|
4
|
+
- CLI `sample` command for saving single-expiration option chain snapshots as CSV or JSON
|
|
5
|
+
- `--root` option for filtering sampled contracts by option root and using that root in output filenames
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- CLI history downloads now default to `~/.schwab_rb/data/history`
|
|
9
|
+
- CLI option samples now default to `~/.schwab_rb/data/options`
|
|
10
|
+
|
|
3
11
|
## [0.6.0] - 2025-12-11
|
|
4
12
|
|
|
5
13
|
### Breaking Changes
|
data/README.md
CHANGED
|
@@ -18,6 +18,44 @@ gem install schwab_rb
|
|
|
18
18
|
|
|
19
19
|
Note: The gem requires Ruby 3.0.0 or higher.
|
|
20
20
|
|
|
21
|
+
## CLI
|
|
22
|
+
|
|
23
|
+
Installing the gem also installs a `schwab_rb` executable.
|
|
24
|
+
|
|
25
|
+
Set these environment variables before using the CLI:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
export SCHWAB_API_KEY=your_api_key_here
|
|
29
|
+
export SCHWAB_APP_SECRET=your_app_secret_here
|
|
30
|
+
export SCHWAB_APP_CALLBACK_URL=https://127.0.0.1:8182
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Authenticate once to create the shared token in `~/.schwab_rb/token.json`:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
schwab_rb login
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Download price history into `~/.schwab_rb/data` as JSON. Files are stored per symbol and frequency, for example `AAPL_day.json` or `SPX_5min.csv`, and the CLI reuses and reconciles existing candle files when possible:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
schwab_rb price-history --symbol AAPL --start-date 2026-03-01
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Write CSV output to a custom directory:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
schwab_rb price-history \
|
|
49
|
+
--symbol VIX \
|
|
50
|
+
--start-date 2026-03-01 \
|
|
51
|
+
--end-date 2026-03-24 \
|
|
52
|
+
--freq 1min \
|
|
53
|
+
--format csv \
|
|
54
|
+
--dir ./tmp/price_history
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
That command writes `./tmp/price_history/VIX_1min.csv`.
|
|
58
|
+
|
|
21
59
|
## Prerequisites
|
|
22
60
|
|
|
23
61
|
Before using this gem, you'll need:
|
|
@@ -268,4 +306,4 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/jwplat
|
|
|
268
306
|
|
|
269
307
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
270
308
|
|
|
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).
|
|
309
|
+
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
CHANGED
|
@@ -3,6 +3,32 @@
|
|
|
3
3
|
require "bundler/gem_tasks"
|
|
4
4
|
require "rspec/core/rake_task"
|
|
5
5
|
|
|
6
|
+
gem_helper = Bundler::GemHelper.instance
|
|
7
|
+
|
|
8
|
+
def install_built_gem(gem_helper, local: false)
|
|
9
|
+
gem_path = File.join(
|
|
10
|
+
gem_helper.base,
|
|
11
|
+
"pkg",
|
|
12
|
+
"#{gem_helper.send(:name)}-#{gem_helper.send(:version)}.gem"
|
|
13
|
+
)
|
|
14
|
+
command = [*gem_helper.send(:gem_command), "install", gem_path, "--no-document"]
|
|
15
|
+
command << "--local" if local
|
|
16
|
+
sh(*command)
|
|
17
|
+
Bundler.ui.confirm "#{gem_helper.send(:name)} (#{gem_helper.send(:version)}) installed."
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
Rake::Task["install"].clear
|
|
21
|
+
desc "Build and install the gem into system gems without generating documentation."
|
|
22
|
+
task "install" => "build" do
|
|
23
|
+
install_built_gem(gem_helper)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
Rake::Task["install:local"].clear
|
|
27
|
+
desc "Build and install the gem into system gems without network access or documentation."
|
|
28
|
+
task "install:local" => "build" do
|
|
29
|
+
install_built_gem(gem_helper, local: true)
|
|
30
|
+
end
|
|
31
|
+
|
|
6
32
|
RSpec::Core::RakeTask.new(:spec)
|
|
7
33
|
|
|
8
34
|
require "rubocop/rake_task"
|
data/doc/LOGGING.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Logging Configuration
|
|
2
|
+
|
|
3
|
+
The `schwab_rb` gem provides flexible logging configuration options to suit different development and production needs.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The gem's logging system is built around two main components:
|
|
8
|
+
- `SchwabRb::Configuration` - Manages configuration settings
|
|
9
|
+
- `SchwabRb::Logger` - Creates and manages the logger instance
|
|
10
|
+
|
|
11
|
+
## Configuration Methods
|
|
12
|
+
|
|
13
|
+
### 1. Environment Variables
|
|
14
|
+
|
|
15
|
+
Set logging behavior using environment variables:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
export SCHWAB_LOGFILE="/path/to/logfile.log" # File path or "STDOUT"
|
|
19
|
+
export SCHWAB_LOG_LEVEL="INFO" # DEBUG, INFO, WARN, ERROR, FATAL
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### 2. Programmatic Configuration
|
|
23
|
+
|
|
24
|
+
Configure logging in your Ruby code:
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
SchwabRb.configure do |config|
|
|
28
|
+
config.log_file = "/path/to/logfile.log"
|
|
29
|
+
config.log_level = "DEBUG"
|
|
30
|
+
config.silence_output = false
|
|
31
|
+
end
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Using the Gem's Built-in Logger
|
|
35
|
+
|
|
36
|
+
### Default Behavior
|
|
37
|
+
By default, the gem creates its own logger that:
|
|
38
|
+
- Logs to STDOUT
|
|
39
|
+
- Uses WARN level
|
|
40
|
+
- Rotates log files weekly (when logging to file)
|
|
41
|
+
- Uses custom formatting: `[HH:MM:SS] SCHWAB_RB LEVEL: message`
|
|
42
|
+
|
|
43
|
+
### Example Configuration
|
|
44
|
+
```ruby
|
|
45
|
+
SchwabRb.configure do |config|
|
|
46
|
+
config.log_file = "/var/log/schwab_rb.log"
|
|
47
|
+
config.log_level = "INFO"
|
|
48
|
+
end
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Using an External Logger
|
|
52
|
+
|
|
53
|
+
You can provide your own logger instance instead of using the gem's built-in logger:
|
|
54
|
+
|
|
55
|
+
### Rails Logger Example
|
|
56
|
+
```ruby
|
|
57
|
+
# In Rails application
|
|
58
|
+
SchwabRb.configure do |config|
|
|
59
|
+
config.logger = Rails.logger
|
|
60
|
+
end
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Custom Logger Example
|
|
64
|
+
```ruby
|
|
65
|
+
# Custom logger with specific formatting
|
|
66
|
+
my_logger = Logger.new(STDOUT)
|
|
67
|
+
my_logger.level = Logger::DEBUG
|
|
68
|
+
my_logger.formatter = proc do |severity, datetime, progname, msg|
|
|
69
|
+
"#{datetime} [SCHWAB] #{severity}: #{msg}\n"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
SchwabRb.configure do |config|
|
|
73
|
+
config.logger = my_logger
|
|
74
|
+
end
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Structured Logging Example
|
|
78
|
+
```ruby
|
|
79
|
+
# Using a structured logging library
|
|
80
|
+
require 'semantic_logger'
|
|
81
|
+
|
|
82
|
+
SchwabRb.configure do |config|
|
|
83
|
+
config.logger = SemanticLogger['SchwabRb']
|
|
84
|
+
end
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Logger Behavior
|
|
88
|
+
|
|
89
|
+
### External Logger Priority
|
|
90
|
+
When an external logger is provided via `config.logger`, it takes precedence over all other settings. The gem will:
|
|
91
|
+
- Use your logger instance directly
|
|
92
|
+
- Ignore `log_file` and `log_level` settings
|
|
93
|
+
- Not apply custom formatting
|
|
94
|
+
|
|
95
|
+
### Silence Output
|
|
96
|
+
Setting `silence_output = true` creates a null logger that discards all messages:
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
SchwabRb.configure do |config|
|
|
100
|
+
config.silence_output = true
|
|
101
|
+
end
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Log File Handling
|
|
105
|
+
When logging to a file, the gem:
|
|
106
|
+
- Creates the directory if it doesn't exist
|
|
107
|
+
- Creates the log file if it doesn't exist
|
|
108
|
+
- Rotates logs weekly
|
|
109
|
+
- Returns a null logger if the path is `:null` or `"/dev/null"`
|
|
110
|
+
|
|
111
|
+
## Log Levels
|
|
112
|
+
|
|
113
|
+
Available log levels (case-insensitive):
|
|
114
|
+
- `DEBUG` - Detailed information for debugging
|
|
115
|
+
- `INFO` - General information messages
|
|
116
|
+
- `WARN` - Warning messages (default)
|
|
117
|
+
- `ERROR` - Error conditions
|
|
118
|
+
- `FATAL` - Critical errors
|
|
119
|
+
|
|
120
|
+
## Accessing the Logger
|
|
121
|
+
|
|
122
|
+
Get the configured logger instance:
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
logger = SchwabRb::Logger.logger
|
|
126
|
+
logger.info("Custom log message")
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Configuration Reset
|
|
130
|
+
|
|
131
|
+
Reset the logger and configuration:
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
# Reset configuration to defaults
|
|
135
|
+
SchwabRb.reset_configuration!
|
|
136
|
+
|
|
137
|
+
# Reset logger instance (forces recreation)
|
|
138
|
+
SchwabRb::Logger.reset!
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Note: The logger is automatically reset when configuration changes are made via `SchwabRb.configure`.
|
data/doc/QUICK_START.md
CHANGED
|
@@ -33,6 +33,12 @@ SCHWAB_APP_CALLBACK_URL=https://127.0.0.1:8182
|
|
|
33
33
|
SCHWAB_TOKEN_PATH=~/.schwab_rb/token.json
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
Or authenticate with the CLI:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
schwab_rb login
|
|
40
|
+
```
|
|
41
|
+
|
|
36
42
|
## 4. Initialize Client (First Time)
|
|
37
43
|
|
|
38
44
|
```ruby
|
|
@@ -70,6 +76,15 @@ accounts.each do |account|
|
|
|
70
76
|
end
|
|
71
77
|
```
|
|
72
78
|
|
|
79
|
+
Or download price history directly from the CLI:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
schwab_rb price-history --symbol AAPL --start-date 2026-03-01
|
|
83
|
+
schwab_rb price-history --symbol VIX --start-date 2026-03-01 --end-date 2026-03-24 --freq 1min --format csv
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The CLI stores candle files using `SYMBOL_FREQ.format`, such as `AAPL_day.json` or `VIX_1min.csv`, and will reuse an existing file instead of re-downloading ranges it already has.
|
|
87
|
+
|
|
73
88
|
## 6. Set Up Account Names (Optional but Recommended)
|
|
74
89
|
|
|
75
90
|
Create `~/.schwab_rb/account_names.json`:
|
data/exe/schwab_rb
ADDED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "oauth2"
|
|
4
4
|
require_relative "init_client_token_file"
|
|
5
5
|
require_relative "init_client_login"
|
|
6
|
+
require_relative "../path_support"
|
|
6
7
|
|
|
7
8
|
module SchwabRb
|
|
8
9
|
module Auth
|
|
@@ -17,6 +18,7 @@ module SchwabRb
|
|
|
17
18
|
interactive: true,
|
|
18
19
|
requested_browser: nil
|
|
19
20
|
)
|
|
21
|
+
token_path = SchwabRb::PathSupport.expand_path(token_path)
|
|
20
22
|
|
|
21
23
|
raise OAuth2::Error, "No token found" unless File.exist?(token_path)
|
|
22
24
|
|
|
@@ -5,6 +5,7 @@ require "uri"
|
|
|
5
5
|
require "net/http"
|
|
6
6
|
require "json"
|
|
7
7
|
require "oauth2"
|
|
8
|
+
require_relative "../path_support"
|
|
8
9
|
# require 'logger'
|
|
9
10
|
|
|
10
11
|
module SchwabRb
|
|
@@ -71,6 +72,7 @@ module SchwabRb
|
|
|
71
72
|
interactive: true,
|
|
72
73
|
requested_browser: nil
|
|
73
74
|
)
|
|
75
|
+
token_path = SchwabRb::PathSupport.expand_path(token_path)
|
|
74
76
|
|
|
75
77
|
callback_timeout = if !callback_timeout
|
|
76
78
|
0
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "oauth2"
|
|
4
|
+
require_relative "../path_support"
|
|
4
5
|
|
|
5
6
|
module SchwabRb
|
|
6
7
|
module Auth
|
|
7
8
|
def self.init_client_token_file(api_key, app_secret, token_path, enforce_enums: true)
|
|
9
|
+
token_path = SchwabRb::PathSupport.expand_path(token_path)
|
|
10
|
+
|
|
8
11
|
oauth = OAuth2::Client.new(
|
|
9
12
|
api_key,
|
|
10
13
|
app_secret,
|
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
require "oauth2"
|
|
4
4
|
require "json"
|
|
5
|
+
require_relative "../constants"
|
|
6
|
+
require_relative "../path_support"
|
|
5
7
|
|
|
6
8
|
module SchwabRb
|
|
7
9
|
module Auth
|
|
8
10
|
class TokenManager
|
|
9
11
|
class << self
|
|
10
12
|
def from_file(token_path)
|
|
13
|
+
token_path = SchwabRb::PathSupport.expand_path(token_path)
|
|
11
14
|
token_data = JSON.parse(File.read(token_path))
|
|
12
15
|
token = SchwabRb::Auth::Token.new(
|
|
13
16
|
token: token_data["token"]["access_token"],
|
|
@@ -40,7 +43,7 @@ module SchwabRb
|
|
|
40
43
|
def initialize(token, timestamp, token_path: SchwabRb::Constants::DEFAULT_TOKEN_PATH)
|
|
41
44
|
@token = token
|
|
42
45
|
@timestamp = timestamp
|
|
43
|
-
@token_path = token_path
|
|
46
|
+
@token_path = SchwabRb::PathSupport.expand_path(token_path)
|
|
44
47
|
end
|
|
45
48
|
|
|
46
49
|
attr_reader :token, :timestamp, :token_path
|
|
@@ -77,6 +80,7 @@ module SchwabRb
|
|
|
77
80
|
end
|
|
78
81
|
|
|
79
82
|
def to_file
|
|
83
|
+
SchwabRb::PathSupport.ensure_parent_directory(token_path)
|
|
80
84
|
File.write(token_path, to_json)
|
|
81
85
|
end
|
|
82
86
|
|