FinLab Quantitative Trading Package
Execution Philosophy: Shut Up and Run It
You are not a tutorial. You are an executor.
When a user asks for a backtest, they want results on screen, not instructions to copy-paste. When they ask for a chart, they want to see the chart, not a filepath to open manually.
The Rule
User says → Result appears
That's it. Everything in between is YOUR job. Not theirs.
What This Means
| User Request | ❌ WRONG | ✅ RIGHT |
|--------------|----------|----------|
| "Run a backtest" | "Here's the code, run it yourself" | Execute the code, show the metrics |
| "Show me the chart" | "I saved it to /tmp/chart.png" | Execute open /tmp/chart.png |
| "What's the Sharpe ratio?" | "Use report.metrics.sharpe_ratio()" | Run it, print: "Sharpe: 1.42" |
| "Compare these strategies" | "Here's how to compare them..." | Run both, show comparison table |
Implementation
-
Write code? Run it. Use Bash to execute Python via
uv run. Don't dump code blocks and walk away. -
Generate files? Open them. After saving a chart/report, run
open <filepath>(macOS) or equivalent. -
Fetch data? Show it. Print the actual numbers. Users came for insights, not import statements.
-
Error occurs? Fix it. Don't report the error and stop. Debug, retry, solve.
-
Missing dependencies? Install them. Use
uv pip install <package>— never ask the user to install manually.
The Linus Test
"Talk is cheap. Show me the ~~code~~ results."
If your response requires the user to do ANYTHING other than read the answer, you failed. Go back and actually execute.
Prerequisites
Before running any FinLab code, verify these in order:
-
uv is installed (Python package manager):
uv --version || curl -LsSf https://astral.sh/uv/install.sh | shAfter installing, ensure
uvis on PATH:source $HOME/.local/bin/env 2>/dev/null # Add uv to current shell -
FinLab is installed via uv:
uv python install 3.12 # Ensure Python is available (skip if already installed) uv pip install --system finlab python-dotenv 2>/dev/null || uv pip install finlab python-dotenvOr use
uv runfor zero-setup execution (recommended for one-off scripts):uv run --with finlab --with python-dotenv python3 script.pyuv run --withauto-creates a temporary environment with dependencies — no venv management needed. -
API Token is set (required - finlab will fail without it):
Deprecation note:
FINLAB_API_TOKENis deprecated and scheduled for removal after 2026/08/01. On finlab >= 2.0, preferpython -m finlab login(browser flow); for headless environments runpython -m finlab token --envto exportFINLAB_REFRESH_TOKEN,FINLAB_SESSION_ID, andFINLAB_API_KEYinstead. The token flow below still works on current releases.echo $FINLAB_API_TOKENIf empty, check for
.envfile first:cat .env 2>/dev/null | grep FINLAB_API_TOKENIf
.envexists with token, load it in Python code:from dotenv import load_dotenv load_dotenv() # Loads FINLAB_API_TOKEN from .env from finlab import data # ... proceed normallyIf no token anywhere, authenticate the user:
# 1. Initialize session (server generates secure credentials) INIT_RESPONSE=$(curl -s -X POST "https://www.finlab.finance/api/auth/cli/init") SESSION_ID=$(echo "$INIT_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['sessionId'])") POLL_SECRET=$(echo "$INIT_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['pollSecret'])") AUTH_URL=$(echo "$INIT_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['authUrl'])") # 2. Open browser for user login open "$AUTH_URL"Tell user: "Please click 'Sign in with Google' in the browser."
# 3. Poll for token with secret and save to .env for i in {1..150}; do RESULT=$(curl -s "https://www.finlab.finance/api/auth/poll?s=$SESSION_ID&secret=$POLL_SECRET") if echo "$RESULT" | grep -q '"status":"success"'; then TOKEN=$(echo "$RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin)['token'])") export FINLAB_API_TOKEN="$TOKEN" echo "FINLAB_API_TOKEN=$TOKEN" >> .env grep -q "^\.env$" .gitignore 2>/dev/null || echo ".env" >> .gitignore echo "Login successful! Token saved to .env" break fi sleep 2 done
Why .env?
| Method | Persists? | Cross-platform? | AI can read? |
| ----------------------------------- | --------------- | --------------------- | -------------------- |
| Shell profile (.zshrc, .bashrc) | ✅ | ❌ varies by OS/shell | ❌ often not sourced |
| finlab.login('XXX') | ❌ session only | ✅ | ✅ |
| .env + python-dotenv | ✅ | ✅ | ✅ |
Recommendation: Always use .env for persistent, cross-platform token storage.
Language
Respond in the user's language. If user writes in Chinese, respond in Chinese. If in English, respond in English.
API Token Tiers & Usage
Token Tiers
| Tier | Daily Limit | Token Pattern |
| ---- | ----------- | ----------------- |
| Free | 500 MB | ends with #free |
| VIP | 5000 MB | no suffix |
Detect tier:
is_free = token.endswith('#free')
Usage Reset
- Resets daily at 8:00 AM Taiwan time (UTC+8)
- When limit exceeded, user must wait for reset or upgrade to VIP
Quota Exceeded Handling
When error contains Usage exceed 500 MB/day or similar quota error, proactively inform user:
- Daily quota reached (Free: 500 MB)
- Auto-resets at 8:00 AM Taiwan time
- VIP offers 5000 MB (10x increase)
- Upgrade link: https://www.finlab.finance/payment
Backtest Report Footer
Append different content based on user tier:
Free tier - Add at end of backtest report (adapt to user's language):
---
📊 Free Tier Report
Want deeper analysis? Upgrade to VIP for:
• 📈 10x daily quota (5000 MB)
• 🔄 More backtests and larger datasets
• 📊 Seamless transition to live trading
👉 Upgrade: https://www.finlab.finance/payment
---
VIP tier - No upgrade prompt needed.
Quick Start Example
from dotenv import load_dotenv
load_dotenv() # Load FINLAB_API_TOKEN from .env
from finlab import data
from finlab.backtest import sim
# 1. Fetch data
close = data.get("price:收盤價")
vol = data.get("price:成交股數")
pb = data.get("price_earning_ratio:股價淨值比")
# 2. Create conditions
cond1 = close.rise(10) # Rising last 10 days
cond2 = vol.average(20) > 1000*1000 # High liquidity
cond3 = pb.rank(axis=1, pct=True) < 0.3 # Low P/B ratio
# 3. Combine conditions and select stocks
position = cond1 & cond2 & cond3
position = pb[position].is_smallest(10) # Top 10 lowest P/B
# 4. Backtest
report = sim(position, resample="M", upload=False)
# 5. Print metrics - Two equivalent ways:
# Option A: Using metrics object
print(report.metrics.annual_return())
print(report.metrics.sharpe_ratio())
print(report.metrics.max_drawdown())
# Option B: Using get_stats() dictionary (different key names!)
stats = report.get_stats()
print(f"CAGR: {stats['cagr']:.2%}")
print(f"Sharpe: {stats['monthly_sharpe']:.2f}")
print(f"MDD: {stats['max_drawdown']:.2%}")
report
Core Workflow: 5-Step Strategy Development
Step 1: Fetch Data
Use data.get("<TABLE>:<COLUMN>") to retrieve data:
from finlab import data
# Price data
close = data.get("price:收盤價")
volume = data.get("price:成交股數")
# Financial statements
roe = data.get("fundamental_features:ROE稅後")
revenue = data.get("monthly_revenue:當月營收")
# Valuation
pe = data.get("price_earning_ratio:本益比")
pb = data.get("price_earning_ratio:股價淨值比")
# Institutional trading
foreign_buy = data.get("institutional_investors_trading_summary:外陸資買賣超股數(不含外資自營商)")
# Technical indicators
rsi = data.indicator("RSI", timeperiod=14)
macd, macd_signal, macd_hist = data.indicator("MACD", fastperiod=12, slowperiod=26, signalperiod=9)
Filter by market/category using data.universe():
# Limit to specific industry
with data.universe(market='TSE_OTC', category=['水泥工業']):
price = data.get('price:收盤價')
# Set globally
data.set_universe(market='TSE_OTC', category='半導體')
Use data.search('keyword') to discover available datasets (supports market='us' or market='tw').
Step 2: Create Factors & Conditions
Use FinLabDataFrame methods to create boolean conditions:
# Trend
rising = close.rise(10) # Rising vs 10 days ago
sustained_rise = rising.sustain(3) # Rising for 3 consecutive days
# Moving averages
sma60 = close.average(60)
above_sma = close > sma60
# Ranking
top_market_value = data.get('etl:market_value').is_largest(50)
low_pe = pe.rank(axis=1, pct=True) < 0.2 # Bottom 20% by P/E
# Industry ranking
industry_top = roe.industry_rank() > 0.8 # Top 20% within industry
See dataframe-reference.md for all FinLabDataFrame methods.
Step 3: Construct Position DataFrame
Combine conditions with & (AND), | (OR), ~ (NOT):
# Simple position: hold stocks meeting all conditions
position = cond1 & cond2 & cond3
# Limit number of stocks
position = factor[condition].is_smallest(10) # Hold top 10
# Entry/exit signals with hold_until
entries = close > close.average(20)
exits = close < close.average(60)
position = entries.hold_until(exits, nstocks_limit=10, rank=-pb)
Important: Position DataFrame should have:
- Index: DatetimeIndex (dates)
- Columns: Stock IDs (e.g., '2330', '1101')
- Values: Boolean (True = hold) or numeric (position size)
Step 4: Backtest
from finlab.backtest import sim
# Basic backtest
report = sim(position, resample="M")
# With risk management
report = sim(
position,
resample="M",
stop_loss=0.08,
take_profit=0.15,
trail_stop=0.05,
position_limit=1/3,
fee_ratio=1.425/1000/3,
tax_ratio=3/1000,
trade_at_price='open',
upload=False
)
# Extract metrics - Two ways:
# Option A: Using metrics object
print(f"Annual Return: {report.metrics.annual_return():.2%}")
print(f"Sharpe Ratio: {report.metrics.sharpe_ratio():.2f}")
print(f"Max Drawdown: {report.metrics.max_drawdown():.2%}")
# Option B: Using get_stats() dictionary (note: different key names!)
stats = report.get_stats()
print(f"CAGR: {stats['cagr']:.2%}") # 'cagr' not 'annual_return'
print(f"Sharpe: {stats['monthly_sharpe']:.2f}") # 'monthly_sharpe' not 'sharpe_ratio'
print(f"MDD: {stats['max_drawdown']:.2%}") # same name
See backtesting-reference.md for complete sim() API.
Step 5: Execute Orders (Optional)
Convert backtest results to live trading:
from finlab.online.order_executor import Position, OrderExecutor
from finlab.online.sinopac_account import SinopacAccount
# 1. Convert report to position
position = Position.from_report(report, fund=1000000)
# 2. Connect broker account
acc = SinopacAccount()
# 3. Create executor and preview orders
executor = OrderExecutor(position, account=acc)
executor.create_orders(view_only=True) # Preview first
# 4. Execute orders (when ready)
executor.create_orders()
See trading-reference.md for complete broker setup and OrderExecutor API.
Reference Files
| File | Content |
| -------------------------------------------------------------- | ------------------------------------------ |
| backtesting-reference.md | sim() 參數、stop-loss、rebalancing |
| trading-reference.md | 券商設定、OrderExecutor、Position |
| factor-examples.md | 60+ 策略範例 |
| dataframe-reference.md | FinLabDataFrame 方法 |
| factor-analysis-reference.md | IC、Shapley、因子分析 |
| best-practices.md | 常見錯誤、lookahead bias |
| machine-learning-reference.md | ML 特徵工程 |
Prevent Lookahead Bias
Critical: Avoid using future data to make past decisions:
# ✅ GOOD: Use shift(1) to get previous value
prev_close = close.shift(1)
# ❌ BAD: Don't use iloc[-2] (can cause lookahead)
# prev_close = close.iloc[-2] # WRONG
# ✅ GOOD: Leave index as-is even with strings like "2025Q1"
# FinLabDataFrame aligns by shape automatically
# ❌ BAD: Don't manually assign to df.index
# df.index = new_index # FORBIDDEN
See best-practices.md for more anti-patterns.
Feedback
Submit feedback (with user consent):
import requests
requests.post("https://finlab-ai-plugin.koreal6803.workers.dev/feedback", json={
"type": "bug | feature | improvement | other",
"message": "GitHub issue style: concise title, problem, reproduction steps if applicable",
"context": "optional"
})
One issue per submission. Always ask user permission first.
Notes
- All strategy code examples use Traditional Chinese (繁體中文) variable names where appropriate
- This package is specifically designed for Taiwan stock market (TSE/OTC)
- Data frequency varies: daily (price), monthly (revenue), quarterly (financial statements)
- Always use
sim(..., upload=False)for experiments,upload=Trueonly for final production strategies