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

⚠️ 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')
)

# 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"])

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 /expirationchain

  • Parameters:

    • symbol (required)

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 RRRRRR␣␣YYMMDD[C/P]STRIKE AAPL  250926C00257500 Two spaces; zero-padded strike (8+ digits)
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)

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.1.4.tar.gz (52.9 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.1.4-py3-none-any.whl (51.1 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: schwab_sdk_unofficial-0.1.4.tar.gz
  • Upload date:
  • Size: 52.9 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.1.4.tar.gz
Algorithm Hash digest
SHA256 59cc4168eb14b87e2707d85f39bc86958ac44ba89bdb14f463120fef25b5def2
MD5 97a523f50312f1ba75fae8583dc37d46
BLAKE2b-256 855fc17f833c8d0a537827444f4ee1dbfd8bf41fe6fdfbed1f874d199546c839

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for schwab_sdk_unofficial-0.1.4-py3-none-any.whl
Algorithm Hash digest
SHA256 c9b8c29845f5b670dbc4a3c1e611268c4cadef4ab1b765ca1342efcc45d66f7f
MD5 74d237b4ee8097124b8893773385cbcb
BLAKE2b-256 647397a37ffaf5527d1da5aa6f5ce537d9ba57327dfc5a96c0b1bd6b41d7171e

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