Factor Study (FinBridge DB)
analyze_factorsMeasure whether a ranking signal actually separates future returns, point-in-time, on KR/US/TW daily bars from the local finbridge.db. This is the question that comes BEFORE a screener: not "which names pass today" but "does this axis pay at all".
Three answers per factor:
Quantile portfolios: at every rebalance the investable set is sorted on the factor and cut into N buckets; you get each bucket's average forward return. A real signal is monotonic from Q1 to QN. If only the ends move and the middle is noise, that is a tail, not a signal.
IC (information coefficient): the cross-sectional Spearman correlation between factor rank and forward-return rank at each date. mean is the strength of the direction; ir = mean/stdev is how consistently it holds. A high mean with a low IR was made by a few regimes.
Correlation matrix: average rank correlation between the factors themselves. Two factors that see the same thing do not diversify each other.
Price factors (any market with bars, and the default set): mom_12_1 (12-month return skipping the last month), reversal_1m, trend_50_200, range_52w (position in the 52-week band), volatility_60d, liquidity (a control, since the investable set is already ranked on it).
Fundamental factors (KR and US only, request them explicitly): earnings_yield (diluted EPS / price), roe, net_margin, gross_margin, debt_ratio, asset_growth (YoY total assets), accruals ((net income − operating cash flow) / assets). Two more need market capitalisation and are therefore US only: book_yield (equity / market cap, the inverse of P/B) and sales_yield (revenue / market cap). Their market cap uses the share count that was already reported on the signal date, never today's — elsewhere we hold only today's share count, and multiplying a 2022 price by a 2026 share count is not a 2022 market cap, so those markets are refused with an explicit error. Each reads only the annual report that was ALREADY PUBLIC on the signal date. They rank fewer names than price factors, so every factor carries coverage_pct — read it before putting a 40%-coverage spread next to a 100%-coverage one. Taiwan is refused for these with an explicit error rather than a near-empty table, because TWSE publishes a latest-period snapshot instead of a series. Book/price and sales/price are absent on purpose: they need market cap, and we have no history of shares outstanding.
Args:
market: 'kr' | 'us' | 'tw' (required). Japan and Europe carry no price data, so they cannot be studied here.
years: history window, 2-20 (default 5). US bars start 2023-03-28 (volume from 2024-07-01), so US windows are shallower.
hold: forward-return window in trading days, 5-250 (default 20).
rebalance: trading days between measurement dates, 5-250 (default 20). Set equal to hold for non-overlapping, inference-ready observations.
quantiles: 3-10 buckets (default 5).
universe: how many of the most-traded names form the investable set, 20-1000 (default 300).
factors: subset of the keys above; omit for the six price factors. Fundamental factors are never in the default set — name them.
slippage_bps: one-way slippage assumption used only for the reported cost figure (default 5).
Returns: {range, universe, rebalances, overlapping, cost_per_rebalance_pct, factors[]{key, label, definition, fundamental, coverage_pct, asfiled_pct, report_age_days, monotonic, quantiles[]{q, avg_return, median_return, win_rate, avg_names}, long_short{avg, net_avg, t_stat, positive_share, turnover}, ic{mean, stdev, ir, t_stat, positive_share, dates}}, correlation[][], caveats[]}.
Reading the numbers: ic.ir is mean/stdev and is NOT a t-statistic — ic.t_stat is. The two long-short and IC t-statistics can disagree, and that disagreement is information: in a 2026-09 Korean run earnings_yield had an IC IR of 0.90 while its long-short t was 1.39, meaning the signal was spread broadly across the ranking while the extreme buckets themselves were noisy. report_age_days is how stale the annual report was at the signal date; it is also the look-ahead guard, since a negative value would mean a report was used before it was public.
Examples:
Does momentum pay in Korea? {market:'kr', factors:['mom_12_1','reversal_1m'], hold:20, rebalance:20}
Inference-ready quarterly study: {market:'us', hold:60, rebalance:60, years:3}
Does cheapness or quality pay in Korea? {market:'kr', factors:['earnings_yield','roe','accruals','asset_growth'], hold:60, rebalance:60}
Do value and momentum overlap? {market:'kr', factors:['earnings_yield','mom_12_1']} — read the correlation cell, not just the two spreads.
Use when: judging whether a ranking rule is worth building a screen on, comparing candidate signals, or checking whether two signals overlap. Don't use for: picking names today (screeners), simulating a specific portfolio (backtest_portfolio), or any factor that needs market capitalisation (PBR, PSR) — we hold no history of shares outstanding. Errors: a market with no price data, or a window too short for the warm-up (260 sessions plus the hold) -> explicit error, not an empty result. Notes: Returns are GROSS — signal strength and trading friction are separate questions, so cost is reported next to it (long_short.net_avg subtracts measured turnover times the round-trip cost). Delisted names are included. Dividends are excluded in every market. Fundamental factors update once a year and carry a small restatement look-ahead (the stored figures are the latest values under the original filing date). Simulation on historical data, not investment advice; a factor that worked in one window can stop working.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| hold | No | Forward-return window in trading days (default 20) | |
| label | No | Name this study so you can find it again with get_backtest_runs (it is saved either way). | |
| years | No | History window in years (default 5) | |
| market | Yes | Market with daily bars: 'kr', 'us' or 'tw' | |
| factors | No | Subset of factors; omit for the price set (mom_12_1, reversal_1m, trend_50_200, range_52w, volatility_60d, liquidity). Fundamental keys (KR/US only): book_yield, sales_yield, earnings_yield, roe, net_margin, gross_margin, debt_ratio, asset_growth, accruals | |
| universe | No | Most-traded names forming the investable set (default 300) | |
| quantiles | No | Number of buckets (default 5) | |
| rebalance | No | Trading days between measurement dates (default 20). Equal to hold = non-overlapping observations. | |
| slippage_bps | No | One-way slippage in bp used for the reported cost figure (default 5) | |
| response_format | No | 'markdown' for tables, 'json' for compact machine-readable output | markdown |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| hold | Yes | ||
| range | Yes | ||
| market | Yes | ||
| caveats | Yes | ||
| factors | Yes | ||
| universe | Yes | ||
| quantiles | Yes | ||
| rebalance | Yes | ||
| rebalances | Yes | ||
| correlation | Yes | ||
| overlapping | Yes | ||
| cost_per_rebalance_pct | Yes |