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.pemWhat 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)
failed to verify signature / sign check error (code 1000)Possible causes
- The private key is incorrect.
- 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).
- On Windows, a backslash in the path is treated as an escape character.
Troubleshooting steps
- Confirm the private key file is correct.
- Regenerate the key pair in the Developer Center and update the local private key.
- Windows users: add an
rprefix to the path string, such asr'C:\Users\Foo\Desktop\rsa_private_key.pem'.
ValueError: Unable to read this file, version 136 != 0
ValueError: Unable to read this file, version 136 != 0Cause: 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:
| Format | File header |
|---|---|
| PKCS#1 | -----BEGIN RSA PRIVATE KEY----- |
| PKCS#8 (recommended for every SDK) | -----BEGIN PRIVATE KEY----- |
Could not deserialize key data
Could not deserialize key dataCause: 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.8request sign failed. int() argument must be a string
request sign failed. int() argument must be a stringCause: 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)
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)
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)
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)
unauthorized: Please login in (code 1200)Possible causes
tiger_idandaccountdo not belong to the same developer account.- 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
account is not authorized to the api userCause: the account field is incorrect, or it does not match the current tiger_id.
Account formats
| Account type | Format |
|---|---|
| Prime Account (live) | 5–10 digits |
| Global Account (live) | Begins with U |
| Paper trading account | 17 digits |
SSL: CERTIFICATE_VERIFY_FAILED or response sign verify failed
SSL: CERTIFICATE_VERIFY_FAILED or response sign verify failedPossible causes
- Your own public key was incorrectly placed in the
tiger_public_keyfield. That field is managed internally by the SDK and must not be modified. - The Python installation does not include root certificates (common when Python was installed from a package on macOS).
- 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 runInstall Certificates.command. - Option 2: install a certificate bundle with
pip install certifi; if the error persists, runpip install pip-system-certs. - Option 3 (temporary debugging only, not for production):
import ssl
ssl._create_default_https_context = ssl._create_unverified_contextOption 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
unable to get local issuer certificateCause: the local root certificate bundle is missing.
Solution:
pip install certifi
# if the error persists
pip install pip-system-certsmacOS users can also run Applications/Python x.x/Install Certificates.command.
module 'http.client' has no attribute 'HTTPConnection'
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)
stomp.exception.ConnectFailedException (persistent connection failure)Possible causes
- A temporary server-side failure.
- The network is unreachable or blocked by a firewall.
- An SSL configuration issue.
Troubleshooting steps
- Check network connectivity.
- Confirm the
use_sslparameter ofPushClienthas not been set toFalse(the default isTrue). - Retry later. If the failure persists, contact support.
Unknown response frame type: '' (frame length was 3)
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)
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
code=4001 kick out by a new connectionCause: 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?
| Market | Permission | Coverage |
|---|---|---|
| US stocks | L1 (Nasdaq Basic) | Real-time Nasdaq quotes, top-of-book bid/ask, and trade ticks |
| US stocks | L2 (Nasdaq TotalView) | Nasdaq 40-level depth |
| Hong Kong stocks | BMP | Manually refreshed quotes only, without order book data |
| Hong Kong stocks | L2 | Automatically pushed quotes, 10-level depth, trade ticks, and broker queue |
| US options | L1 | Best bid/ask across 16 exchanges and trade ticks |
| Hong Kong options | L2 | Provided together with HK Futures Exchange L2; not sold separately |
| Futures | L2 | Real-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/ETFs | Futures | Options | Standard quotes | Depth quotes |
|---|---|---|---|---|---|
| API enabled | 20 | 10 | 10 | 20 | 10 |
| Total assets > 10k USD or volume > 100k USD | 200 | 20 | 200 | 100 | 20 |
| Total assets > 50k USD or volume > 500k USD | 500 | 50 | 500 | 500 | 100 |
| Total assets > 500k USD or volume > 2M USD | 1000 | 100 | 1000 | 1000 | 200 |
| Total assets > 1M USD or volume > 5M USD | 2000 | 200 | 2000 | 2000 | 500 |
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?
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?
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 passtrade_session=TradingSession.PreMarket / AfterHourstoget_bars. - Overnight: requires the additional
usOvernightpermission; useget_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?
get_bars support?| Period | Historical 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?
get_bars return adjusted prices?Yes, controlled by the right parameter:
| Value | Meaning |
|---|---|
QuoteRight.BR (default) | Forward-adjusted |
QuoteRight.NR | Unadjusted |
The API returns adjusted prices directly and does not provide a separate adjustment factor.
How far ahead can get_trading_calendar query?
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?
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 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 mean in trade ticks?partCode is the exchange code where a US trade occurred. Common values:
| partCode | Exchange |
|---|---|
n | NYSE |
t | NSDQ (Nasdaq) |
p | ARCA (NYSE Arca) |
z | BZX (Cboe BZX) |
k | EDGX (Cboe EDGX) |
v | IEX |
d | ADF (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_connectedMarket 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_assets and get_assets?get_prime_assets | get_assets | |
|---|---|---|
| Applicable accounts | Prime Accounts, paper trading accounts | Global Accounts (Prime Accounts can call it, but most fields are empty) |
| Return value | PortfolioAccount, with segments['S'] (securities) and segments['C'] (futures) | list[PortfolioAccount], with SecuritySegment and CommoditySegment |
| Recommended for | Net liquidation, buying power, unrealized loss, position value | Global 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 useRead 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'
'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 presentWhat currency does realized_pnl use?
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&LDoes outside_rth=True apply to every order type?
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=Trueis ignored. - Stop orders (STP) and trailing stop orders (TRAIL): also regular hours only;
outside_rthis ignored. - Attached orders (take-profit / stop-loss
order_leg): setoutside_rth=Falseexplicitly.
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?
get_orders, get_open_orders, and get_filled_orders differ?| Method | Returns | Notes |
|---|---|---|
get_orders | Orders 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_orders | Working orders, including partially filled orders that are still working | Partially filled orders have filled > 0 and remaining > 0 |
get_filled_orders | Orders with executions, including partially filled orders that were later canceled | A partially filled and then canceled order may be in state CANCELLED or HELD, not necessarily FILLED |
get_cancelled_orders | Canceled 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?
| Field | Description |
|---|---|
account | Account number |
symbol | Symbol, such as 'AAPL' or '00700' |
sec_type | Contract type: STK (stock) / OPT (option) / FUT (futures) |
action | Side: BUY / SELL |
order_type | Order type: MKT (market) / LMT (limit) / STP (stop) / STP_LMT (stop limit) / TRAIL (trailing stop) |
quantity | Order quantity |
limit_price | Required for limit orders |
aux_price | Required for stop orders (trigger price) |
API Usage Notes
Why does nextPageToken keep returning the same batch of data?
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 retrievedWhat does a non-empty nextPageToken mean?
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?
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:
| Tier | Limit | Representative methods |
|---|---|---|
| High | 120 requests/minute | place_order, get_orders, get_stock_briefs, get_trade_ticks, get_option_briefs |
| Medium | 60 requests/minute | get_bars, get_depth_quote, get_prime_assets, get_positions, get_option_chain |
| Low | 10 requests/minute | grab_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
[5 rows x 9 columns] instead of the full datapandas 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
| Code | Meaning | Common cause |
|---|---|---|
0 | Success | — |
1 | Server error | Unprocessable parameters or an internal service error |
2 | Network timeout | Unstable network; consider deploying closer to the API region |
4 | Access forbidden | IP not whitelisted / signature failure / subscription quota exceeded / institutional user did not pass secret_key |
5 | Rate limit exceeded | Returns HTTP 429; the error message contains the limit description |
1000 | Common parameter error | Signature error / incorrect tiger_id / malformed request parameters |
1010 | Business parameter error | Empty symbol / malformed parameter / unsupported sec_type |
1200 | Prime Account trading error | Outside trading hours / market order in pre-market or after-hours / insufficient position |
1300 | Paper account trading error | Similar to Prime Account errors |
4000 | Permission denied | Bar range exceeded / market data device already held / insufficient market data permission |
4001 | Connection kicked out | A new connection was established and the old one was disconnected |
For the complete error code list, see the Error Codes guide.
Updated 1 day ago
