Real-Trade Review
A backtest report shows theoretical positions; a real-trade review shows what you actually traded. TradeReview uses actual fill prices, fees, taxes, dividends and deposits/withdrawals to rebuild daily holdings, cash and NAV, computes time-weighted returns that exclude external cash flows, and produces the same FinLab report you get from a backtest.
Review straight from a broker account
import pandas as pd
from finlab.online.sinopac_account import SinopacAccount
from finlab.portfolio import TradeReview
acc = SinopacAccount()
review = TradeReview.from_account(acc, start="2026-01-01")
review.summary() # start/end NAV, net inflow, dividends, fees and tax, P&L, TWR
review.daily # daily cash, market value, NAV, flows, return, cumulative P&L
review.holdings # shares held at each close
review.trades # FIFO-matched round trips (with dividends); open lots have no exit_date
review.limitations # data limitations of this review: always read them
review.report.display() # FinLab report: equity curve, metrics, MAE/MFE, trades
from_account:
- Pulls fills from
startto today through the broker API (see the table below). - Rebuilds starting holdings from the broker's current holdings (undoing fills, stock dividends and splits in the window), then reconciles the rebuilt ending holdings with the broker; mismatches are listed in
review.limitations. - For Taiwan stocks, applies FinLab's dividend announcements automatically: cash dividends on the ex-dividend date, stock dividends and ETF splits on the ex-rights / resumption date.
- Values holdings daily at unadjusted close.
Two return modes
| Mode | When | NAV | External flows |
|---|---|---|---|
| Account | Broker API has complete deposits/withdrawals (Schwab, Binance), or you pass initial_cash and cash_events |
cash + market value | deposits, withdrawals |
| Holdings | Taiwan brokers (the settlement account is a bank account the API cannot see) | market value | buys are invested before the open; sell proceeds and dividends are withdrawn after the close |
Daily return r = (NAV + outflow) / (previous NAV + inflow) - 1, chained into the equity curve; CAGR, Sharpe and drawdown in the report are computed from it and are unaffected by deposits and withdrawals. In account mode, pnl is the NAV change minus net external inflow.
To get a full account NAV for a Taiwan account, pass starting cash and cash flows:
review = TradeReview.from_account(
acc,
start="2026-01-01",
initial_cash=500_000,
cash_events=pd.DataFrame(
{
"time": ["2026-03-02"],
"amount": [100_000],
"type": ["deposit"],
}
),
)
Compare with strategy targets
compare uses the actual position at the close of each target date, never later fills:
target = pd.DataFrame({"2330": [2000], "2454": [1000]}, index=["2026-03-02"])
review.compare(target) # date, stock_id, target, actual, diff (shares)
review.compare(weights, kind="weight") # or compare weights
Broker support and limitations
| Broker | Fill source | Fees / tax | Closed positions | Dividends | Deposits / withdrawals | Default mode |
|---|---|---|---|---|---|---|
| Sinopac | realized P&L + detail, open lot detail | buys from detail; sells derived from realized P&L (combined in fee) | buy legs recovered | FinLab auto | none | holdings |
| realized P&L, open lot detail | none | as Sinopac when the SDK has the detail call; otherwise shares sold in the window are treated as held at start | FinLab auto | none | holdings | |
| Fubon | filled_history (30 days per call, fubon_neo 2.1.1+) |
not in API; estimated from fee_ratio and statutory tax |
yes | FinLab auto | none | holdings |
| Masterlink | statement account_statment |
actual per fill | yes | FinLab auto | none | holdings |
| E.SUN (Fugle) | get_transactions_by_date (180 days per call) |
actual per fill | yes | FinLab auto | none | holdings |
| Schwab | get_transactions |
net amount minus gross | yes | broker records | broker records | account |
| Binance | myTrades (per symbol) |
paid in quote or base asset; BNB-paid fees are not counted | only currently held assets and those passed in symbols= |
quote-asset distributions | quote-asset deposits/withdrawals | account |
Common limitations:
- Only long cash and margin positions are reviewed; short-sale and securities-lending fills are skipped and listed in
limitations. Margin interest is not counted. - Most Taiwan fills only have a date; buys are processed before sells on the same day, which does not affect end-of-day valuation.
- Capital reductions and par value changes lack complete per-share refund and exchange ratio data and are not applied automatically; add them to
corporate_actions(columnsstock_id, date, cash, ratio). Stock dividends are converted as "NT$ per share ÷ par value", rounding fractional shares down. - When a broker API cannot return data that far back, the review can only start from the earliest available date.
Use your own fills
For other brokers or exported files, map them to the standard columns and build the review directly:
import pandas as pd
from finlab.markets.tw import TWMarket
from finlab.portfolio import TradeReview
from finlab.portfolio.trade_review import tw_corporate_actions
fills = pd.DataFrame(
{
"time": ["2026-01-05 09:01", "2026-01-07 10:00"],
"stock_id": ["1101", "1101"],
"action": ["buy", "sell"],
"quantity": [5000, 5000], # shares, not lots
"price": [35.2, 36.0],
"fee": [250, 256], # actual amount of this fill
"tax": [0, 540],
}
)
market = TWMarket()
review = TradeReview(
fills,
market.get_price("close", adj=False),
start="2026-01-05",
initial_holdings={"2330": 1000},
corporate_actions=tw_corporate_actions(
["1101", "2330"], "2026-01-05", "2026-12-31"
),
market=market,
)