Skip to main content

Python library for Charles Schwab API

Project description

SchwabPy

PyPI version Python Versions License: MIT Downloads

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:

  1. Create a Schwab Developer Account: Visit Schwab Developer Portal
  2. Register an Application: Create an app to get your App Key and App Secret
  3. 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_data property 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:

  1. Authorization: User authorizes the app via browser
  2. Token Exchange: Authorization code is exchanged for tokens
  3. Auto Refresh: Access tokens (30 min) are automatically refreshed using refresh tokens (7 days)
  4. 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

  1. Secure Token Storage: Tokens are automatically saved with secure permissions (0600 - owner read/write only)
  2. Never commit tokens: Add .schwab_tokens.json to .gitignore (already included)
  3. Keep secrets secure: Don't hardcode App Key/Secret in code
  4. Use environment variables: Store credentials in env vars or config files
  5. 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 flow
  • 02_get_quotes.py - Get stock quotes
  • 03_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

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

schwab_client-2.0.2.tar.gz (31.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

schwab_client-2.0.2-py3-none-any.whl (28.9 kB view details)

Uploaded Python 3

File details

Details for the file schwab_client-2.0.2.tar.gz.

File metadata

  • Download URL: schwab_client-2.0.2.tar.gz
  • Upload date:
  • Size: 31.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for schwab_client-2.0.2.tar.gz
Algorithm Hash digest
SHA256 0d08fd24782a01e487f8e118e3bdceebb4980494df6f4a7450cd82d228c0a10f
MD5 b89d6fcd34b5a75602c7dfd9413a3deb
BLAKE2b-256 f5887006f112e95aa66b3eec1f5bf9334d53fc3ac44cfa208dc96c4621c14349

See more details on using hashes here.

Provenance

The following attestation bundles were made for schwab_client-2.0.2.tar.gz:

Publisher: workflow.yml on jaycollett/SchwabPy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file schwab_client-2.0.2-py3-none-any.whl.

File metadata

  • Download URL: schwab_client-2.0.2-py3-none-any.whl
  • Upload date:
  • Size: 28.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for schwab_client-2.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 81fd986f64e846a728a40a7069258577df451de67e609806f64067d972e43b82
MD5 e57095e1720181b40033371887b61f80
BLAKE2b-256 231455ff35d0b4e4a0a4baab50490c061dbb09ffebefb7aea57244f689c57b59

See more details on using hashes here.

Provenance

The following attestation bundles were made for schwab_client-2.0.2-py3-none-any.whl:

Publisher: workflow.yml on jaycollett/SchwabPy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page