Cliente ligero para API de Schwab: OAuth, REST y Streaming WebSocket
Project description
Schwab SDK (Python)
Cliente ligero para la API de Schwab: OAuth, REST (Trader/Market Data) y Streaming WebSocket.
- Enfoque: wrappers directos, sin validaciones complejas; manejo robusto de tokens, refresh y reintentos.
- Cobertura: Accounts, Orders, Market Data y Streaming (Level One, Book, Chart, Screener, Account Activity).
Instalación
Paquete PyPI no oficial (nombre de distribución):
pip install schwab_sdk_unofficial
Import en código (módulo):
from schwab_sdk import Client
⚠️ Nota: El nombre del paquete en PyPI es schwab_sdk_unofficial, pero el import se mantiene como schwab_sdk para una API limpia.
Tabla de contenidos
- Requisitos
- Configuración
- Inicio rápido
- Autenticación (OAuth)
- Manejo de requests y errores (REST)
- Accounts (
accounts.py) - Orders (
orders.py) - Market Data (
marketpy) - Streaming WebSocket (
streamingpy) - Troubleshooting avanzado
- Contribuciones
- Disclaimer
- Licencia
Requisitos
- Python 3.9+
- Instalar dependencias:
pip install requests websocket-client flask
Configuración
Crear .env (o exportar variables de entorno):
SCHWAB_CLIENT_ID=tu_client_id
SCHWAB_CLIENT_SECRET=tu_client_secret
SCHWAB_REDIRECT_URI=https://127.0.0.1:8080/callback
Los tokens se guardan en schwab_tokens.json y se rotan automáticamente (access ~29min; aviso de re-login al expirar refresh ~7d).
Inicio rápido
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')
)
# Primer uso: login OAuth (abre el navegador)
client.login()
# REST
quotes = client.market.get_quotes(["AAPL","MSFT"]) # Market Data
accounts = client.account.get_accounts() # Cuentas
# Streaming (Level One equities)
ws = client.streaming
ws.on_data(lambda f: print("DATA", f))
ws.connect(); ws.login()
ws.equities_subscribe(["AAPL"])
Autenticación (OAuth)
client.login(timeout=300, auto_open_browser=True)- Útiles:
client.has_valid_token(),client.refresh_token_now(),client.logout() - Internamente: servidor HTTPS callback adhoc (dev), intercambio de code por tokens, auto-refresh y aviso al expirar refresh.
Manejo de requests y errores (REST)
Todas las llamadas REST usan Client._request() con:
- Headers de Authorization automáticos
- Reintento de refresh en 401 (una vez) y reenvío inmediato
- Reintentos con backoff para 429/5xx (exponencial con factor 0.5)
Accounts (accounts.py)
get_account_numbers() -> List[dict]
- GET
/accounts/accountNumbers - Devuelve pares
accountNumberyhashValue. - Ejemplo de respuesta:
[
{"accountNumber":"12345678","hashValue":"827C...AC12"}
]
get_accounts(params: dict|None=None) -> dict
- GET
/accounts - Parámetros (query):
fields(opcional): actualmente la API aceptapositionspara devolver posiciones. Ej.:fields=positions.
get_account_by_id(account_hash: str, params: dict|None=None) -> dict
- GET
/accounts/{accountNumber} account_hash: identificador cifrado de la cuenta (hashValue).- Parámetros (query):
fields(opcional):positionspara incluir posiciones. Ej.:fields=positions.
find_account(last_4_digits: str) -> dict|None
- Helper que usa
get_account_numbers()y filtra por los últimos 4 dígitos para luego llamarget_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 -
UNA FECHA REQUERIDA: puedes pasar solo
from_dateo soloto_date. Si pasas una sola fecha, el SDK completará la otra para el mismo día:- Formato corto
YYYY-MM-DD: start →YYYY-MM-DDT00:00:00.000Z, end →YYYY-MM-DDT23:59:59.000Z - Formato ISO UTC completo
YYYY-MM-DDTHH:MM:SS.ffffffZ: se usa tal cual; si falta la otra fecha, se deriva con las 00:00:00.000Z o 23:59:59.000Z del mismo día.
- Formato corto
-
Params:
startDate: ISO UTC -YYYY-MM-DDTHH:MM:SS.ffffffZ(o cortoYYYY-MM-DD)endDate: ISO UTC -YYYY-MM-DDTHH:MM:SS.ffffffZ(o cortoYYYY-MM-DD)filters: dict opcional:types: string con tipos válidos:TRADE,RECEIVE_AND_DELIVER,DIVIDEND_OR_INTEREST,ACH_RECEIPT,ACH_DISBURSEMENT,CASH_RECEIPT,CASH_DISBURSEMENT,ELECTRONIC_FUND,WIRE_OUT,WIRE_IN,JOURNALsymbol: símbolo específicostatus: estado de transacciones
Nota: En algunas configuraciones de la API
typespuede considerarse obligatorio. El SDK no lo exige y lo trata como filtro opcional.
Ejemplo correcto:
from datetime import datetime, timezone, timedelta
# Obtener hashValue
hash_value = client.account.get_account_numbers()[0]['hashValue']
# Crear fechas en formato UTC
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)
# Llamada correcta
transactions = client.account.get_transactions(
account_number=hash_value, # ¡Usar 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"} # Opcional
)
get_transaction(account_hash: str, transaction_id: str) -> dict
- GET
/accounts/{accountHash}/transactions/{transactionId} - Parámetros (path):
account_hash(requerido)transaction_id(requerido): ID numérico de la transacción
- Devuelve el detalle de una transacción específica.
get_user_preferences() -> dict
- GET
/userPreference - Devuelve preferencias del usuario y, cuando aplica, información del streamer necesaria para WebSocket:
streamerSocketUrlschwabClientCustomerIdschwabClientCorrelIdSchwabClientChannelSchwabClientFunctionId
- Útil para inicializar
client.streaming(LOGIN y suscripciones).
Orders (orders.py)
Todas las respuestas incluyen metadatos HTTP y los datos nativos:
{
"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 - Requisitos de la API:
fromEnteredTimeytoEnteredTimeson obligatorios (ISO-8601). El SDK los completa a "últimos 60 días" si no los pasas. status(case-insensitive): se normaliza a mayúsculas. Valores aceptados por la 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(opcional): límite de registros (por defecto API 3000).- Formato de fecha/hora: 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 - Requisitos de la API:
fromEnteredTimeytoEnteredTimeson obligatorios (ISO-8601). Si no los pasas, el SDK usa "últimos 60 días". - Filtros idénticos a
get_orders(incluye normalización destatus). maxResults(opcional): límite de registros (por defecto API 3000).
place_order(account_number: str, order_data: dict) -> dict
- POST
/accounts/{accountNumber}/orders - Extrae
order_iddel headerLocationcuando está presente.
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} - Intenta extraer
order_iddesdeLocationsi el servidor lo devuelve.
replace_order(account_hash: str, order_id: str, new_order_data: dict) -> dict
- PUT
/accounts/{accountNumber}/orders/{orderId} - Devuelve nuevo
order_id(deLocation) cuando aplica.
preview_order(account_hash: str, order_data: dict) -> dict
- POST
/accounts/{accountNumber}/previewOrder - Intenta extraer
order_iddesdeLocationsi el servidor lo devuelve.
Helpers de payload
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)
Ejemplo (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=... - Parámetros:
symbols(requerido): str o lista de símbolos separados por coma. Ej.:AAPL,AMZN,$DJI,/ESH23.params(opcional):fields: subconjunto de datos. Valores:quote,fundamental,extended,reference,regular. Por defecto: todos.indicative: boolean (true|false) para incluir cotizaciones indicativas (p.ej., ETFs). Ej.:indicative=false.
get_quote(symbol: str, params: dict|None=None) -> dict
- GET
/{symbol}/quotes - Parámetros:
symbol(requerido): símbolo único (ej.:TSLA).params(opcional):fields: igual que enget_quotes.
get_option_chain(symbol: str, params: dict|None=None) -> dict
-
GET
/chains -
Parámetros:
symbol(requerido)contractType(opcional):CALL,PUT,ALLstrikeCount(opcional): int (nº de strikes arriba/abajo del ATM)includeUnderlyingQuote(opcional): booleanstrategy(opcional):SINGLE,ANALYTICAL,COVERED,VERTICAL,STRADDLE, etc.interval(opcional): número (intervalo entre strikes para spreads)strike(opcional): número (strike específico)range(opcional):ITM,NTM,OTM,ALLfromDate/toDate(opcional):YYYY-MM-DD(el SDK también acepta ISO y recorta a fecha)volatility,underlyingPrice,interestRate,daysToExpiration(opcionales): paraANALYTICALexpMonth(opcional):JAN–DEC,ALLoptionType(opcional)entitlement(opcional): tipo de cliente (PN,NP,PP)
-
GET
/expirationchain -
Parámetros:
symbol(requerido)
get_price_history(symbol, periodType="month", period=1, frequencyType="daily", frequency=1, startDate=None, endDate=None, params=None) -> dict
- GET
/pricehistory - Parámetros:
periodType:day|month|year|ytdperiod: intfrequencyType:minute|daily|weekly|monthlyfrequency: intstartDate/endDate(ms desde epoch)- Extra opcionales (
needExtendedHoursData, etc., según permisos)
get_movers(symbol_id: str, params: dict|None=None) -> dict
- GET
/movers/{symbol_id}(p.ej.,$DJI,$SPX,NASDAQ) - Parámetros (si aplica):
sort:VOLUME,TRADES,PERCENT_CHANGE_UP,PERCENT_CHANGE_DOWNfrequency:0,1,5,10,30,60(min). Por defecto0.
get_markets(params: dict|None=None) -> dict
- GET
/markets - Parámetros (query):
markets(requerido por API): array deequity,option,bond,future,forex(el SDK aceptaparams={"markets": ...})date(opcional):YYYY-MM-DD(si envías ISO, el SDK recorta a fecha)
get_market_hours(market_id: str, params: dict|None=None) -> dict
- GET
/markets/{market_id}(equity,option,bond,forex) - Parámetros (query):
date(opcional):YYYY-MM-DD(si envías ISO, el SDK recorta a fecha)
get_instruments(symbols: str|List[str], projection: str, extra_params: dict|None=None) -> dict
- GET
/instruments - Parámetros:
symbols(requerido): símbolo único o lista separada por comasprojection(requerido por API):symbol-search,symbol-regex,desc-search,desc-regex,search,fundamental
Nota: el SDK no lo exige en la firma pero se recomienda proveerlo.
get_instrument_by_cusip(cusip_id: str, params: dict|None=None) -> dict
- GET
/instruments/{cusip_id}
Streaming WebSocket (streaming.py)
Callbacks
on_data(fn)(frames de datos)on_response(fn)(confirmaciones/errores de comandos)on_notify(fn)(heartbeats/avisos)
Flujo básico
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 sin "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
Formatos de claves (tabla rápida)
| Tipo | Formato | Ejemplo | Notas |
|---|---|---|---|
| Equities | Ticker | AAPL, MSFT |
Mayúsculas |
| Options | RRRRRR␣␣YYMMDD[C/P]STRIKE |
AAPL 250926C00257500 |
Dos espacios; strike acolchonado (8+ dígitos) |
| Futures | /<root><month><yy> |
/ESZ25 |
Root/mes/año en mayúsculas |
| FuturesOptions | ./<root><month><year><C/P><strike> |
./OZCZ23C565 |
Dependiente del feed |
| Forex | PAIR |
EUR/USD, USD/JPY |
Separador / |
| Screener | PREFIX_SORTFIELD_FREQUENCY |
NYSE_VOLUME_5 |
Prefijo/criterio/frecuencia |
Ejemplos por servicio
Level One Options
ws.options_subscribe(["AAPL 250926C00257500"]) # cadena estándar de opciones
# Campos por defecto: 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
# Campos por defecto: 0,1,2,3,4,5,8,12,13,18,19,20,24,33
Level One Forex
ws.forex_subscribe(["EUR/USD","USD/JPY"])
# Campos por defecto: 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"]) # También: ws.nyse_book, ws.options_book
# Campos por defecto: 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() # Obtiene accountHash y se suscribe
Utilidades
subscribe(service, keys, fields=None)add(service, keys)unsubscribe(service, keys)/unsubscribe_service(service, keys)view(service, fields)
Campos recomendados
- 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
Tabla rápida de fields (copiar/pegar)
| Servicio | CSV de 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 |
| 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 |
Ejemplos SUBS / ADD / VIEW / UNSUBS por servicio
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"]) # agrega sin reemplazar
ws.equities_view([0,1,2,3,5,8,18]) # cambia campos
ws.equities_unsubscribe(["TSLA"]) # elimina símbolos
# 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"]) # genérico ADD
ws.view("NASDAQ_BOOK", [0,1,2,3]) # genérico VIEW
ws.unsubscribe_service("NASDAQ_BOOK", ["MSFT"]) # genérico UNSUBS
# CHART (Series)
ws.chart_equity(["AAPL"], fields=[0,1,2,3,4,5,6,7])
ws.add("CHART_EQUITY", ["MSFT"]) # genérico ADD
ws.view("CHART_EQUITY", [0,1,2,3,4,5,6,7]) # genérico VIEW
ws.unsubscribe("CHART_EQUITY", ["AAPL"]) # genérico 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]) # suscribe actividad
# Para UNSUBS necesitas la misma key usada en la SUBS (account_hash)
account_hash = getattr(client, "_account_hash", None)
if account_hash:
ws.unsubscribe_service("ACCT_ACTIVITY", [account_hash])
Guía rápida de campos (IDs → significado)
Nota: los mapeos exactos pueden variar según permisos/versión. A continuación, equivalencias prácticas observadas en los frames.
LEVELONE_EQUITIES
| ID | Campo |
|---|---|
| 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 | Campo |
|---|---|
| 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 (si provisto) |
LEVELONE_FUTURES
| ID | Campo |
|---|---|
| 0 | symbol/key |
| 1 | bidPrice |
| 2 | askPrice |
| 3 | lastPrice |
| 4 | bidSize |
| 5 | askSize |
| 8 | totalVolume |
| 12 | openInterest |
| 13 | contractDepth/series info (según feed) |
| 18 | netChange |
| 19 | sessionChange (o días/indicador según feed) |
| 20 | percentChange/ratio (según feed) |
| 24 | lastSettlement/mark |
| 33 | priorSettle |
Estructura de frames
- Confirmaciones (
response):{ "response": [ { "service":"ADMIN","command":"LOGIN","content":{"code":0,"msg":"..."}} ] } - Datos (
data):{ "service":"LEVELONE_EQUITIES","timestamp":...,"command":"SUBS","content":[{"key":"AAPL",...}] } - Notificaciones (
notify):{ "notify": [ { "heartbeat": "..." } ] }
Cheat-sheet Streamer API (parámetros y comandos)
- Conexión y prerequisitos
- Auth: usa el Access Token del flujo OAuth.
- IDs de sesión (de
GET /userPreference):schwabClientCustomerId,schwabClientCorrelId,SchwabClientChannel,SchwabClientFunctionId. - Transporte: WebSocket JSON. Un solo stream por usuario (si abres más: código 12 CLOSE_CONNECTION).
- Envoltura de cada comando
- Campos comunes:
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.): identificador único del request.SchwabClientCustomerIdySchwabClientCorrelId(recomendados): deuserPreference.parameters(opcional): depende del servicio/comando.
- Notas:
SUBSsobrescribe lista;ADDagrega;UNSUBSquita;VIEWcambiafields.
- ADMIN (sesión)
LOGIN(service=ADMIN,command=LOGIN)- parameters:
Authorization(token sin "Bearer"),SchwabClientChannel,SchwabClientFunctionId.
- parameters:
LOGOUT(service=ADMIN,command=LOGOUT)- parameters: vacío.
- LEVEL ONE (cotizaciones L1)
- parameters comunes:
keys(req., lista CSV),fields(opcional, índices). LEVELONE_EQUITIES:keystickers en mayúsculas (ej.:AAPL,TSLA).LEVELONE_OPTIONS:keysformato Schwab opcionesRRRRRR YYMMDD[C/P]STRIKE.LEVELONE_FUTURES:keys/<root><monthCode><yearCode>(mes: F,G,H,J,K,M,N,Q,U,V,X,Z; año 2 dígitos), ej.:/ESZ25.LEVELONE_FUTURES_OPTIONS:keys./<root><month><yy><C|P><strike>, ej.:./OZCZ23C565.LEVELONE_FOREX:keysparesBASE/QUOTECSV (ej.:EUR/USD,USD/JPY).
- BOOK (Level II)
- servicios:
NYSE_BOOK,NASDAQ_BOOK,OPTIONS_BOOK. - parameters:
keys(req., tickers),fields(opcional, índices de niveles).
- CHART (velas streaming)
CHART_EQUITY:keysequities;fieldsíndices (OHLCV, time, seq).CHART_FUTURES:keysfuturos (mismo formato que L1 futuros);fieldsíndices.
- SCREENER (gainers/losers/activos)
- servicios:
SCREENER_EQUITY,SCREENER_OPTION. keyspatrónPREFIX_SORTFIELD_FREQUENCY, ej.:EQUITY_ALL_VOLUME_5.PREFIXejemplos:$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=día completo).
fields(opcional): índices de campos del screener.
- ACCOUNT (actividad de cuenta)
- servicio:
ACCT_ACTIVITY(SUBS/UNSUBS). keys(req.): identificador arbitario de tu sub; si envías varios, usa el primero.fields(recomendado):0(o0,1,2,3según ejemplo y necesidad).
- Respuestas del servidor
- Tipos:
response(a tus requests),notify(heartbeats),data(flujo de mercado). - Códigos clave:
0SUCCESS,3LOGIN_DENIED,11SERVICE_NOT_AVAILABLE,12CLOSE_CONNECTION,19REACHED_SYMBOL_LIMIT,20STREAM_CONN_NOT_FOUND,21BAD_COMMAND_FORMAT,26/27/28/29éxitos deSUBS/UNSUBS/ADD/VIEW.
- Delivery Types
All Sequence: todo con número de secuencia.Change: sólo campos cambiados (conflado).Whole: mensajes completos con throttling.
- Buenas prácticas
- Haz
LOGINy esperacode=0antes deSUBS/ADD. - Para agregar símbolos sin perder los existentes, usa
ADD(noSUBS). - Cambia
fieldsconVIEWpara performance. - Maneja
notify(heartbeats) y reconecta si se pierden. - Reutiliza tu
SchwabClientCorrelIddurante la sesión. - Si ves
19(límite de símbolos), distribuye cargas (sharding) por servicio/sesión.
Troubleshooting avanzado
notify.code=12(Only one connection): cierra otras sesiones WebSocket activas.response.content.code=3(Login denied): token inválido/expirado →client.login().response.content.code=21(Bad command formatting): revisa formato deAuthorization(sinBearer) y claves (espaciado en opciones, mayúsculas).- 401 REST persistente: borra
schwab_tokens.jsony relanzaclient.login(). - Latencia alta/frames perdidos: evita reconexiones paralelas; usa la resuscripción automática del SDK.
Contribuciones
¡Tu colaboración es bienvenida! Ideas, issues y PRs ayudan a mejorar el SDK:
- Abre un issue con detalles claros (entorno, pasos, error esperado/obtenido).
- Propón mejoras de cobertura de endpoints y ejemplos.
- Sigue un estilo claro y añade pruebas o ejemplos mínimos cuando sea posible.
Si quieres mantener sesiones de trabajo o discutir roadmap, abre un issue etiquetado como discussion.
Disclaimer
Este proyecto es no oficial y no está afiliado, patrocinado ni respaldado por Charles Schwab & Co., Inc. “Schwab” y otras marcas son propiedad de sus respectivos titulares. El uso de este SDK está sujeto a los términos y condiciones de las APIs de Schwab y a la normativa aplicable. Úsalo bajo tu propio criterio y responsabilidad.
Licencia
MIT (LICENSE)
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_sdk_unofficial-0.1.1.tar.gz.
File metadata
- Download URL: schwab_sdk_unofficial-0.1.1.tar.gz
- Upload date:
- Size: 52.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
88ce66320b2919bc0c887f3132d406c1a44b6ec13a73f4783d994f6aa7676527
|
|
| MD5 |
b3f3272a1f343ab62bc033326ad79458
|
|
| BLAKE2b-256 |
ce98a6c827bf1a4ce349bd40eed7df2d4fdec103155737622f2f8208cc6f91de
|
File details
Details for the file schwab_sdk_unofficial-0.1.1-py3-none-any.whl.
File metadata
- Download URL: schwab_sdk_unofficial-0.1.1-py3-none-any.whl
- Upload date:
- Size: 51.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e9e0af2b87207ea6768265c7ec23b0398f4f86f03ac73419805acc58fe58e846
|
|
| MD5 |
a1ce675b79dc1269de61f33badbbb15b
|
|
| BLAKE2b-256 |
ce7cfe4121fe006b320741772cb90ba5b8e0216693885dde888ad87b309907cf
|