Python library for Charles Schwab API
Project description
SchwabPy
A Python library for accessing the Charles Schwab Trading and Market Data APIs. This library provides a simple and intuitive interface for retrieving market data, managing accounts, viewing portfolios, and placing trades.
Quick Start Guide | Full Documentation | Examples | Changelog
Features
- 🔐 OAuth 2.0 Authentication - Automatic token management and refresh
- 🔒 Secure Token Storage - Tokens stored with secure file permissions (0600)
- 📊 Market Data - Real-time quotes, price history, option chains, and more
- 💼 Account Management - View accounts, positions, balances, and transactions
- 📈 Trading - Place, modify, and cancel orders (equities and options)
- 🔄 Auto Token Refresh - Tokens are automatically refreshed when needed
- ⚡ Rate Limiting - Built-in rate limiting (120 req/min default, configurable)
- 🔁 Automatic Retries - Exponential backoff for transient network errors
- ✅ Input Validation - Comprehensive validation of all parameters
- 🎯 Type Hints - Full type hint support for better IDE integration
- 🧪 Tested - 47+ unit tests ensuring reliability
- 📝 Comprehensive Documentation - Clear examples and API references
Installation
Install from PyPI (recommended):
pip install schwab-client
Or install from source:
git clone https://github.com/jaycollett/SchwabPy.git
cd SchwabPy
pip install -e .
Prerequisites
Before using this library, you need to:
- Create a Schwab Developer Account: Visit Schwab Developer Portal
- Register an Application: Create an app to get your App Key and App Secret
- Set Redirect URI: Configure your app's callback URL (e.g.,
https://127.0.0.1)
Quick Start
1. Authentication
First, authenticate with the Schwab API:
from schwabpy import SchwabClient
# Initialize the client
client = SchwabClient(
client_id="YOUR_APP_KEY",
client_secret="YOUR_APP_SECRET",
redirect_uri="https://127.0.0.1"
)
# Start authentication flow
client.authenticate()
# This will print a URL - visit it in your browser and authorize
# After authorization, you'll be redirected to a URL like:
# https://127.0.0.1/?code=AUTHORIZATION_CODE&session=...
# Complete authentication with the callback URL
client.authorize_from_callback("PASTE_FULL_CALLBACK_URL_HERE")
Note: After first authentication, tokens are saved locally and automatically refreshed. You won't need to re-authenticate for 7 days.
2. Get Stock Quotes
from schwabpy import SchwabClient
client = SchwabClient(
client_id="YOUR_APP_KEY",
client_secret="YOUR_APP_SECRET"
)
# Get a single quote
quote = client.market_data.get_quote("AAPL")
print(f"{quote.symbol}: ${quote.last_price}")
print(f"Change: {quote.net_change} ({quote.net_percent_change}%)")
# Get multiple quotes
quotes = client.market_data.get_quotes(["AAPL", "MSFT", "GOOGL"])
for symbol, quote in quotes.items():
print(f"{symbol}: ${quote.last_price}")
3. View Portfolio Holdings
# Get account numbers
accounts = client.accounts.get_account_numbers()
account_hash = accounts[0]['hashValue']
# Get account balance
balance = client.accounts.get_balance(account_hash)
print(f"Cash Balance: ${balance.cash_balance:,.2f}")
print(f"Buying Power: ${balance.buying_power:,.2f}")
# Get positions
positions = client.accounts.get_positions(account_hash)
for position in positions:
# Position provides raw API data - you can calculate metrics as needed
quantity = position.long_quantity - position.short_quantity
print(f"{position.symbol}: {quantity} shares @ ${position.average_price}")
print(f" Market Value: ${position.market_value:,.2f}")
print(f" Day P&L: ${position.current_day_profit_loss:,.2f}")
4. Get Price History
# Get daily price history for the last month
history = client.market_data.get_price_history(
symbol="AAPL",
period_type="month",
period=1,
frequency_type="daily",
frequency=1
)
candles = history.get('candles', [])
for candle in candles[-5:]: # Last 5 days
print(f"Date: {candle['datetime']}, Close: ${candle['close']}")
5. Get Option Chains
# Get option chain for a stock
chain = client.market_data.get_option_chain(
symbol="AAPL",
contract_type="CALL",
strike_count=10 # 10 strikes above and below current price
)
print(f"Underlying: {chain.symbol} @ ${chain.underlying_price}")
print(f"Available expirations: {len(chain.call_exp_date_map)}")
6. Place an Order
from schwabpy.orders import Orders
# Get account hash
accounts = client.accounts.get_account_numbers()
account_hash = accounts[0]['hashValue']
# Build a simple market order
order = Orders.build_equity_order(
symbol="AAPL",
quantity=10,
instruction="BUY",
order_type="MARKET"
)
# Place the order
result = client.orders.place_order(account_hash, order)
print("Order placed successfully!")
# Or build a limit order
order = Orders.build_equity_order(
symbol="AAPL",
quantity=10,
instruction="BUY",
order_type="LIMIT",
price=150.00
)
API Reference
Market Data
# Quotes
quote = client.market_data.get_quote("AAPL")
quotes = client.market_data.get_quotes(["AAPL", "MSFT"])
# Price History
history = client.market_data.get_price_history(
symbol="AAPL",
period_type="month",
period=1,
frequency_type="daily",
frequency=1
)
# Option Chains
chain = client.market_data.get_option_chain(
symbol="AAPL",
contract_type="CALL",
strike_count=10
)
# Option Expirations
expirations = client.market_data.get_option_expiration_chain("AAPL")
# Search Instruments
results = client.market_data.search_instruments(
symbol="tech",
projection="desc-search"
)
# Get Instrument by CUSIP
instrument = client.market_data.get_instrument("037833100") # AAPL
# Market Hours
hours = client.market_data.get_market_hours(["equity", "option"])
# Market Movers
movers = client.market_data.get_movers("$SPX", sort="PERCENT_CHANGE_UP")
Accounts
# Account Numbers
accounts = client.accounts.get_account_numbers()
# Account Details
account = client.accounts.get_account(account_hash, fields="positions")
# All Accounts
all_accounts = client.accounts.get_accounts(fields="positions")
# Positions
positions = client.accounts.get_positions(account_hash)
# Balance
balance = client.accounts.get_balance(account_hash)
# Orders
orders = client.accounts.get_orders(account_hash, status="WORKING")
order = client.accounts.get_order(account_hash, order_id)
all_orders = client.accounts.get_all_orders(status="FILLED")
# Transactions
transactions = client.accounts.get_transactions(
account_hash,
start_date="2024-01-01",
end_date="2024-01-31"
)
transaction = client.accounts.get_transaction(account_hash, transaction_id)
# User Preferences (includes streamer info)
prefs = client.accounts.get_user_preference()
Orders
from schwabpy.orders import Orders
# Place Order
order = Orders.build_equity_order("AAPL", 10, "BUY", "MARKET")
result = client.orders.place_order(account_hash, order)
# Replace Order
new_order = Orders.build_equity_order("AAPL", 10, "BUY", "LIMIT", price=150.00)
result = client.orders.replace_order(account_hash, order_id, new_order)
# Cancel Order
result = client.orders.cancel_order(account_hash, order_id)
# Build Option Order
option_order = Orders.build_option_order(
symbol="AAPL 240315C00150000",
quantity=1,
instruction="BUY_TO_OPEN",
order_type="LIMIT",
price=5.50
)
# Build Spread Order
legs = [
{"symbol": "AAPL 240315C00150000", "quantity": 1, "instruction": "BUY_TO_OPEN"},
{"symbol": "AAPL 240315C00155000", "quantity": 1, "instruction": "SELL_TO_OPEN"}
]
spread = Orders.build_spread_order(legs, "NET_DEBIT", price=2.50)
Data Models
The library provides clean data models for API responses:
- Account: Account information
- Position: Portfolio position with raw API data
- Balance: Account balance details
- Quote: Real-time quote data
- Instrument: Financial instrument details
- Order: Order information
- OptionChain: Option chain data
All models include:
- Clean attribute access (e.g.,
quote.last_price) - Raw data access (
.raw_dataproperty for complete API response) - Type hints for better IDE support
Important: Models provide raw API data without calculations. This allows you to:
- Perform your own calculations based on your requirements
- Avoid assumptions made by the library
- Access complete API response data via
.raw_data
Example with Position:
position = positions[0]
# Raw API fields
print(position.long_quantity) # e.g., 100.0
print(position.short_quantity) # e.g., 0.0
print(position.average_price) # e.g., 150.0
print(position.market_value) # e.g., 16000.0
print(position.current_day_profit_loss) # e.g., 500.0
# You can calculate derived metrics as needed
net_quantity = position.long_quantity - position.short_quantity
cost_basis = position.average_price * net_quantity
unrealized_pl = position.market_value - cost_basis
unrealized_pl_pct = (unrealized_pl / cost_basis) * 100 if cost_basis != 0 else 0
# Access complete API response
full_data = position.raw_data
Authentication Details
OAuth 2.0 Flow
SchwabPy uses OAuth 2.0 for authentication:
- Authorization: User authorizes the app via browser
- Token Exchange: Authorization code is exchanged for tokens
- Auto Refresh: Access tokens (30 min) are automatically refreshed using refresh tokens (7 days)
- Token Storage: Tokens are securely stored locally in
.schwab_tokens.json
Token Lifecycle
- Access Token: Valid for 30 minutes, automatically refreshed
- Refresh Token: Valid for 7 days, used to get new access tokens
- After 7 days, you'll need to re-authenticate through the browser
Security Best Practices
- Secure Token Storage: Tokens are automatically saved with secure permissions (0600 - owner read/write only)
- Never commit tokens: Add
.schwab_tokens.jsonto.gitignore(already included) - Keep secrets secure: Don't hardcode App Key/Secret in code
- Use environment variables: Store credentials in env vars or config files
- Use context managers: Properly cleanup resources
import os
# Using environment variables
client = SchwabClient(
client_id=os.getenv("SCHWAB_APP_KEY"),
client_secret=os.getenv("SCHWAB_APP_SECRET"),
redirect_uri="https://127.0.0.1"
)
# Or use as context manager for automatic cleanup
with SchwabClient(
client_id=os.getenv("SCHWAB_APP_KEY"),
client_secret=os.getenv("SCHWAB_APP_SECRET")
) as client:
quote = client.market_data.get_quote("AAPL")
# Client resources are automatically cleaned up when exiting
Note: If you see a warning about insecure token file permissions, run:
chmod 600 .schwab_tokens.json
Rate Limits
The Schwab API has rate limits, and this library includes built-in rate limiting protection:
- Automatic Rate Limiting: The client automatically limits requests to 120 per minute (configurable)
- No Manual Throttling: You don't need to add delays between requests
- Transparent Sleep: When the limit is reached, the client automatically sleeps
# Configure rate limiting (default is 120 req/min)
client = SchwabClient(
client_id="YOUR_APP_KEY",
client_secret="YOUR_APP_SECRET",
rate_limit_per_minute=120 # Adjust based on your app's limits
)
# Make requests normally - rate limiting is automatic
for symbol in ["AAPL", "MSFT", "GOOGL", "TSLA", "NVDA"]:
quote = client.market_data.get_quote(symbol) # Automatically rate limited
Schwab API Limits (vary by app configuration):
- Order endpoints: Typically 0-120 requests per minute per account
- Market data: Generally higher limits
- Account data: Standard REST API limits
The library will raise RateLimitError if the API returns a 429 status code.
Error Handling
from schwabpy import SchwabClient
from schwabpy.exceptions import (
AuthenticationError,
RateLimitError,
APIError
)
try:
quote = client.market_data.get_quote("AAPL")
except AuthenticationError:
print("Authentication failed - please re-authenticate")
except RateLimitError:
print("Rate limit exceeded - wait before retrying")
except APIError as e:
print(f"API error: {e}")
Examples
Check the examples/ directory for complete examples:
01_authentication.py- OAuth authentication flow02_get_quotes.py- Get stock quotes03_get_portfolio.py- View portfolio holdings
Development
Project Structure
schwabpy/
├── __init__.py # Package initialization
├── auth.py # OAuth 2.0 authentication
├── client.py # Main API client
├── accounts.py # Account operations
├── market_data.py # Market data operations
├── orders.py # Order management
├── models.py # Data models
├── exceptions.py # Custom exceptions
└── utils.py # Utility functions
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Disclaimer
⚠️ Important: This library is for educational purposes. Always test with small amounts and paper trading accounts first.
- Not officially affiliated with Charles Schwab
- Use at your own risk
- No warranty provided
- Review all orders carefully before submission
Resources
- Schwab Developer Portal
- Schwab API Documentation
- Market Data API Documentation (included in downloads)
- Trader API Documentation (included in downloads)
License
MIT License - see LICENSE file for details
Support
For issues, questions, or contributions:
- Open an issue on GitHub
- Check existing documentation
- Review example scripts
Happy Trading! 📈
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file schwab_client-2.0.1.tar.gz.
File metadata
- Download URL: schwab_client-2.0.1.tar.gz
- Upload date:
- Size: 30.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ce8828cf911010b3ab25b3099f3c06eebdeb3a15331697c62752e9d762a428b4
|
|
| MD5 |
96692e1295266780562513e0d9b97c59
|
|
| BLAKE2b-256 |
343cd3032ca5e3ff1fb82f841318ae2cc8acad7e4d5c90f7e241829e4f139dbd
|
Provenance
The following attestation bundles were made for schwab_client-2.0.1.tar.gz:
Publisher:
workflow.yml on jaycollett/SchwabPy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
schwab_client-2.0.1.tar.gz -
Subject digest:
ce8828cf911010b3ab25b3099f3c06eebdeb3a15331697c62752e9d762a428b4 - Sigstore transparency entry: 731260332
- Sigstore integration time:
-
Permalink:
jaycollett/SchwabPy@e66f87f05b57b2050f05b6bf56464d6bd463aacf -
Branch / Tag:
refs/tags/v2.0.1 - Owner: https://github.com/jaycollett
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
workflow.yml@e66f87f05b57b2050f05b6bf56464d6bd463aacf -
Trigger Event:
release
-
Statement type:
File details
Details for the file schwab_client-2.0.1-py3-none-any.whl.
File metadata
- Download URL: schwab_client-2.0.1-py3-none-any.whl
- Upload date:
- Size: 28.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c205bbdd00a020c413bd5dbade97d240ad997bc78b4f64aed2c62810c8ef9f1e
|
|
| MD5 |
1b00b01fb5be640a40a3dd128272a43f
|
|
| BLAKE2b-256 |
a5e9da15b0c0bfcd4e02937b390e482d67cd565a5c1626a4c07ef8e09adf3cc9
|
Provenance
The following attestation bundles were made for schwab_client-2.0.1-py3-none-any.whl:
Publisher:
workflow.yml on jaycollett/SchwabPy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
schwab_client-2.0.1-py3-none-any.whl -
Subject digest:
c205bbdd00a020c413bd5dbade97d240ad997bc78b4f64aed2c62810c8ef9f1e - Sigstore transparency entry: 731260333
- Sigstore integration time:
-
Permalink:
jaycollett/SchwabPy@e66f87f05b57b2050f05b6bf56464d6bd463aacf -
Branch / Tag:
refs/tags/v2.0.1 - Owner: https://github.com/jaycollett
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
workflow.yml@e66f87f05b57b2050f05b6bf56464d6bd463aacf -
Trigger Event:
release
-
Statement type: