Metadata-Version: 2.4
Name: finlab
Version: 2.1.0
Summary: Analyzing stock has never been easier.
Author-email: FinLab <finlab.company@finlab.tw>
License-Expression: GPL-3.0-or-later
Project-URL: Homepage, https://finlab.finance
Project-URL: Documentation, https://finlab.finance/docs/
Project-URL: 文件教學, https://finlab.finance/docs/getting-started/
Project-URL: 選股策略, https://studio.finlab.finance/strategies
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Operating System :: OS Independent
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests
Requires-Dist: croniter>=1.3.8
Requires-Dist: numpy>=1.21.6
Requires-Dist: pandas>=2.1.0
Requires-Dist: pyarrow>=7.0.0
Requires-Dist: lz4
Requires-Dist: tqdm
Requires-Dist: jinja2
Requires-Dist: ipython
Requires-Dist: scipy
Requires-Dist: cryptography>=43.0.0
Provides-Extra: cli
Requires-Dist: pywebview; extra == "cli"
Requires-Dist: google-auth-oauthlib; extra == "cli"
Requires-Dist: flask; extra == "cli"
Provides-Extra: qlib
Requires-Dist: pyyaml; extra == "qlib"
Requires-Dist: ruamel.yaml; extra == "qlib"
Requires-Dist: joblib; extra == "qlib"
Requires-Dist: redis; extra == "qlib"
Requires-Dist: python-redis-lock; extra == "qlib"
Requires-Dist: scipy; extra == "qlib"
Requires-Dist: dill; extra == "qlib"
Requires-Dist: matplotlib; extra == "qlib"
Requires-Dist: lightgbm; extra == "qlib"
Requires-Dist: xgboost; extra == "qlib"
Requires-Dist: catboost; extra == "qlib"
Requires-Dist: scikit-learn; extra == "qlib"
Requires-Dist: torch; (sys_platform != "darwin" or platform_machine != "x86_64") and extra == "qlib"
Requires-Dist: ta-lib; extra == "qlib"
Requires-Dist: plotly; extra == "qlib"
Provides-Extra: terminal
Requires-Dist: asciichartpy>=1.5.25; extra == "terminal"
Provides-Extra: brokers
Requires-Dist: shioaji; extra == "brokers"
Dynamic: license-file

# FinLab

Backtesting and research toolkit for Taiwan equities, with data and market support for the US, Japan, Korea, and Hong Kong.

[![PyPI](https://img.shields.io/pypi/v/finlab)](https://pypi.org/project/finlab/)
[![Python](https://img.shields.io/pypi/pyversions/finlab)](https://pypi.org/project/finlab/)
[![License](https://img.shields.io/pypi/l/finlab)](https://www.gnu.org/licenses/gpl-3.0.html)

FinLab gives you market, fundamental, and alternative data through one function, a pandas-based `FinLabDataFrame` that aligns daily, monthly, and quarterly data automatically, and a Cython backtest engine that produces an interactive report.

## Features

- **Data access**: `data.get()` loads prices, financial statements, monthly revenue, institutional flows, and more as a date × symbol table. Browse the catalog at [studio.finlab.finance/chat/database](https://studio.finlab.finance/chat/database).
- **Frequency alignment**: quarterly and monthly data are aligned to each company's disclosure date when combined with daily data, so a report is not used before it was published.
- **Backtesting**: `sim()` runs portfolio backtests with rebalancing schedules, transaction costs, stop-loss and take-profit rules, and position limits.
- **Reports**: an interactive report with the equity curve, drawdowns, yearly and monthly returns, trades, and risk metrics.
- **Charts**: candlesticks with indicators, market treemaps, and fundamental radar charts built on Plotly.
- **Markets**: Taiwan stocks and convertible bonds, plus US, Japan, Korea, Hong Kong, and UK equities and funds.
- **Live trading**: turn a backtest into orders through supported Taiwan and US brokers.

## Installation

```bash
pip install finlab
```

FinLab supports Python 3.10 to 3.14. Log in once to store your credentials:

```bash
python -m finlab login
```

In Google Colab, call `finlab.login()` instead. For servers and CI, see [authentication](https://finlab.finance/docs/en/getting-started/).

## Quickstart

Select the 20 companies with the strongest recent revenue growth, keep those trading above their 60-day average, and rebalance monthly:

```python
from finlab import data
from finlab.backtest import sim

close = data.get('price:收盤價')  # daily close
revenue = data.get('monthly_revenue:當月營收')  # monthly revenue

growth = revenue.average(3) / revenue.average(12)
position = growth.is_largest(20) & (close > close.average(60))

report = sim(position, resample='M')
report.display()
```

Monthly revenue is aligned to its reporting deadline and combined with daily prices automatically. `report.display()` opens the interactive report in Jupyter; use `report.to_html('report.html')` elsewhere.

<img src="https://finlab.finance/docs/assets/readme/report.png" width="800" alt="Backtest report with annual return, equity curve against the benchmark, drawdown, and yearly returns">

*Illustrative in-sample backtest. Past results do not indicate future returns.*

## The report

The report opens on the performance view. Five scorecards (profit, risk, risk-adjusted return, win rate, liquidity) each check the strategy against a threshold and open their own charts:

| | |
|:---:|:---:|
| <img src="https://finlab.finance/docs/assets/readme/report_performance.png" width="400" alt="Performance: equity curve, drawdown, and returns by year"><br>Performance | <img src="https://finlab.finance/docs/assets/readme/report_trades.png" width="400" alt="Trades: every position with entry, exit, return, MAE and GMFE"><br>Trades |
| <img src="https://finlab.finance/docs/assets/readme/report_yearly.png" width="400" alt="Yearly comparison of strategy and benchmark returns"><br>Strategy vs. benchmark by year | <img src="https://finlab.finance/docs/assets/readme/report_risk.png" width="400" alt="Risk: drawdown history, worst drawdowns, and recovery times"><br>Risk |
| <img src="https://finlab.finance/docs/assets/readme/report_ratios.png" width="400" alt="Risk-adjusted return: rolling Sharpe ratio against the benchmark"><br>Risk-adjusted return | <img src="https://finlab.finance/docs/assets/readme/report_winrate.png" width="400" alt="Win rate: distribution of trade returns"><br>Win rate |
| <img src="https://finlab.finance/docs/assets/readme/report_liquidity.png" width="400" alt="Liquidity: share of tradable positions by capital size"><br>Liquidity and capacity | <img src="https://finlab.finance/docs/assets/readme/report_holdings.png" width="400" alt="Current holdings with unrealized return and weights"><br>Current holdings |

Search any stock from the report (⌘K) to open its chart, fundamentals, revenue, institutional flows, and margin data beside the results:

<img src="https://finlab.finance/docs/assets/readme/report_stock_panel.png" width="800" alt="Report with the 2330 stock panel open: candlestick chart, valuation, revenue, institutional flows, and margin trading">

## Working with data

Each `data.get()` call returns a `FinLabDataFrame`: dates as the index, symbols as the columns. Tables of different frequencies combine directly, and FinLab aligns quarterly and monthly values to their publication dates first.

```python
from finlab import data

close = data.get('price:收盤價')  # daily
revenue = data.get('monthly_revenue:當月營收')  # monthly
roe = data.get('fundamental_features:ROE稅後')  # quarterly

uptrend = close > close.average(60)
growing = (revenue.average(3) / revenue.average(12)).rise(3)
quality = roe.rank(axis=1, pct=True) > 0.7

position = uptrend & growing & quality & revenue.average(3).is_largest(100)

print(position.sum(axis=1).tail())  # stocks selected on each of the last five days
```

Output:

```text
date
2026-09-17    17
2026-09-18    20
2026-09-21    22
2026-09-22    22
2026-09-23    22
dtype: int64
```

Frequently used methods include `average`, `rise`, `fall`, `sustain`, `is_largest`, `is_smallest`, `industry_rank`, and `hold_until`. See the [FinLabDataFrame reference](https://finlab.finance/docs/en/reference/dataframe/).

## Backtest options and results

`sim()` accepts rebalancing frequency, execution price, position limits, exits, and trading costs:

```python
from finlab.backtest import sim

report = sim(
    position,
    resample='W',  # rebalance weekly
    trade_at_price='open',  # execute at the next open
    position_limit=0.1,  # at most 10% per stock
    stop_loss=0.08,
    take_profit=0.3,
    fee_ratio=1.425 / 1000,
    tax_ratio=3 / 1000,
)

stats = report.get_stats()  # CAGR, max drawdown, Sharpe, ...
print({k: round(stats[k], 3) for k in ['cagr', 'max_drawdown', 'daily_sharpe']})

trades = report.get_trades()  # one row per trade
print(trades[['stock_id', 'entry_date', 'exit_date', 'return']].dropna().tail(3))

print(report.next_weights.head(3))  # target weights for the next rebalance
```

Output (illustrative in-sample backtest):

```text
{'cagr': 0.148, 'max_drawdown': -0.255, 'daily_sharpe': 0.92}
            stock_id entry_date  exit_date    return
trade_index                                         
7980            3231 2026-09-14 2026-09-21  0.028918
7981            5434 2026-09-14 2026-09-21  0.024809
7982            6505 2026-09-14 2026-09-21  0.046099
symbol
2301 光寶科    0.045455
2303 聯電     0.045455
2308 台達電    0.045455
Name: 2026-09-27 00:00:00, dtype: float64
```

## From backtest to orders

Convert the latest target weights into share quantities and place orders through a broker account:

```python
from finlab.online.order_executor import Position, OrderExecutor
from finlab.online.sinopac_account import SinopacAccount

position = Position.from_report(
    report, 1_000_000, odd_lot=True
)  # TWD 1M, odd lots allowed
executor = OrderExecutor(position, account=SinopacAccount())
print(position)  # target lots per stock
executor.create_orders(view_only=True)  # preview before sending
```

Output, for an account with no current holdings (quantities in lots; 1 lot = 1,000 shares):

```text
symbol stock_id quantity order_condition
  2301     2301    0.175            CASH
  2303     2303    0.312            CASH
  2330     2330     0.02            CASH
  2357     2357    0.052            CASH
  2377     2377    0.326            CASH
   ... (15 more stocks)
BUY         2615       X 0.429      @ 113.5        CASH
BUY         2330       X 0.02       @ 2500.0       CASH
BUY         3034       X 0.09       @ 545.0        CASH
BUY         3037       X 0.045      @ 1160.0       CASH
BUY         3044       X 0.092      @ 543.0        CASH
... (15 more orders)
```

Supported brokers: SinoPac, Fubon, Masterlink, E.SUN (Fugle), and Pocket for Taiwan; Charles Schwab for the US. Each broker requires its own SDK and credentials; see the [order API guide](https://finlab.finance/docs/en/details/order_api/).

## Charts

Candlesticks with Bollinger Bands, volume, and KD for any Taiwan stock:

```python
from finlab.plot import plot_tw_stock_candles

plot_tw_stock_candles('2330', recent_days=250)
```

<img src="https://finlab.finance/docs/assets/readme/candles.png" width="800" alt="Candlestick chart of 2330 with Bollinger Bands, volume, and KD">

A market treemap sized by market value and colored by return over a period:

```python
from finlab.plot import plot_tw_stock_treemap

plot_tw_stock_treemap(
    start='2026-08-24', end='2026-09-22', area_ind='market_value', item='return_ratio'
)
```

<img src="https://finlab.finance/docs/assets/readme/treemap.png" width="800" alt="Treemap of Taiwan stocks grouped by market and industry, sized by market value and colored by return">

Fundamental decile ranks for a group of stocks:

```python
from finlab.plot import plot_tw_stock_radar

plot_tw_stock_radar(portfolio=['2330', '2454', '2317'])
```

<img src="https://finlab.finance/docs/assets/readme/radar.png" width="640" alt="Radar chart comparing fundamental decile ranks of 2330, 2454, and 2317">

## Other markets

Switch the data source with `data.set_market()` and pass the matching market to `sim()`:

```python
from finlab import data
from finlab.backtest import sim
from finlab.markets.us import USMarket

data.set_market('us')
close = data.get('price:adj_close')

position = close == close.rolling(200).max()  # 200-day high
report = sim(
    position,
    resample='2W',
    position_limit=0.2,
    stop_loss=0.2,
    market=USMarket(),
    fee_ratio=0.001,
    tax_ratio=0,
)

print(close.shape)  # (dates, symbols)
print(position.iloc[-1].sum(), 'stocks at a 200-day high on', position.index[-1].date())
```

Output:

```text
(2696, 10460)
95 stocks at a 200-day high on 2026-09-22
```

Market classes are available for Taiwan (`TWMarket`), US (`USMarket`), Japan (`JPMarket`), Korea (`KRMarket`), Hong Kong (`HKMarket`), and the UK (`UKMarket`), with fund variants for ETFs.

## Documentation

- [Getting started](https://finlab.finance/docs/en/getting-started/)
- API reference: [data](https://finlab.finance/docs/en/reference/data/), [FinLabDataFrame](https://finlab.finance/docs/en/reference/dataframe/), [backtest](https://finlab.finance/docs/en/reference/backtest/), [plot](https://finlab.finance/docs/en/reference/plot/)
- [FAQ](https://finlab.finance/docs/en/faq/)
- [Changelog](https://finlab.finance/docs/en/change-log/)
- 中文文件：[finlab.finance/docs](https://finlab.finance/docs/)

## Support

Ask questions and report problems in the [FinLab Discord](https://discord.gg/tAr4ysPqvR).

## License

GPL-3.0-or-later. See [LICENSE](https://www.gnu.org/licenses/gpl-3.0.html).

## Disclaimer

FinLab is software for research and education. Nothing in this package or its documentation is investment advice. Backtest results depend on data quality and modeling assumptions and do not predict future performance. You are responsible for your own trading decisions.
