Frequently Asked Questions

Authentication, Configuration, and Networking

How do I obtain my Tiger ID, private key, and account number?

Sign in to the Developer Center. Your Tiger ID is displayed on the page. Select Generate Key to create a key pair and download the configuration file (tiger_openapi_config.properties), which contains fields such as tiger_id, private_key, and account. Your account number is also available in Tiger Trade under Me > Account Management.


Which private-key format should I use for Python and Java?

PKCS#8 is recommended for every SDK.

Both formats are available for download when you generate the key pair in the Developer Center. To convert manually:

# PKCS#1 to PKCS#8
openssl pkcs8 -topk8 -inform PEM -in private_pkcs1.pem -outform PEM -nocrypt -out private_pkcs8.pem

# PKCS#8 to PKCS#1
openssl rsa -in private_pkcs8.pem -out private_pkcs1.pem

# Export a public key
openssl rsa -in private_pkcs1.pem -pubout -out public_key.pem

What is the API server address?

The SDK includes the server address (openapi.tigerfintech.com); no manual configuration is required.


failed to verify signature / sign check error (code 1000)

Possible causes

  1. The private key is incorrect.
  2. The private key does not match the public key uploaded to Developer Center (for example, the key pair was regenerated but the request still uses the old key).
  3. On Windows, a backslash in the path is treated as an escape character.

Troubleshooting steps

  1. Confirm the private key file is correct.
  2. Regenerate the key pair in the Developer Center and update the local private key.
  3. Windows users: add an r prefix to the path string, such as r'C:\Users\Foo\Desktop\rsa_private_key.pem'.

ValueError: Unable to read this file, version 136 != 0

Cause: incorrect private key format.

Solution: copy the private key again from the Developer Center in the format your SDK expects. Identify the format by its file header:

FormatFile header
PKCS#1-----BEGIN RSA PRIVATE KEY-----
PKCS#8 (recommended for every SDK)-----BEGIN PRIVATE KEY-----

Could not deserialize key data

Cause: the installed cryptography version is incompatible with the current SDK (for example 45.x).

Solution: downgrade to a compatible release:

pip install cryptography==42.0.8

request sign failed. int() argument must be a string

Cause: incorrect private key format, or the key content was not copied in full.

Solution: regenerate the key pair in the Developer Center and copy the private key content completely.


OSError: [Errno 22] Invalid argument (Windows path escaping)

A related error is request sign failed. Short octet stream on tag decoding.

Cause: on Windows, the backslash \ in a path is treated as an escape character.

Solution: add an r prefix to use a raw string, or switch to forward slashes:

client_config.private_key = read_private_key(r'C:\Users\Foo\Desktop\rsa_private_key.pem')
# or
client_config.private_key = read_private_key('C:/Users/Foo/Desktop/rsa_private_key.pem')

public key error (code 1000)

Cause: tiger_id was not passed to the server correctly.

Solution: confirm client_config.tiger_id matches the Tiger ID shown in the Developer Center, with no extra spaces or line breaks.


failed to get developer information (code 1000)

Cause: the Tiger ID does not exist.

Solution: confirm tiger_id is correct.


unauthorized: Please login in (code 1200)

Possible causes

  1. tiger_id and account do not belong to the same developer account.
  2. Institutional users: the sub-account has not been granted API permission in the institutional console.

Solution: institutional users should ask their administrator to add the account under API permission management in the institutional console.


account is not authorized to the api user

Cause: the account field is incorrect, or it does not match the current tiger_id.

Account formats

Account typeFormat
Prime Account (live)5–10 digits
Global Account (live)Begins with U
Paper trading account17 digits

SSL: CERTIFICATE_VERIFY_FAILED or response sign verify failed

Possible causes

  1. Your own public key was incorrectly placed in the tiger_public_key field. That field is managed internally by the SDK and must not be modified.
  2. The Python installation does not include root certificates (common when Python was installed from a package on macOS).
  3. System SSL certificates are missing or expired.

Solutions

  • Option 1 (recommended when Python was installed from a package on macOS): open Applications/Python x.x/ and run Install Certificates.command.
  • Option 2: install a certificate bundle with pip install certifi; if the error persists, run pip install pip-system-certs.
  • Option 3 (temporary debugging only, not for production):
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
⚠️

Option 3 skips SSL certificate verification and exposes you to man-in-the-middle attacks. Use it only temporarily during local debugging.


unable to get local issuer certificate

Cause: the local root certificate bundle is missing.

Solution:

pip install certifi
# if the error persists
pip install pip-system-certs

macOS users can also run Applications/Python x.x/Install Certificates.command.


module 'http.client' has no attribute 'HTTPConnection'

Cause: OpenSSL is not installed on the system, or Python was compiled without linking to OpenSSL.

Solution: first confirm OpenSSL is available:

python -c "import ssl; print(ssl.OPENSSL_VERSION)"

If that errors, reinstall Python and ensure it is linked against OpenSSL.


stomp.exception.ConnectFailedException (persistent connection failure)

Possible causes

  1. A temporary server-side failure.
  2. The network is unreachable or blocked by a firewall.
  3. An SSL configuration issue.

Troubleshooting steps

  1. Check network connectivity.
  2. Confirm the use_ssl parameter of PushClient has not been set to False (the default is True).
  3. Retry later. If the failure persists, contact support.

Unknown response frame type: '' (frame length was 3)

Cause: PushClient was initialized with use_ssl=False, so the protocols do not match.

Solution: remove the use_ssl=False parameter and use the default SSL connection.


OSError: [WinError 10038] (Windows, operation on something that is not a socket)

Cause: a push method (such as query_subscribed_quote) was called before the connection finished reconnecting.

Solution: run the operation from the on_connected callback, or check the connection state first:

if push_client.is_connected():
    push_client.query_subscribed_quote()

code=4001 kick out by a new connection

Cause: the same Tiger ID opened more than one persistent connection; the newer connection kicks out the older one.

Note: paper trading and live accounts under the same Tiger ID share a single WebSocket connection. To work with both accounts over one connection, subscribe to each account from the same PushClient instance.


Market Data

What market data permissions are available?

MarketPermissionCoverage
US stocksL1 (Nasdaq Basic)Real-time Nasdaq quotes, top-of-book bid/ask, and trade ticks
US stocksL2 (Nasdaq TotalView)Nasdaq 40-level depth
Hong Kong stocksBMPManually refreshed quotes only, without order book data
Hong Kong stocksL2Automatically pushed quotes, 10-level depth, trade ticks, and broker queue
US optionsL1Best bid/ask across 16 exchanges and trade ticks
Hong Kong optionsL2Provided together with HK Futures Exchange L2; not sold separately
FuturesL2Real-time quotes, 10-level depth, and trade ticks (L1 is included in L2 and not sold separately)

Historical-data and subscription quotas (bar counts and the number of subscribable symbols) increase automatically with account assets or trading volume. Meeting any one condition is sufficient:

Condition (any one)Stocks/ETFsFuturesOptionsStandard quotesDepth quotes
API enabled2010102010
Total assets > 10k USD or volume > 100k USD2002020010020
Total assets > 50k USD or volume > 500k USD50050500500100
Total assets > 500k USD or volume > 2M USD100010010001000200
Total assets > 1M USD or volume > 5M USD200020020002000500

Does a US Stock L2 purchase also apply to my paper trading account?

Yes. Market data permissions are associated with the Tiger ID, not with an individual account.


How do I activate US options L1 market data?

Purchase it in the Market Data Store in the Tiger Trade app. It takes effect for the API automatically, enabling option market data methods such as get_option_briefs, get_option_chain, get_option_trade_ticks, and get_option_bars.


Can multiple devices use market data simultaneously?

Market data access can only be held by one device at a time. When using multiple devices, call grab_quote_permission() to claim it. For multi-host deployments, create QuoteClient on a single host; TradeClient is not subject to this restriction, so multiple hosts can place orders concurrently.


Why does unsubscribing return According to your user level, you can only unsubscribe after 1 minute?

Cause: after subscribing you must wait at least one minute before unsubscribing. This is a market data service restriction.

Solution: wait one minute after the last subscribe operation before calling unsubscribe.


Why do financial_report and stock_fundamental return a permission error?

Fundamental-data methods such as get_financial_report, get_stock_fundamental, and get_financial_daily require an additional permission that is not enabled by default.

How to enable: contact support to apply. The corresponding Java SDK requests are QuoteFinancialReportRequest and QuoteStockFundamentalRequest, with the same permission requirement.


How do I request pre-market, after-hours, or overnight data?

  • Pre-market / after-hours: get_stock_briefs(symbols, include_hour_trading=True), or pass trade_session=TradingSession.PreMarket / AfterHours to get_bars.
  • Overnight: requires the additional usOvernight permission; use get_bars(..., trade_session=TradingSession.OverNight).
  • Pre-market and after-hours bars are available only from April 2024 onward, and only at intervals of 60 minutes or less.

What historical range does get_bars support?

PeriodHistorical range
Minute (1/5/15/30/60 minutes)Last 10 years (query day by day using the date parameter)
Daily and above (day/week/month/year)Complete history

A single request returns at most 1,200 records. Range queries using begin_time / end_time have additional limits: 1-minute and 5-minute bars cover only the last month, and 15/30/60-minute bars only the last year. For older minute bars, query day by day with the date parameter instead.


Does get_bars return adjusted prices?

Yes, controlled by the right parameter:

ValueMeaning
QuoteRight.BR (default)Forward-adjusted
QuoteRight.NRUnadjusted

The API returns adjusted prices directly and does not provide a separate adjustment factor.


How far ahead can get_trading_calendar query?

It provides market trading calendars from 2015 through the end of the current year (weekends and statutory holidays are excluded; temporary closures may not be). You can specify the market (Market.US / Market.HK / Market.CN) and a date range.


What does tickType * mean in trade ticks?

* means a neutral trade, where the SDK cannot tell whether the trade was buyer- or seller-initiated (this usually happens when the trade price falls exactly at the midpoint of the bid/ask). + means buyer-initiated and - means seller-initiated.


Is the sn field in trade ticks unique?

sn (sequence number) increases monotonically for a given symbol within a single trading day, so it can be used to order messages and de-duplicate locally. Values are not comparable across symbols or across trading days.


What does partCode mean in trade ticks?

partCode is the exchange code where a US trade occurred. Common values:

partCodeExchange
nNYSE
tNSDQ (Nasdaq)
pARCA (NYSE Arca)
zBZX (Cboe BZX)
kEDGX (Cboe EDGX)
vIEX
dADF (FINRA)

For the complete mapping, see tigeropen.common.consts.tick_constants.PART_CODE_MAP in the SDK.


How do I receive real-time market data, order status, and asset updates?

Open a WebSocket connection with PushClient and handle pushed data in callbacks instead of polling:

from tigeropen.push.push_client import PushClient
from tigeropen.tiger_open_config import TigerOpenClientConfig

client_config = TigerOpenClientConfig(props_path='your_config_directory_path')
protocol, host, port = client_config.socket_host_port
push_client = PushClient(host, port, use_ssl=(protocol == 'ssl'))
push_client.connect(client_config.tiger_id, client_config.private_key)

# Subscribe to stock quotes
push_client.subscribe_quote(['AAPL'])

# Subscribe to asset changes (a full snapshot is pushed every 5 seconds by default)
push_client.subscribe_asset(account='your account number')

Can I subscribe to Hong Kong option quotes?

Yes. Use push_client.subscribe_option. This requires HK Futures Exchange L2 (HKEXFuturesQuoteLv2), which covers both Hong Kong futures and Hong Kong options. The data structure is similar to US options and includes latest price, bid/ask, and volume fields.


Are stock and option subscription quotas shared?

No. Standard market data quotas apply separately to each data type within that category, so stock and option subscription quotas are counted independently. Standard market data and Level 2 depth data are also counted as separate categories. For quota categories and tier limits, see Market Data Permissions and Limits.


What is the US option identifier format, and how do I build one?

A US option identifier is a 21-character string in the format SYMBOL YYMMDDP/CXXXXXXXX: two spaces between the symbol and the date, a 6-digit expiry, then P/C followed by 8 digits for the strike multiplied by 1,000 and left-padded with zeros.

Example:

  • AAPL 190118P00160000 = AAPL, expiring 2019-01-18, PUT, strike 160.0

Prefer the SDK helper to avoid formatting mistakes:

from tigeropen.common.util.contract_utils import get_option_identifier

identifier = get_option_identifier('AAPL', '20190104', 'PUT', 134)
# returns: 'AAPL  190104P00134000'

Hong Kong option identifiers are obtained from get_option_symbols; they use a different format and must not be built by hand.


Do push subscriptions survive reconnection?

No. After PushClient reconnects, previous subscriptions are not restored automatically. Register all subscriptions again in the on_connected callback:

def on_connected(frame):
    push_client.subscribe_quote(['AAPL', 'TSLA'])
    push_client.subscribe_asset(account=client_config.account)
    push_client.subscribe_order(account=client_config.account)

push_client.on_connected = on_connected

Market data generated while disconnected is not replayed; after reconnecting you only receive data pushed from that point onward.


Accounts and Trading

What is the difference between get_prime_assets and get_assets?

get_prime_assetsget_assets
Applicable accountsPrime Accounts, paper trading accountsGlobal Accounts (Prime Accounts can call it, but most fields are empty)
Return valuePortfolioAccount, with segments['S'] (securities) and segments['C'] (futures)list[PortfolioAccount], with SecuritySegment and CommoditySegment
Recommended forNet liquidation, buying power, unrealized loss, position valueGlobal Account sub-account roll-ups

If you use a Prime Account, always use get_prime_assets rather than get_assets, which returns many empty fields.


How do I read total account value and position P&L?

from tigeropen.trade.trade_client import TradeClient
from tigeropen.tiger_open_config import TigerOpenClientConfig

client_config = TigerOpenClientConfig(props_path='your_config_directory_path')
trade_client = TradeClient(client_config)

# Prime Account assets
assets = trade_client.get_prime_assets()
# Access each segment through segments (S = securities, C = futures/commodities)
net_liq = assets.segments['S'].net_liquidation          # net liquidation
gross_pos = assets.segments['S'].gross_position_value   # gross position value
# gross_position_value > net_liquidation indicates leverage is in use

Read per-position realized and unrealized P&L with get_positions():

from tigeropen.common.consts import Currency, Market, SecurityType

positions = trade_client.get_positions(
    sec_type=SecurityType.STK,
    currency=Currency.ALL,
    market=Market.ALL,
)
for position in positions:
    print(position.contract.symbol, position.realized_pnl, position.unrealized_pnl)

'PortfolioAccount' object has no attribute 'net_liquidation'

net_liquidation is not a direct attribute of PortfolioAccount; access it through the segments dictionary:

assets.segments['S'].net_liquidation  # securities segment net liquidation
assets.segments['C'].net_liquidation  # futures segment net liquidation, when present

What currency does realized_pnl use?

The currency of realized_pnl follows the market of the position: HKD for Hong Kong positions and USD for US positions. To normalize to a single currency, pass base_currency='USD' when calling the method.


Do I need to poll for real-time position P&L?

No polling is required. Subscribe to asset updates with PushClient:

push_client.subscribe_asset(account='your account number')
# The server pushes a full asset snapshot every 5 seconds by default, including current position P&L

Does outside_rth=True apply to every order type?

No. outside_rth (allow pre-market and after-hours trading) applies only to limit orders (LMT):

  • Market orders (MKT): always execute during regular hours only; outside_rth=True is ignored.
  • Stop orders (STP) and trailing stop orders (TRAIL): also regular hours only; outside_rth is ignored.
  • Attached orders (take-profit / stop-loss order_leg): set outside_rth=False explicitly.

To trade before or after regular hours you must use a limit order with outside_rth=True:

from tigeropen.trade.domain.order import LimitOrder
order = LimitOrder(account=client_config.account,
                   contract=contract,
                   action='BUY',
                   quantity=1,
                   limit_price=150.0,
                   outside_rth=True)

Set outside_rth=False explicitly on attached take-profit and stop-loss legs so they are not triggered outside regular hours:

from tigeropen.common.util.order_utils import order_leg

profit_taker = order_leg('PROFIT', 180.0, time_in_force='GTC', outside_rth=False)
stop_loss = order_leg('LOSS', 140.0, time_in_force='GTC', outside_rth=False)

How do get_orders, get_open_orders, and get_filled_orders differ?

MethodReturnsNotes
get_ordersOrders in all states (filterable by state)Returns the current day's orders by default; the states parameter is supported only for Global Accounts
get_open_ordersWorking orders, including partially filled orders that are still workingPartially filled orders have filled > 0 and remaining > 0
get_filled_ordersOrders with executions, including partially filled orders that were later canceledA partially filled and then canceled order may be in state CANCELLED or HELD, not necessarily FILLED
get_cancelled_ordersCanceled orders, including partially filled orders that were later canceled

Typical case: a limit order fills 50 shares and is then canceled manually. It appears in both get_filled_orders (it has executions) and get_cancelled_orders (it was canceled).


Which fields are required to place an order?

FieldDescription
accountAccount number
symbolSymbol, such as 'AAPL' or '00700'
sec_typeContract type: STK (stock) / OPT (option) / FUT (futures)
actionSide: BUY / SELL
order_typeOrder type: MKT (market) / LMT (limit) / STP (stop) / STP_LMT (stop limit) / TRAIL (trailing stop)
quantityOrder quantity
limit_priceRequired for limit orders
aux_priceRequired for stop orders (trigger price)

API Usage Notes

Why does nextPageToken keep returning the same batch of data?

When paging with page_token, every parameter other than page_token must stay unchanged between requests. Changing another parameter (such as start_time or limit) causes the server to error or to start over from the beginning.

Correct usage:

page_token = ''
while page_token is not None:
    response = trade_client.get_orders(page_token=page_token, limit=100)
    orders = response.result
    page_token = response.next_page_token  # None means all data has been retrieved

What does a non-empty nextPageToken mean?

A non-empty nextPageToken means more data is available. Even when the current page returned exactly limit records, a token is present whenever the total exceeds limit. When the returned next_page_token is None or empty, all data has been retrieved.


Which time do start_time and end_time filter on in get_orders?

By default they filter on order creation time (LATEST_CREATED). To filter on the latest status update time instead (for example, to query orders filled within a period), pass sort_by=OrderSortBy.LATEST_STATUS_UPDATED:

from tigeropen.common.consts import OrderSortBy

# Orders whose status changed (filled, canceled, and so on) since 2025-01-01
orders = trade_client.get_orders(
    start_time='2025-01-01',
    sort_by=OrderSortBy.LATEST_STATUS_UPDATED
)

Note: the sort_by parameter is supported only for Prime Accounts, not for Global Accounts.


Are API calls rate-limited?

Yes. Limits are counted independently per Tiger ID + method, over a rolling 60-second window:

TierLimitRepresentative methods
High120 requests/minuteplace_order, get_orders, get_stock_briefs, get_trade_ticks, get_option_briefs
Medium60 requests/minuteget_bars, get_depth_quote, get_prime_assets, get_positions, get_option_chain
Low10 requests/minutegrab_quote_permission, get_symbols, get_market_status, get_trade_rank

Exceeding a limit returns HTTP 429 and application code 5, with limit details in the error message. Repeatedly or persistently exceeding limits risks being blacklisted.


SDK Usage

Output shows [5 rows x 9 columns] instead of the full data

pandas truncates output by default. Add this at the start of your code:

import pandas as pd
pd.set_option('display.max_columns', 500)
pd.set_option('display.max_rows', 5000)
pd.set_option('display.width', 5000)

Error Code Quick Reference

CodeMeaningCommon cause
0Success
1Server errorUnprocessable parameters or an internal service error
2Network timeoutUnstable network; consider deploying closer to the API region
4Access forbiddenIP not whitelisted / signature failure / subscription quota exceeded / institutional user did not pass secret_key
5Rate limit exceededReturns HTTP 429; the error message contains the limit description
1000Common parameter errorSignature error / incorrect tiger_id / malformed request parameters
1010Business parameter errorEmpty symbol / malformed parameter / unsupported sec_type
1200Prime Account trading errorOutside trading hours / market order in pre-market or after-hours / insufficient position
1300Paper account trading errorSimilar to Prime Account errors
4000Permission deniedBar range exceeded / market data device already held / insufficient market data permission
4001Connection kicked outA new connection was established and the old one was disconnected

For the complete error code list, see the Error Codes guide.


Did this page help you?