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
- 🔐 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
Install from PyPI (recommended):
pip install schwab-clientOr install from source:
git clone https://github.com/jaycollett/SchwabPy.git
cd SchwabPy
pip install -e .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)
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.
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}")# 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}")# 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']}")# 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)}")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
)# 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")# 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()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)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_dataSchwabPy 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
- 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
- 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 exitingNote: If you see a warning about insecure token file permissions, run:
chmod 600 .schwab_tokens.jsonThe 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 limitedSchwab 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.
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}")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
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
Contributions are welcome! Please feel free to submit a Pull Request.
- Not officially affiliated with Charles Schwab
- Use at your own risk
- No warranty provided
- Review all orders carefully before submission
- Schwab Developer Portal
- Schwab API Documentation
- Market Data API Documentation (included in downloads)
- Trader API Documentation (included in downloads)
MIT License - see LICENSE file for details
For issues, questions, or contributions:
- Open an issue on GitHub
- Check existing documentation
- Review example scripts
Happy Trading! 📈