Skip to main content

Cliente ligero para API de Schwab: OAuth, REST y Streaming WebSocket

Project description

Schwab SDK (Python)

Lightweight client for the Schwab API: OAuth, REST (Trader/Market Data), and WebSocket Streaming.

  • Focus: thin wrappers, no heavy validation; robust token handling, refresh, and retries.
  • Coverage: Accounts, Orders, Market Data, and Streaming (Level One, Book, Chart, Screener, Account Activity).

Installation

Unofficial PyPI package (distribution name):

pip install schwab_sdk_unofficial

Import in code (module):

from schwab_sdk import Client, AsyncClient

⚠️ Note: The package name on PyPI is schwab_sdk_unofficial, but the import remains schwab_sdk for a clean API.

Table of Contents

Requirements

  • Python 3.9+
  • Install dependencies:
pip install requests websocket-client flask

Configuration

Create .env (or export environment variables):

SCHWAB_CLIENT_ID=your_client_id
SCHWAB_CLIENT_SECRET=your_client_secret
SCHWAB_REDIRECT_URI=https://127.0.0.1:8080/callback

Tokens are saved in schwab_tokens.json and rotate automatically (access ~29 min; re-login notice when refresh expires ~7 d).

Quick Start

from schwab_sdk import Client
import os

client = Client(
    os.environ['SCHWAB_CLIENT_ID'],
    os.environ['SCHWAB_CLIENT_SECRET'],
    os.environ.get('SCHWAB_REDIRECT_URI','https://127.0.0.1:8080/callback'),
    token_path='my_tokens.json'  # Optional: custom token file path
)

# First use: OAuth login (opens the browser)
client.login()

# REST
quotes = client.market.get_quotes(["AAPL","MSFT"])  # Market Data
accounts = client.account.get_accounts()               # Accounts

# Streaming (Level One equities)
ws = client.streaming
ws.on_data(lambda f: print("DATA", f))
ws.connect(); ws.login()
ws.equities_subscribe(["AAPL"])

Async Support

The SDK also provides async versions of all endpoints using AsyncClient:

import asyncio
from schwab_sdk import AsyncClient

async def main():
    # Initialize async client
    async with AsyncClient(
        os.environ['SCHWAB_CLIENT_ID'],
        os.environ['SCHWAB_CLIENT_SECRET'],
        token_path='async_tokens.json'
    ) as client:
        # Login (async wrapper)
        await client.login()
        
        # Async REST calls
        quotes = await client.market.get_quotes(["AAPL", "MSFT"])
        accounts = await client.account.get_accounts()
        orders = await client.orders.get_all_orders()
        
        # Async streaming
        await client.streaming.connect()
        await client.streaming.login()
        await client.streaming.equities_subscribe(["AAPL"])
        
        # Set up callbacks
        client.streaming.on_data(lambda data: print("DATA:", data))
        client.streaming.on_response(lambda resp: print("RESPONSE:", resp))
        
        # Keep running to receive data
        await asyncio.sleep(10)  # Example: run for 10 seconds
        await client.streaming.disconnect()

# Run async function
asyncio.run(main())

Async Benefits

  • Non-blocking: Multiple API calls can run concurrently
  • Better performance: Especially for multiple simultaneous requests
  • Context manager: Automatic session cleanup with async with
  • Same API: All methods have async equivalents with await

Async vs Sync

Feature Sync Async
Import from schwab_sdk import Client from schwab_sdk import AsyncClient
Usage client.method() await client.method()
Context client = Client(...) async with AsyncClient(...)
Performance Sequential Concurrent
Dependencies requests, websocket-client aiohttp, websockets

Async Streaming

The async streaming client provides non-blocking WebSocket connections:

import asyncio
from schwab_sdk import AsyncClient

async def streaming_example():
    async with AsyncClient(client_id, client_secret) as client:
        await client.login()
        
        # Connect to streaming
        await client.streaming.connect()
        await client.streaming.login()
        
        # Set up callbacks
        client.streaming.on_data(lambda data: print("Market data:", data))
        client.streaming.on_response(lambda resp: print("Response:", resp))
        client.streaming.on_notify(lambda notify: print("Notification:", notify))
        
        # Subscribe to data streams
        await client.streaming.equities_subscribe(["AAPL", "MSFT"])
        await client.streaming.options_subscribe([
            client.streaming.create_option_symbol("AAPL", "2025-12-19", "C", 200.0)
        ])
        await client.streaming.account_activity_subscribe("your_account_hash")
        
        # Keep running
        await asyncio.sleep(30)  # Run for 30 seconds
        
        # Cleanup
        await client.streaming.disconnect()

asyncio.run(streaming_example())

Streaming Services Available

Service Method Description
Equities equities_subscribe(symbols) Stock quotes
Options options_subscribe(option_symbols) Option quotes
Futures futures_subscribe(symbols) Futures quotes
Forex forex_subscribe(pairs) Currency pairs
Account account_activity_subscribe(account_hash) Account activity

Async Accounts Module

Async version of account and transaction endpoints:

Methods

  • get_accounts(fields=None) - Get all accounts
  • get_account_by_id(account_hash, fields=None) - Get specific account
  • get_transactions(account_hash, from_date=None, to_date=None, symbol=None, types=None) - Get transactions
  • get_transaction(account_hash, transaction_id) - Get specific transaction
  • get_preferences(account_hash) - Get account preferences

Example

async with AsyncClient(client_id, client_secret) as client:
    await client.login()
    
    # Get all accounts
    accounts = await client.account.get_accounts()
    
    # Get account with positions
    account = await client.account.get_account_by_id("123456789", fields="positions")
    
    # Get transactions for date range
    transactions = await client.account.get_transactions(
        "123456789", 
        from_date="2025-01-01", 
        to_date="2025-01-31"
    )

Async Orders Module

Async version of order management endpoints:

Methods

  • get_orders(account_hash, max_results=None, from_entered_time=None, to_entered_time=None, status=None) - Get orders
  • get_all_orders(max_results=None, from_entered_time=None, to_entered_time=None, status=None) - Get all orders
  • place_order(account_hash, order_data) - Place new order
  • get_order(account_hash, order_id) - Get specific order
  • cancel_order(account_hash, order_id) - Cancel order
  • replace_order(account_hash, order_id, order_data) - Replace order
  • preview_order(account_hash, order_data) - Preview order

Example

async with AsyncClient(client_id, client_secret) as client:
    await client.login()
    
    # Get orders for account
    orders = await client.orders.get_orders("123456789", status="FILLED")
    
    # Place a market order
    order_data = {
        "orderType": "MARKET",
        "session": "NORMAL",
        "duration": "DAY",
        "orderStrategyType": "SINGLE",
        "orderLegCollection": [{
            "instruction": "BUY",
            "quantity": 100,
            "instrument": {"symbol": "AAPL", "assetType": "EQUITY"}
        }]
    }
    result = await client.orders.place_order("123456789", order_data)

Async Market Module

Async version of market data endpoints:

Methods

  • get_quote(symbol) - Get single quote
  • get_quotes(symbols) - Get multiple quotes
  • get_movers(symbol_id, sort=None, frequency=None, params=None) - Get market movers
  • get_option_chain(symbol, contract_type=None, ...) - Get option chain
  • get_expiration_chain(symbol, params=None) - Get expiration dates
  • get_markets(date=None) - Get market hours
  • get_market_hours(date=None) - Get market hours for date

Example

async with AsyncClient(client_id, client_secret) as client:
    await client.login()
    
    # Get quotes
    quotes = await client.market.get_quotes(["AAPL", "MSFT", "GOOGL"])
    
    # Get option chain
    options = await client.market.get_option_chain(
        "AAPL",
        contract_type="CALL",
        strike_count=5,
        from_date="2025-01-01",
        to_date="2025-12-31"
    )
    
    # Get market movers
    movers = await client.market.get_movers("$SPX.X", sort="PERCENT_CHANGE")

Authentication (OAuth)

  • client.login(timeout=300, auto_open_browser=True)
  • Handy: client.has_valid_token(), client.refresh_token_now(), client.logout()
  • Internals: adhoc HTTPS callback server (dev), code-for-token exchange, auto-refresh and notice when refresh expires.

Request & Error Handling (REST)

All REST calls use Client._request() with:

  • Automatic Authorization headers
  • Refresh retry on 401 (once) and immediate resend
  • Retries with backoff for 429/5xx (exponential with factor 0.5)

Accounts (accounts.py)

get_account_numbers() -> List[dict]

  • GET /accounts/accountNumbers
  • Returns accountNumber and hashValue pairs.
  • Example response:
[
  {"accountNumber":"12345678","hashValue":"827C...AC12"}
]

get_accounts(params: dict|None=None) -> dict

  • GET /accounts

  • Query parameters:

    • fields (optional): the API currently accepts positions to return positions. E.g.: fields=positions.

get_account_by_id(account_hash: str, params: dict|None=None) -> dict

  • GET /accounts/{accountNumber}

  • account_hash: encrypted account identifier (hashValue).

  • Query parameters:

    • fields (optional): positions to include positions. E.g.: fields=positions.

find_account(last_4_digits: str) -> dict|None

  • Helper that uses get_account_numbers() and filters by the last 4 digits, then calls get_account_by_id.

get_transactions(account_hash: str, from_date: str|None, to_date: str|None, filters: dict|None=None) -> dict

  • GET /accounts/{accountHash}/transactions

  • ONE DATE REQUIRED: you may pass only from_date or only to_date. If you pass a single date, the SDK fills in the other for the same day:

    • Short format YYYY-MM-DD: start → YYYY-MM-DDT00:00:00.000Z, end → YYYY-MM-DDT23:59:59.000Z
    • Full ISO UTC YYYY-MM-DDTHH:MM:SS.ffffffZ: used as-is; if the other date is missing, it is derived with 00:00:00.000Z or 23:59:59.000Z of the same day.
  • Params:

    • startDate: ISO UTC - YYYY-MM-DDTHH:MM:SS.ffffffZ (or short YYYY-MM-DD)

    • endDate: ISO UTC - YYYY-MM-DDTHH:MM:SS.ffffffZ (or short YYYY-MM-DD)

    • filters: optional dict:

      • types: string with valid types: TRADE, RECEIVE_AND_DELIVER, DIVIDEND_OR_INTEREST, ACH_RECEIPT, ACH_DISBURSEMENT, CASH_RECEIPT, CASH_DISBURSEMENT, ELECTRONIC_FUND, WIRE_OUT, WIRE_IN, JOURNAL
      • symbol: specific symbol
      • status: transaction status

    Note: In some API configurations, types may be considered mandatory. The SDK does not require it and treats it as an optional filter.

Correct example:

from datetime import datetime, timezone, timedelta

# Get hashValue
hash_value = client.account.get_account_numbers()[0]['hashValue']

# Create UTC dates
start = datetime.now(timezone.utc) - timedelta(days=7)
start = start.replace(hour=0, minute=0, second=0, microsecond=0)
end = datetime.now(timezone.utc).replace(hour=23, minute=59, second=59, microsecond=999999)

# Proper call
transactions = client.account.get_transactions(
    account_hash=hash_value,  # Use hashValue!
    from_date=start.strftime('%Y-%m-%dT%H:%M:%S.%fZ'),
    to_date=end.strftime('%Y-%m-%dT%H:%M:%S.%fZ'),
    filters={"types": "TRADE,DIVIDEND_OR_INTEREST"}  # Optional
)

get_transaction(account_hash: str, transaction_id: str) -> dict

  • GET /accounts/{accountHash}/transactions/{transactionId}

  • Path parameters:

    • account_hash (required)
    • transaction_id (required): numeric transaction ID
  • Returns details of a specific transaction.

get_user_preferences() -> dict

  • GET /userPreference

  • Returns user preferences and, when applicable, streamer information needed for WebSocket:

    • streamerSocketUrl
    • schwabClientCustomerId
    • schwabClientCorrelId
    • SchwabClientChannel
    • SchwabClientFunctionId
  • Useful to initialize client.streaming (LOGIN and subscriptions).


Orders (orders.py)

All responses include HTTP metadata and the native data:

{
  "status_code": 200,
  "success": true,
  "headers": {"...": "..."},
  "url": "https://...",
  "elapsed_seconds": 0.42,
  "method": "GET|POST|PUT|DELETE",
  "params": {"...": "..."},
  "data": {},
  "order_id": "..."   
}

get_orders(account_hash, from_entered_time=None, to_entered_time=None, status=None, max_results=None) -> dict

  • GET /accounts/{accountNumber}/orders
  • API requirements: fromEnteredTime and toEnteredTime are mandatory (ISO-8601). The SDK defaults them to "last 60 days" if you omit them.
  • status (case-insensitive): normalized to uppercase. Values accepted by the API: AWAITING_PARENT_ORDER, AWAITING_CONDITION, AWAITING_STOP_CONDITION, AWAITING_MANUAL_REVIEW, ACCEPTED, AWAITING_UR_OUT, PENDING_ACTIVATION, QUEUED, WORKING, REJECTED, PENDING_CANCEL, CANCELED, PENDING_REPLACE, REPLACED, FILLED, EXPIRED, NEW, AWAITING_RELEASE_TIME, PENDING_ACKNOWLEDGEMENT, PENDING_RECALL, UNKNOWN.
  • maxResults (optional): record limit (API default 3000).
  • Datetime format: ISO-8601 YYYY-MM-DDTHH:MM:SS.000Z.

get_all_orders(from_entered_time=None, to_entered_time=None, status=None, max_results=None) -> dict

  • GET /orders
  • API requirements: fromEnteredTime and toEnteredTime are mandatory (ISO-8601). If you omit them, the SDK uses "last 60 days".
  • Filters identical to get_orders (including status normalization).
  • maxResults (optional): record limit (API default 3000).

place_order(account_hash: str, order_data: dict) -> dict

  • POST /accounts/{accountNumber}/orders
  • Extracts order_id from the Location header when present.

get_order(account_hash: str, order_id: str) -> dict

  • GET /accounts/{accountNumber}/orders/{orderId}

cancel_order(account_hash: str, order_id: str) -> dict

  • DELETE /accounts/{accountNumber}/orders/{orderId}
  • Tries to extract order_id from Location if the server returns it.

replace_order(account_hash: str, order_id: str, new_order_data: dict) -> dict

  • PUT /accounts/{accountNumber}/orders/{orderId}
  • Returns new order_id (from Location) when applicable.

preview_order(account_hash: str, order_data: dict) -> dict

  • POST /accounts/{accountNumber}/previewOrder
  • Tries to extract order_id from Location if the server returns it.

Payload helpers

  • build_limit_order(symbol, quantity, price, instruction="BUY")
  • build_market_order(symbol, quantity, instruction="BUY")
  • build_bracket_order(symbol, quantity, entry_price, take_profit_price, stop_loss_price)

Example (preview):

acc = client.account.get_account_numbers()[0]['hashValue']
order = client.orders.build_limit_order("AAPL", 1, 100.00)
preview = client.orders.preview_order(acc, order)

Market Data (market.py)

get_quotes(symbols: str|List[str], params: dict|None=None) -> dict

  • GET /quotes?symbols=...

  • Parameters:

    • symbols (required): str or list of symbols separated by commas. E.g.: AAPL,AMZN,$DJI,/ESH23.

    • params (optional):

      • fields: subset of data. Values: quote, fundamental, extended, reference, regular. Default: all.
      • indicative: boolean (true|false) to include indicative quotes (e.g., ETFs). Example: indicative=false.

get_quote(symbol: str, params: dict|None=None) -> dict

  • GET /{symbol}/quotes

  • Parameters:

    • symbol (required): single symbol (e.g., TSLA).

    • params (optional):

      • fields: same as in get_quotes.

get_option_chain(symbol: str, contract_type: str|None=None, strike_count: int|None=None, include_underlying_quote: bool|None=None, strategy: str|None=None, interval: float|None=None, strike: float|None=None, range_type: str|None=None, from_date: str|None=None, to_date: str|None=None, volatility: float|None=None, underlying_price: float|None=None, interest_rate: float|None=None, days_to_expiration: int|None=None, exp_month: str|None=None, option_type: str|None=None, entitlement: str|None=None, params: dict|None=None) -> dict

  • GET /chains

  • Parameters:

    • symbol (required): Underlying asset symbol
    • contract_type (optional): Contract Type. Available values: CALL, PUT, ALL
    • strike_count (optional): The Number of strikes to return above or below the at-the-money price
    • include_underlying_quote (optional): Underlying quotes to be included (boolean)
    • strategy (optional): OptionChain strategy. Default is SINGLE. Available values: SINGLE, ANALYTICAL, COVERED, VERTICAL, CALENDAR, STRANGLE, STRADDLE, BUTTERFLY, CONDOR, DIAGONAL, COLLAR, ROLL
    • interval (optional): Strike interval for spread strategy chains (see strategy param)
    • strike (optional): Strike Price
    • range_type (optional): Range(ITM/NTM/OTM etc.)
    • from_date (optional): From date (pattern: yyyy-MM-dd)
    • to_date (optional): To date (pattern: yyyy-MM-dd)
    • volatility (optional): Volatility to use in calculations. Applies only to ANALYTICAL strategy chains
    • underlying_price (optional): Underlying price to use in calculations. Applies only to ANALYTICAL strategy chains
    • interest_rate (optional): Interest rate to use in calculations. Applies only to ANALYTICAL strategy chains
    • days_to_expiration (optional): Days to expiration to use in calculations. Applies only to ANALYTICAL strategy chains
    • exp_month (optional): Expiration month. Available values: JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC, ALL
    • option_type (optional): Option Type
    • entitlement (optional): Applicable only if its retail token, entitlement of client PP-PayingPro, NP-NonPro and PN-NonPayingPro. Available values: PN, NP, PP
    • params (optional): Additional query parameters

get_expiration_chain(symbol: str, params: dict|None=None) -> dict

  • GET /expirationchain

  • Parameters:

    • symbol (required): Underlying asset symbol
    • params (optional): Additional query parameters
  • Returns: JSON response with option expiration dates for the symbol

get_price_history(symbol, periodType="month", period=1, frequencyType="daily", frequency=1, startDate=None, endDate=None, params=None) -> dict

  • GET /pricehistory

  • Parameters:

    • periodType: day|month|year|ytd
    • period: int
    • frequencyType: minute|daily|weekly|monthly
    • frequency: int
    • startDate/endDate (ms since epoch)
    • Additional optionals (needExtendedHoursData, etc., depending on entitlements)

get_movers(symbol_id: str, sort: str|None=None, frequency: int|None=None, params: dict|None=None) -> dict

  • GET /movers/{symbol_id} (e.g., $DJI, $SPX, NASDAQ)

  • Parameters:

    • symbol_id (required): Index Symbol. Available values: $DJI, $COMPX, $SPX, NYSE, NASDAQ, OTCBB, INDEX_ALL, EQUITY_ALL, OPTION_ALL, OPTION_PUT, OPTION_CALL
    • sort (optional): Sort by a particular attribute. Available values: VOLUME, TRADES, PERCENT_CHANGE_UP, PERCENT_CHANGE_DOWN
    • frequency (optional): To return movers with the specified directions of up or down. Available values: 0,1,5,10,30,60 (min). Default 0
    • params (optional): Additional query parameters

get_markets(params: dict|None=None) -> dict

  • GET /markets

  • Query parameters:

    • markets (required by API): array of equity, option, bond, future, forex (the SDK accepts params={"markets": ...})
    • date (optional): YYYY-MM-DD (if you send ISO, the SDK trims to date)

get_market_hours(market_id: str, params: dict|None=None) -> dict

  • GET /markets/{market_id} (equity, option, bond, forex)

  • Query parameters:

    • date (optional): YYYY-MM-DD (if you send ISO, the SDK trims to date)

get_instruments(symbols: str|List[str], projection: str, extra_params: dict|None=None) -> dict

  • GET /instruments

  • Parameters:

    • symbols (required): single symbol or comma-separated list
    • projection (required by API): symbol-search, symbol-regex, desc-search, desc-regex, search, fundamental Note: the SDK does not require it in the signature, but it is recommended to provide it.

get_instrument_by_cusip(cusip_id: str, params: dict|None=None) -> dict

  • GET /instruments/{cusip_id}

WebSocket Streaming (streaming.py)

Callbacks

  • on_data(fn) (data frames)
  • on_response(fn) (command confirmations/errors)
  • on_notify(fn) (heartbeats/notices)

Basic flow

ws = client.streaming
ws.on_data(lambda f: print("DATA", f))
ws.on_response(lambda f: print("RESP", f))
ws.on_notify(lambda f: print("NOTIFY", f))
ws.connect(); ws.login()  # Authorization = access token without "Bearer"
ws.equities_subscribe(["AAPL","MSFT"])             # LEVELONE_EQUITIES
ws.options_subscribe(["AAPL  250926C00257500"])      # LEVELONE_OPTIONS
ws.nasdaq_book(["MSFT"])                             # NASDAQ_BOOK
ws.chart_equity(["AAPL"])                            # CHART_EQUITY
ws.screener_equity(["NYSE_VOLUME_5"])                # SCREENER_EQUITY

Key Formats (quick table)

Type Format Example Notes
Equities Ticker AAPL, MSFT Uppercase
Options RRRRRRYYMMDDsWWWWWddd AAPL 251219C00200000 6-char symbol, YYMMDD, C/P, 5+3 strike
Futures /<root><month><yy> /ESZ25 Root/month/year in uppercase
FuturesOptions ./<root><month><year><C/P><strike> ./OZCZ23C565 Depends on the feed
Forex PAIR EUR/USD, USD/JPY / separator
Screener PREFIX_SORTFIELD_FREQUENCY NYSE_VOLUME_5 Prefix/criterion/frequency

Service-by-Service Examples

Level One Options

ws.options_subscribe(["AAPL  250926C00257500"])  # standard option string
# Default fields: 0,2,3,4,8,16,17,18,20,28,29,30,31,37,44

Level One Futures

ws.futures_subscribe(["/ESZ25"])  # E-mini S&P 500 Dec 2025
# Default fields: 0,1,2,3,4,5,8,12,13,18,19,20,24,33

Level One Forex

ws.forex_subscribe(["EUR/USD","USD/JPY"])  
# Default fields: 0,1,2,3,4,5,6,7,8,9,10,11,15,16,17,20,21,27,28,29

Book (Level II)

ws.nasdaq_book(["MSFT"])  # Also: ws.nyse_book, ws.options_book
# Default fields: 0 (Symbol), 1 (BookTime), 2 (Bids), 3 (Asks)

Chart (Series)

ws.chart_equity(["AAPL"])      # 0..7: key, open, high, low, close, volume, sequence, chartTime
ws.chart_futures(["/ESZ25"])   # 0..5

Screener

ws.screener_equity(["NYSE_VOLUME_5"])   
ws.screener_options(["CBOE_VOLUME_5"])

Account Activity

ws.account_activity()  # Gets accountHash and subscribes

Utilities

  • subscribe(service, keys, fields=None)
  • add(service, keys)
  • unsubscribe(service, keys) / unsubscribe_service(service, keys)
  • view(service, fields)

Option Symbol Helper

  • create_option_symbol(symbol, expiration, option_type, strike_price) - Creates Schwab option symbol from components

Example:

# Create option symbol from components
option_symbol = ws.create_option_symbol("AAPL", "2025-12-19", "C", 200.0)
# Returns: "AAPL  251219C00200000"

# Use in subscription
ws.options_subscribe([option_symbol])

Parameters:

  • symbol: Underlying symbol (e.g., "AAPL")
  • expiration: Expiration date in "YYYY-MM-DD" format (e.g., "2025-10-03")
  • option_type: "C" for Call or "P" for Put
  • strike_price: Strike price (e.g., 257.5)

Recommended Fields

  • LEVELONE_EQUITIES: 0,1,2,3,4,5,8,10,18,42,33,34,35
  • LEVELONE_OPTIONS: 0,2,3,4,8,16,17,18,20,28,29,30,31,37,44
  • LEVELONE_FUTURES: 0,1,2,3,4,5,8,12,13,18,19,20,24,33
  • CHART_EQUITY: 0,1,2,3,4,5,6,7

Quick fields table (copy/paste)

Service Fields CSV
LEVELONE_EQUITIES 0,1,2,3,4,5,8,10,18,42,33,34,35
LEVELONE_OPTIONS 0,2,3,4,8,16,17,18,20,28,29,30,31,37,44
LEVELONE_FUTURES 0,1,2,3,4,5,8,12,13,18,19,20,24,33
LEVELONE_FUTURES_OPTIONS 0,1,2,3,4,5,8,12,13,18,19,20,24,33
LEVELONE_FOREX 0,1,2,3,4,5,6,7,8,9,10,11,15,16,17,20,21,27,28,29
NASDAQ_BOOK 0,1,2,3
NYSE_BOOK 0,1,2,3
OPTIONS_BOOK 0,1,2,3
CHART_EQUITY 0,1,2,3,4,5,6,7
CHART_FUTURES 0,1,2,3,4,5
SCREENER_EQUITY 0,1,2,3,4
SCREENER_OPTION 0,1,2,3,4
ACCT_ACTIVITY 0,1,2

SUBS / ADD / VIEW / UNSUBS examples by service

ws = client.streaming
ws.on_data(lambda f: print("DATA", f))
ws.on_response(lambda f: print("RESP", f))
ws.connect(); ws.login()

# LEVELONE_EQUITIES
ws.equities_subscribe(["AAPL","TSLA"], fields=[0,1,2,3,4,5,8,10,18,42,33,34,35])
ws.equities_add(["MSFT"])                          # adds without replacing
ws.equities_view([0,1,2,3,5,8,18])                  # changes fields
ws.equities_unsubscribe(["TSLA"])                  # removes symbols

# LEVELONE_OPTIONS
ws.options_subscribe(["AAPL  250926C00257500"], fields=[0,2,3,4,8,16,17,18,20,28,29,30,31,37,44])
ws.options_add(["AAPL  250926P00257500"])          
ws.options_view([0,2,3,4,8,16,17,20,28,29,30,31,37,44])
ws.options_unsubscribe(["AAPL  250926C00257500"])  

# LEVELONE_FUTURES
ws.futures_subscribe(["/ESZ25"], fields=[0,1,2,3,4,5,8,12,13,18,19,20,24,33])
ws.futures_add(["/NQZ25"])
ws.futures_view([0,1,2,3,4,5,8,12,13,18,19,20,24,33])
ws.futures_unsubscribe(["/ESZ25"])  

# BOOK (Level II)
ws.nasdaq_book(["MSFT"], fields=[0,1,2,3])
ws.add("NASDAQ_BOOK", ["AAPL"])                    # generic ADD
ws.view("NASDAQ_BOOK", [0,1,2,3])                   # generic VIEW
ws.unsubscribe_service("NASDAQ_BOOK", ["MSFT"])    # generic UNSUBS

# CHART (Series)
ws.chart_equity(["AAPL"], fields=[0,1,2,3,4,5,6,7])
ws.add("CHART_EQUITY", ["MSFT"])                  # generic ADD
ws.view("CHART_EQUITY", [0,1,2,3,4,5,6,7])          # generic VIEW
ws.unsubscribe("CHART_EQUITY", ["AAPL"])           # generic UNSUBS

# SCREENER
ws.screener_equity(["EQUITY_ALL_VOLUME_5"], fields=[0,1,2,3,4])
ws.add("SCREENER_EQUITY", ["NYSE_TRADES_1"])      
ws.view("SCREENER_EQUITY", [0,1,2,3,4])
ws.unsubscribe("SCREENER_EQUITY", ["EQUITY_ALL_VOLUME_5"])

# ACCT_ACTIVITY
ws.account_activity(fields=[0,1,2])                  # subscribe account activity
# For UNSUBS you need the same key used in SUBS (account_hash)
account_hash = getattr(client, "_account_hash", None)
if account_hash:
    ws.unsubscribe_service("ACCT_ACTIVITY", [account_hash])

Quick Field Guide (IDs → meaning)

Note: exact mappings may vary depending on entitlements/version. Below are practical equivalences observed in frames.

LEVELONE_EQUITIES

ID Field
0 symbol/key
1 bidPrice
2 askPrice
3 lastPrice
4 bidSize
5 askSize
8 totalVolume
10 referencePrice (open/mark)
18 netChange
42 percentChange

LEVELONE_OPTIONS

ID Field
0 symbol/key
2 bidPrice
3 askPrice
4 lastPrice
8 totalVolume
16 openInterest
17 daysToExpiration
20 strikePrice
28 delta
29 gamma
30 theta
31 vega
44 impliedVolatility (if provided)

LEVELONE_FUTURES

ID Field
0 symbol/key
1 bidPrice
2 askPrice
3 lastPrice
4 bidSize
5 askSize
8 totalVolume
12 openInterest
13 contractDepth/series info (per feed)
18 netChange
19 sessionChange (or days/indicator per feed)
20 percentChange/ratio (per feed)
24 lastSettlement/mark
33 priorSettle

Frame Structure

  • Confirmations (response): { "response": [ { "service":"ADMIN","command":"LOGIN","content":{"code":0,"msg":"..."}} ] }
  • Data (data): { "service":"LEVELONE_EQUITIES","timestamp":...,"command":"SUBS","content":[{"key":"AAPL",...}] }
  • Notifications (notify): { "notify": [ { "heartbeat": "..." } ] }

Streamer API Cheat Sheet (parameters and commands)

  1. Connection and prerequisites
  • Auth: use the Access Token from the OAuth flow.
  • Session IDs (from GET /userPreference): schwabClientCustomerId, schwabClientCorrelId, SchwabClientChannel, SchwabClientFunctionId.
  • Transport: JSON WebSocket. One stream per user (if you open more: code 12 CLOSE_CONNECTION).
  1. Envelope of each command
  • Common fields:

    • service (req.): ADMIN, LEVELONE_EQUITIES, LEVELONE_OPTIONS, LEVELONE_FUTURES, LEVELONE_FUTURES_OPTIONS, LEVELONE_FOREX, NYSE_BOOK, NASDAQ_BOOK, OPTIONS_BOOK, CHART_EQUITY, CHART_FUTURES, SCREENER_EQUITY, SCREENER_OPTION, ACCT_ACTIVITY.
    • command (req.): LOGIN, SUBS, ADD, UNSUBS, VIEW, LOGOUT.
    • requestid (req.): unique request identifier.
    • SchwabClientCustomerId and SchwabClientCorrelId (recommended): from userPreference.
    • parameters (optional): depends on service/command.
  • Notes: SUBS overwrites list; ADD appends; UNSUBS removes; VIEW changes fields.

  1. ADMIN (session)
  • LOGIN (service=ADMIN, command=LOGIN)

    • parameters: Authorization (token without "Bearer"), SchwabClientChannel, SchwabClientFunctionId.
  • LOGOUT (service=ADMIN, command=LOGOUT)

    • parameters: empty.
  1. LEVEL ONE (L1 quotes)
  • Common parameters: keys (req., CSV list), fields (optional, indexes).
  • LEVELONE_EQUITIES: keys uppercase tickers (e.g., AAPL,TSLA).
  • LEVELONE_OPTIONS: keys Schwab option format RRRRRR YYMMDD[C/P]STRIKE.
  • LEVELONE_FUTURES: keys /<root><monthCode><yearCode> (month codes: F,G,H,J,K,M,N,Q,U,V,X,Z; year two digits), e.g., /ESZ25.
  • LEVELONE_FUTURES_OPTIONS: keys ./<root><month><yy><C|P><strike>, e.g., ./OZCZ23C565.
  • LEVELONE_FOREX: keys BASE/QUOTE pairs CSV (e.g., EUR/USD,USD/JPY).
  1. BOOK (Level II)
  • Services: NYSE_BOOK, NASDAQ_BOOK, OPTIONS_BOOK.
  • Parameters: keys (req., tickers), fields (optional, level indexes).
  1. CHART (streaming series)
  • CHART_EQUITY: keys equities; fields indexes (OHLCV, time, seq).
  • CHART_FUTURES: keys futures (same format as L1 futures); fields indexes.
  1. SCREENER (gainers/losers/actives)
  • Services: SCREENER_EQUITY, SCREENER_OPTION.

  • keys pattern PREFIX_SORTFIELD_FREQUENCY, e.g., EQUITY_ALL_VOLUME_5.

    • PREFIX examples: $COMPX, $DJI, $SPX, INDEX_ALL, NYSE, NASDAQ, OTCBB, EQUITY_ALL, OPTION_PUT, OPTION_CALL, OPTION_ALL.
    • SORTFIELD: VOLUME, TRADES, PERCENT_CHANGE_UP, PERCENT_CHANGE_DOWN, AVERAGE_PERCENT_VOLUME.
    • FREQUENCY: 0,1,5,10,30,60 (min; 0 = full day).
  • fields (optional): screener field indexes.

  1. ACCOUNT (account activity)
  • Service: ACCT_ACTIVITY (SUBS/UNSUBS).
  • keys (req.): arbitrary identifier for your sub; if you send multiple, the first is used.
  • fields (recommended): 0 (or 0,1,2,3 per example/need).
  1. Server responses
  • Types: response (to your requests), notify (heartbeats), data (market flow).
  • Key codes: 0 SUCCESS, 3 LOGIN_DENIED, 11 SERVICE_NOT_AVAILABLE, 12 CLOSE_CONNECTION, 19 REACHED_SYMBOL_LIMIT, 20 STREAM_CONN_NOT_FOUND, 21 BAD_COMMAND_FORMAT, 26/27/28/29 successes for SUBS/UNSUBS/ADD/VIEW.
  1. Delivery Types
  • All Sequence: everything with sequence number.
  • Change: only changed fields (conflated).
  • Whole: full messages with throttling.
  1. Best practices
  • Do LOGIN and wait for code=0 before SUBS/ADD.
  • To add symbols without losing existing ones, use ADD (not SUBS).
  • Change fields with VIEW for performance.
  • Handle notify (heartbeats) and reconnect if they are lost.
  • Reuse your SchwabClientCorrelId during the session.
  • If you see 19 (symbol limit), shard loads by service/session.

Advanced Troubleshooting

  • notify.code=12 (Only one connection): close other active WebSocket sessions.
  • response.content.code=3 (Login denied): invalid/expired token → client.login().
  • response.content.code=21 (Bad command formatting): check Authorization format (without Bearer) and keys (spacing for options, uppercase).
  • Persistent REST 401: delete schwab_tokens.json and re-run client.login().
  • High latency/lost frames: avoid parallel reconnects; use the SDK's auto-resubscription.

Contributions

Your contributions are welcome! Ideas, issues, and PRs help improve the SDK:

  • Open an issue with clear details (environment, steps, expected/actual error).
  • Propose endpoint coverage improvements and examples.
  • Follow a clear style and add tests or minimal examples when possible.

If you want to hold working sessions or discuss the roadmap, open an issue labeled discussion.

Disclaimer

This project is unofficial and is not affiliated with, sponsored by, or endorsed by Charles Schwab & Co., Inc. “Schwab” and other trademarks are the property of their respective owners. Use of this SDK is subject to the terms and conditions of Schwab APIs and applicable regulations. Use at your own discretion and responsibility.


License

MIT (LICENSE)

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_sdk_unofficial-0.2.1.tar.gz (72.5 kB view details)

Uploaded Source

Built Distribution

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

schwab_sdk_unofficial-0.2.1-py3-none-any.whl (65.3 kB view details)

Uploaded Python 3

File details

Details for the file schwab_sdk_unofficial-0.2.1.tar.gz.

File metadata

  • Download URL: schwab_sdk_unofficial-0.2.1.tar.gz
  • Upload date:
  • Size: 72.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for schwab_sdk_unofficial-0.2.1.tar.gz
Algorithm Hash digest
SHA256 8550a0c84428afeb0bfd44554e6e76b46cc8f8432f982f5383c96fec8037974c
MD5 119acff7344f91dbdf05f1fcc225cf91
BLAKE2b-256 823a6c084fcce992df76d1b8d1831b3fcd086544a969db40f3c70c01f9be4aed

See more details on using hashes here.

File details

Details for the file schwab_sdk_unofficial-0.2.1-py3-none-any.whl.

File metadata

File hashes

Hashes for schwab_sdk_unofficial-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 712e8f650c656b7f4299c427f8d6b4559a239d66622924e33ab315f97015af3a
MD5 8125ecd4ec9de98c1cf158181a279b58
BLAKE2b-256 3238bb3837f8d50ae93ad32522af2c687d9ba945e0b74ec2e00373d4999a32bb

See more details on using hashes here.

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