Resources
FAQ
On this page13
Find your question by what you are doing: signing in, loading data, writing rules, backtesting, placing orders, then the platform, your account and support. The first sentence of each answer is the answer. New to FinLab? Start with the Quickstart.
Stuck? Try these first
| Situation | Try first | Details |
|---|---|---|
| Cannot sign in or fetch data | Run finlab.login() again, then test data.get('price:adj_close') after data.set_market('us') | Download data |
| Don't know the dataset key | Find fields with data.search('keyword') | finlab.data |
| Stop loss or take profit behaves unexpectedly | Check whether the position is sparse, or use hold_until() | Backtesting |
| Broker integration and live orders | Configure the account first, then preview orders with OrderExecutor | Orders |
Signing in
I can't find the login window, or data won't download. What do I do?
Run finlab.login() at the top of your script, then download the data again. This happens most often in VSCode, a terminal or another IDE.
finlab.login() opens the browser-based login flow:
import finlab
finlab.login()
from finlab import data
data.set_market('us')
close = data.get('price:adj_close')Automated environments cannot open a browser. Run this locally first:
python -m finlab token --envThen set the printed environment variables on the server or scheduler.
The legacy finlab.login('token') still works, but don't write tokens directly in notebooks or scripts.
Loading data
Why does my data stop at the end of 2023?
Because you haven't subscribed to VIP: free accounts get Taiwan data up to 2023-12-31 (the last trading day being 2023-12-29). Subscribe to get historical data up to the latest trading day.
Why does the downloaded data have dates but no values?
You probably set a universe filter with set_universe(). Run data.reset_universe() to remove it, or restart the kernel and run your code again.
data.get() raises DatasetNotFoundError. Why can't it find the dataset?
Check the dataset name with data.search(), then check that you are signed in. finlab.exceptions.DatasetNotFoundError (a RuntimeError subclass) has three common causes:
- The dataset name is misspelled
- You haven't signed in, or the login has expired
- A member without VIP is requesting the latest data
import finlab
from finlab import data
# 1. Confirm you are signed in
finlab.login()
# 2. Find the correct field name
print(data.search('close', market='us'))
# 3. Download with the correct name
data.set_market('us')
close = data.get('price:adj_close')
print(f"Data range: {close.index[0]} ~ {close.index[-1]}")For servers or scheduled jobs, run python -m finlab token --env locally first, then set the printed environment variables in the runtime environment. More download errors are covered in Download data.
The download succeeded, but the DataFrame is empty. Why?
Usually the filter in data.us_universe() or data.universe() is too strict, or the sector or industry name doesn't exist; the data source may also have no data for now. Check whether the table is empty:
from finlab import data
data.set_market('us')
close = data.get('price:adj_close')
if close.empty:
print("Data is empty: check whether the universe filter is too strict, or whether the dataset itself has no data")
else:
print(f"Data OK: {close.shape[0]} trading days, {close.shape[1]} stocks")How do I turn a company-profile column into a date-by-symbol table?
Copy the column onto every date to build a FinlabDataFrame with dates as the index and symbols as columns, so it combines with other data. Taking the beta column of us_company_profile as an example (the profile holds one current value per symbol):
import pandas as pd
from finlab import data
from finlab.dataframe import FinlabDataFrame
data.set_market('us')
profile = data.get('us_company_profile').set_index('symbol')
revenue = data.get('us_income_statement:revenue')
beta = profile['beta']
beta = FinlabDataFrame(
pd.DataFrame([list(beta)], columns=profile.index, index=revenue.index)
)For shares outstanding, use data.get('us_income_statement:weighted_average_shs_out'), which already has dates as the index and symbols as columns. The shares_outstanding column of us_company_profile is empty because the data source does not provide it.
Writing rules
Do I need reindex to align data of different frequencies?
No: data from data.get() aligns dates automatically in arithmetic and comparisons. Reindexing yourself only spends time adjusting data.
from finlab import data
data.set_market('us')
close = data.get('price:adj_close') # daily
net_income = data.get('us_income_statement:net_income')
total_assets = data.get('us_balance_sheet:total_assets')
roa = net_income / total_assets # quarterlyclose and roa are both FinlabDataFrames, so you don't need to turn roa into daily data first:
# No need for this
roa_daily = roa.reindex(close.index, method='ffill')
condition = (close < 30) & (roa_daily > 0)
# Write it directly
condition = (close < 30) & (roa > 0)FinLab computes roa_daily behind the scenes.
Why can't I write df.index = pd.to_datetime(df.index)? Can it be just a warning?
No, by design: assigning to FinlabDataFrame.index raises an AttributeError. It is a safety guard, not a generic date-format error, and it should not be downgraded to a warning.
A FinlabDataFrame returned by data.get() keeps the period labels of financial statement data, such as the US quarterly label 2024-US-Q1 (Taiwan data also uses 2024-Q1 and, for monthly revenue, 2024-M01). Overwriting index makes it easy to treat the reporting period as the date the data became public, so the backtest uses data that wasn't available yet (lookahead bias).
For factors such as AssetGrowth that combine data from different sources, you usually don't need to reindex the source data first:
from finlab import data
data.set_market('us')
close = data.get('price:adj_close')
asset = data.get('us_balance_sheet:total_assets')
# FinlabDataFrame aligns different frequencies during operations
asset_growth = asset / asset.shift(4) - 1
# If the final position needs daily trading dates, reindex the position at the end
position = (asset_growth > 0).index_str_to_date()
position = position.reindex(close.index, method='ffill')If you explicitly need dates instead of quarterly labels, use index_str_to_date(), which places each value on its company's actual filing date (Taiwan data also offers deadline(), the statutory filing deadline):
asset_by_filing = asset.index_str_to_date()For an external CSV or a custom market, parse and clean the dates as a regular pandas.DataFrame first, then wrap it as a FinlabDataFrame:
import pandas as pd
from finlab.dataframe import FinlabDataFrame
raw = pd.read_csv('factor.csv', index_col=0)
raw.index = pd.to_datetime(raw.index)
factor = FinlabDataFrame(raw)You are sure you want plain pandas index rules
Convert to pandas.DataFrame first. This leaves FinlabDataFrame's auto-alignment and lookahead protection behind, so you are responsible for checking the dates:
import pandas as pd
plain = pd.DataFrame(asset_by_filing)
plain.index = pd.to_datetime(plain.index)How do I stop trading in certain months, such as July and August?
Build a month condition and intersect it with your other conditions. To have no signals in July and August:
month_condition = close.index.to_series().apply(lambda x: x.month not in [7, 8])How can I customize the rebalance frequency further?
Apply pandas' resample directly to the position before you pass it to the backtest; see the pandas documentation.
Backtesting
Why doesn't my stop loss or take profit exit the trade?
Because when position holds a stock on a given day, sim() follows position. Only when the position's index is sparse (for example, one row per month) and the system finds no position for that day does it use stop_loss to decide whether to sell:
report = sim(position, stop_loss=0.1)This may seem counterintuitive, but consider: if the stop triggers and position is still True, should the system buy again the next day, or has it already stopped out? That is ambiguous, so when position is True it takes priority over the stop: the simplest rule. It also means that if your position's index covers every trading day, stop_loss has no effect. Set the stop through hold_until() instead:
buy = close > close.average(20)
sell = close < close.average(60)
# Original
position = buy.hold_until(sell)
# With a stop loss
position = buy.hold_until(sell, stop_loss=0.1)Take the entries and exits from your position, then set the stop loss or take profit:
position.is_entry().hold_until(position.is_exit(), stop_loss=0.1)The backtest finished with no trades. Why?
Most often the entry conditions are too strict: position is almost entirely False, and get_trades() returns an empty DataFrame. Other causes: the date range is too short, or the stock pool after filtering is too small. Check how many stocks qualify per day:
from finlab.backtest import sim
# Check entry signals
entry_stats = position.sum(axis=1)
print(f"Average daily entry stocks: {entry_stats.mean():.2f}")
print(f"Maximum entry stocks: {entry_stats.max()}")
if entry_stats.mean() < 1:
# Too strict: relax the conditions (e.g. a price range), or use .is_largest(N) for a fixed count
# Rank by a score, not a condition: always the 30 strongest relative to their 20-day average
ma20 = close.average(20)
position_fixed = (close / ma20).is_largest(30)
report = sim(position, resample='M')
trades = report.get_trades()
print(f"{len(trades)} trades" if len(trades) else "No trade records. Please relax entry conditions.")The backtest raises KeyError: Timestamp('2023-05-01'). Why?
A date is missing from the data, usually because several data sources cover different date ranges, or shift() or rolling() left the first rows empty. Three ways to fix it:
from finlab import data
data.truncate_start = '2020-01-01'from finlab import data
data.set_market('us')
close = data.get('price:adj_close')
volume = data.get('price:volume')
# Check date ranges
print(f"Close prices: {close.index[0]} ~ {close.index[-1]}")
print(f"Volume: {volume.index[0]} ~ {volume.index[-1]}")
# Keep only dates both tables have
common_dates = close.index.intersection(volume.index)
close_aligned = close.loc[common_dates]
volume_aligned = volume.loc[common_dates]position = (close > close.average(20)) & (volume > 1_000_000)
position_cleaned = position.dropna(how='all', axis=0) # Remove rows that are all NaN
position_cleaned = position_cleaned.fillna(False)More backtest errors are covered in Backtesting.
Why do my backtest returns differ from my own calculation?
Check three things first: whether you used adjusted prices, the exact entry and exit timing of your strategy, and whether trades fill at the open or the close. If you still have questions, contact FinLab support.
Results on my machine or Colab differ from the platform. Which is right?
The results from running the latest version of the package on your machine or on Colab are authoritative.
Can I stop the backtest from uploading a web report?
You don't need to: locally and on Colab nothing is uploaded by default. sim()'s upload defaults to None and uploads only when the FINLAB_FORCED_STRATEGY_NAME environment variable is set, as in cloud-scheduled runs and notebooks run manually on the FinLab Studio website. Since 2.1.0, Studio AI and the desktop app also need upload=True to upload. You can still turn it off explicitly with upload=False:
report = sim(position, upload=False)Why doesn't display_mae_mfe_analysis show the Edge ratio?
Set the mae_mfe_window parameter in sim(); see finlab.backtest.
How should I optimize and validate a strategy?
Follow the Research to production workflow and tune parameters with Strategy optimization, keeping an out-of-sample period for validation.
Placing orders
Can I place orders directly on the FinLab platform?
No. To connect to a broker's order API, run your code in another environment.
How do I connect to a broker API?
For US stocks, FinLab connects to Charles Schwab (SchwabAccount). Taiwan brokers (SinoPac, E.SUN, Fubon, Taishin, formerly Masterlink) are also supported for the Taiwan market. After opening the account, follow Orders.
The broker account won't connect. What should I check?
Usually the environment variables are wrong (API key, app secret, token path), the token file is missing or expired, or the network is down. When SchwabAccount() (or a Taiwan broker account such as SinopacAccount()) fails, check each in turn:
import os
from finlab.online.schwab_account import SchwabAccount
# Check environment variables
required_vars = [
'SCHWAB_API_KEY',
'SCHWAB_SECRET',
'SCHWAB_TOKEN_PATH',
]
for var in required_vars:
if not os.environ.get(var):
print(f"{var} is not set")
# Check the token file
token_path = os.environ.get('SCHWAB_TOKEN_PATH')
if token_path and not os.path.exists(token_path):
print(f"Token file not found: {token_path}")
# Try to connect
try:
acc = SchwabAccount()
print("Account connection successful")
except Exception as e:
print(f"Connection failed: {e}. Check your API key, app secret and token file.")Taiwan broker accounts read their own environment variables (API keys, certificate path and password); Orders lists them per broker.
How do I avoid selling holdings that don't belong to this strategy?
Create a Position for your manual holdings (positions unrelated to the strategy), add it to the strategy's position, and pass the sum to OrderExecutor:
from finlab.online.order_executor import OrderExecutor, Position
strategy = Position.from_report(report, 100_000) # This strategy's position, with $100,000
manual = Position({'AAPL': 10}) # Manual holding: 10 shares of AAPL you hold separately
executor = OrderExecutor(strategy + manual, account=account)
executor.create_orders(view_only=True) # Preview firstOrders fail with insufficient funds. What now?
The available cash can't buy the stocks in the target position. Common causes: too much capital allocated to the strategy, commissions not accounted for, or an account balance that differs from what you expect. Compare available cash with the allocation before ordering:
from finlab.online.order_executor import OrderExecutor, Position
fund = 100_000 # Capital (USD) to allocate to this strategy
available_cash = account.get_cash()
print(f"Available funds: ${available_cash:,.0f}")
print(f"Planned allocation: ${fund:,.0f}")
if fund > available_cash * 0.9: # Leave a 10% buffer
print("Insufficient funds: reduce the allocation, add funds, or hold fewer stocks")
else:
position = Position.from_report(report, fund)
executor = OrderExecutor(position, account=account)
executor.create_orders(view_only=True) # Preview firstSome orders were rejected by the broker. Why?
In the Taiwan market, the usual cause is a disposition stock: it requires cash or shares to be deposited at the broker before it can be traded. Suspended stocks and limit-up or limit-down locks also prevent execution. List alert and disposition stocks among the new orders before sending them:
from finlab.online.order_executor import OrderExecutor
executor = OrderExecutor(position, account=account)
# 1. List alert and disposition stocks among the new orders
executor.show_alerting_stocks()
# 2. Preview first
executor.create_orders(view_only=True)
# 3. Execute after confirmation
if input("Enter 'YES' to confirm and execute orders: ") == 'YES':
executor.create_orders()If disposition stocks are listed, deposit the required cash or shares on your broker's platform before placing the orders.
I missed a strategy's entry or exit date. What should I do?
Use Edge ratio analysis (report.display_mae_mfe_analysis() with mae_mfe_window set in sim()) to judge whether entering mid-cycle makes sense.
Custom markets and external data
A custom market can't find its data file (FileNotFoundError). Why?
The CSV doesn't exist, the path is wrong, or you used a relative path from the wrong working directory. Check that the file exists before reading it, and print the working directory in the error:
import os
import pandas as pd
from finlab.market import Market
class CryptoMarket(Market):
def __init__(self, data_dir='./data'):
self.data_dir = data_dir
def get_price(self, trade_at_price='close', adj=True):
file_path = os.path.join(self.data_dir, f'crypto_{trade_at_price}.csv')
if not os.path.exists(file_path):
raise FileNotFoundError(
f"Data file not found: {file_path}\n"
f"Check that the file is in {self.data_dir} and the name is correct. "
f"Working directory: {os.getcwd()}"
)
return pd.read_csv(file_path, index_col=0, parse_dates=True)
try:
market = CryptoMarket(data_dir='/path/to/data')
close = market.get_price('close')
except FileNotFoundError as e:
print(e)The full approach is in Custom markets.
The backtest fails with "Index must be DatetimeIndex". How do I fix the data?
The index isn't a DatetimeIndex, or columns hold strings instead of numbers. Convert both after reading the CSV:
import pandas as pd
df = pd.read_csv('data.csv', index_col=0)
# Convert the index to a DatetimeIndex
if not isinstance(df.index, pd.DatetimeIndex):
try:
df.index = pd.to_datetime(df.index)
except Exception as e:
print(f"Unable to convert the index: {e}")
print(f"Index samples: {df.index[:3].tolist()}")
print("Check that the first column is a date (e.g. 2020-01-01)")
# Convert non-numeric columns to numbers
non_numeric_cols = df.select_dtypes(exclude=['number']).columns.tolist()
for col in non_numeric_cols:
df[col] = pd.to_numeric(df[col], errors='coerce')Machine learning
Model training says there isn't enough data. What should I check?
The training set has fewer than 1000 samples, or aligning features with labels dropped a lot of rows. Count the training samples first:
from finlab import data
from finlab.ml import feature as mlf, label as mll
data.set_market('us')
with data.us_universe(index='SP500'):
close = data.get('price:adj_close')
features = mlf.combine({'momentum': close / close.shift(20)}, resample='W')
labels = mll.excess_over_mean(features.index, resample='W')
# Align features and labels
data_ml = features.join(labels.rename('label'), how='inner')
# Training split
train_data = data_ml[data_ml.index.get_level_values(0) <= '2022-12-31']
print(f"Training set: {len(train_data)} samples")With fewer than 1000 samples:
- Increase the historical data range
- Use a shorter resample interval (for example
'M'to'W'): the closer together the samples, the more rows - Reduce the number of features (fewer missing values)
The full process is in the ML strategy workflow.
My training and test sets overlap in time. How do I split them?
If the split isn't done in time order, the last training date can fall on or after the first test date, which leaks data. Split on fixed dates and check the order:
train_end = '2022-12-31'
test_start = '2023-01-01'
train_data = data_ml[data_ml.index.get_level_values(0) <= train_end]
test_data = data_ml[data_ml.index.get_level_values(0) >= test_start]
train_last_date = train_data.index.get_level_values(0).max()
test_first_date = test_data.index.get_level_values(0).min()
if train_last_date >= test_first_date:
raise ValueError(
f"Training and test set dates overlap: training ends {train_last_date}, "
f"test starts {test_first_date}. This causes data leakage."
)All predictions are NaN. Why?
Training failed without raising an error, or the prediction data contains NaN or Inf. Check the prediction data, and fill it before predicting again if needed:
import finlab.ml.qlib as q
train = train_data.dropna()
model = q.LGBModel()
model.fit(train.drop(columns=['label']), train['label'])
all_pred = model.predict(data_ml.drop(columns=['label']))
if all_pred.isna().all().all():
X_pred = data_ml.drop(columns=['label'])
print(f"NaN count: {X_pred.isna().sum().sum()}")
print(f"Inf count: {(X_pred == float('inf')).sum().sum()}")
# Fill missing values and predict again
X_pred = X_pred.fillna(0).replace([float('inf'), float('-inf')], 0)
all_pred = model.predict(X_pred)
print(f"Prediction complete: {all_pred.notna().sum().sum()} predictions")Setup and environment
How do I install TA-Lib?
Install it with pip: PyPI provides prebuilt wheels for Windows, macOS and Linux.
pip install ta-lib!pip install ta-libIn other kernels, install with conda:
conda install -c conda-forge ta-libFor other options, see the official installation guide.
How do I export or import data on Colab, and which path do I use?
Mount Google Drive first: click "Mount Drive" in the "Files" panel on the left, or run:
from google.colab import drive
drive.mount('/content/drive')After mounting, /drive/MyDrive (the full path is /content/drive/MyDrive) is the root of your Google Drive.
I get a MemoryError when running on the platform. What now?
The run exceeded the web memory limit. Move it to Google Colab.
Can I use the FinLab platform on my phone?
For viewing strategy performance, individual stocks and industry information, yes. For writing and running strategies, use a computer because of memory limits.
AI coding assistants
How do I write FinLab strategies with an AI assistant?
Install the official FinLab AI Skill: go to finlab.finance and follow the instructions to add it to an AI coding tool such as Claude Code, Cursor or Windsurf, and the AI learns the whole API. Then you can:
- Ask "Write me a stock selection strategy based on revenue growth and technical breakout" and the AI generates runnable code
- Ask "What financial statement datasets are available in
data.get?" and the AI lists the correct keys - Paste an error message and the AI helps diagnose and fix it
- Ask "Add a 10% stop loss to this strategy and run a backtest" and the AI edits the code directly
For more, see the Quickstart.
What is the difference between the AI Skill and FinLab Studio?
Studio runs in your browser with nothing to install; the AI Skill runs inside an AI coding tool on your own machine.
- FinLab Studio: an online AI platform. Write strategies by chatting in the browser.
- FinLab Skill: installed into local AI coding tools (Claude Code, Cursor and others). The AI can read and write files, run code and debug in your development environment. Suited to advanced users who already have a local setup.
The FinLab platform
How do I make a strategy update automatically every day?
On the platform's strategy page, click "Auto Update" at the top, choose the strategies to update and set a time.
Will I be notified when a strategy's signals change?
Yes. Strategies with auto-update enabled on the platform send an email when signals change, between 06:00 and 07:00 on business days.
What does the green dot next to a strategy name mean?
The strategy has a signal change or a rebalance on that day.
What's the difference between "Current Statistics" and "Recent Changes List"?
Current Statistics shows the strategy's actual current holdings. The Recent Changes List shows those holdings plus recent changes for individual stocks.
Why does the Recent Changes List change every day?
The data the strategy uses updates daily, so stocks that haven't been entered yet change with it. In practice, rebalance on the cycle you originally set.
Account, subscription and support
Can I change the credit card linked to my subscription?
Yes. Open your account settings in FinLab Studio and change the card in the payment settings.
I registered with the wrong account. How do I switch?
Sign in with the new Google account, then email finlab.company@finlab.tw from the old account's address to request the change.
Where do I ask questions about using FinLab?
Use the FinLab support page.
- Ask mainly about platform and package usage. For general programming syntax or problems unrelated to FinLab, try Google or ChatGPT first.
- Describe your situation fully: environment, package version and when it happened, with the complete code and error message. The more you include, the faster support can help.
- For strategy questions, describe the concept step by step for a more efficient answer.
What are the support hours, and how fast is the reply?
Support hours are Monday to Friday, 9:00 to 17:00. Requests are answered within two business days.
Where can I submit feature requests?
Through the FinLab support page.
Where can I find new research and tutorials?
Research articles and strategy studies are published on the FinLab blog. Package changes are listed in the Changelog.
Next steps
- Quickstart From installing to your first backtest in about five minutes.
- Download data Find dataset names and handle download errors.
- Backtesting Rebalance frequency, stops and fill timing.
- Orders Connect a broker, preview and send orders.
Was this page helpful?