Changelog
2.2.13 (2026-10-16)
Fixed
FinlabDataFrame.corrwith(axis=1)no longer returns incorrect zeros for nonconstant cross-sectional inputs or self-correlation. Missing values, constants, mixed DataFrame inputs and ordinary FinLab date-aware arithmetic retain their expected behavior. Upgrade and rerun affected factor correlation calculations. (Issue #436, PR #437)
2.2.12 (2026-10-16)
Fixed
-
Report snapshots, HTML export and uploads reuse the data readiness saved by the backtest, avoiding repeated metadata requests and full-table hashing. Historical backtests without pending sources no longer show a suggested trading time. Performance and holdings are unchanged. (Issue #431, PR #432)
-
HTML holdings reports show the execution date beside the entry price and list the signal date separately. Expired rebalance dates are hidden when no rebalance is pending, while valid legacy and other-market schedules remain visible. The analysis layout is also updated; backtest calculations are unchanged. (PR #430)
-
sim()now rejects positions with no rows or stock columns before backtesting and explains how to align stock columns or use a date Series for a market filter. Explicit all-zero cash positions remain supported. (Issue #423, PR #424) -
groupby_category().groups,indicesandngroupsretain pandas grouping metadata. Unequal industry sizes no longer fail, and equal sizes no longer convert group mappings into a DataFrame. Aggregation and disclosure-date alignment are unchanged. (Issue #420, PR #421) -
Korean, Japanese, Hong Kong and UK stock and fund backtests now use the existing adjusted-price tables, fixing missing-dataset errors. Fees, taxes and historical data are unchanged. (Issue #418, PR #419)
-
Backtests starting at a non-trading monthly, weekly or custom rebalance date now retain the first holding period that could previously be skipped. Affected trades and performance may change; later rebalances and next-session execution rules are unchanged. (Issue #416, PR #417)
-
data.get()stops waiting sooner when authorization, downloads or version checks accept a connection but do not respond, retaining the existing retry policy. Download timeouts limit inactivity per read, allowing continuously streaming large files to finish. Data and cache rules are unchanged. (Issue #414, PR #415) -
Ordinary and date-range
data.get(..., save_to_storage=False)calls now fail before fetching and explain how to selectdata.CacheStorage()and omit the old argument, instead of warning and still writing to disk. Intraday and retained-version reads continue to support the argument. (Issue #411, PR #412) -
Cloud readiness checks reuse verified publication hashes supplied by read-only storage instead of rehashing entire DataFrames. Mismatches remain unconfirmed, while writable caches and legacy data retain their existing verification. (Issue #433, PR #434)
2.2.11 (2026-10-13)
Changed
-
The backtest report dashboard now uses the updated Studio analysis and holdings pages, including the performance timeline, annual bands, and plain-language strategy checks. Backtest calculations and data values are unchanged.
-
Quarterly boolean conditions now use dense arrays for alignment with daily data,
&/|/^, and boolean-mask filtering, reducing per-column overhead for full-market US stock selection. Results, unknown values before disclosure, and public output dtypes are unchanged. (Issue #404, PR #405)
Fixed
-
In read-only environments,
python -m finlab aianddata.get()now continue using the refreshed in-memory credentials when token renewal succeeds but the disk cache cannot be written. Calls in the same process reuse the new credentials; a new process refreshes them again. Initial login and environment-variable login are unchanged. (Issue #408, PR #409) -
On Python 3.10,
finlab[qlib]uses a TA-Lib version that still provides compatible wheels, avoiding source builds that fail without the system library after newer TA-Lib releases dropped Python 3.10 wheels. Dependency selection on Python 3.11 and later is unchanged.
Documentation
- The
industry_rank()documentation now identifies Taiwan industry classifications as the TPEx industry value-chain platform, explains the default broad categories and averaging across categories, and clarifies that the current classification snapshot applies to all dates rather than historical point-in-time classifications.rank()examples explicitly rank across columns. Function and data behavior are unchanged. (Issue #406, PR #407)
2.2.10 (2026-10-10)
Added
- When
data.get()downloads a new version rewritten outside scheduled updates, it shows the dataset, correction time, support-record link and anas_of=example. Reading again or requesting a date range after the cache update does not repeat the notice. It uses the existing revision index without adding a network request to every call; manually appended dates can also trigger it. This feature does not modify data values; historical retention and access restrictions still apply. (Issue #400, PR #402)
Fixed
-
finlab.ml.feature.combine()now appliesresampleto MultiIndex features, using the existing resampling dates both on their own and when mixed with wide features. Weekly/monthly settings are no longer ignored and feature columns are no longer emptied by date mismatch. StringWretains Sunday dates; pass explicit date anchors for the last trading day of each week and use the sameresamplefor labels. Withoutresample, labelperiodstill means trading days. (Issue #397, PR #399)Behavior change Results may change
2.2.9 (2026-10-10)
Changed
sim()no longer emitsDelistedHoldingWarningfor delisted holdings, and thefinlab.backtest.delisting.DelistedHoldingWarningclass is removed. How delisted holdings are exited and kept out of later rebalances is unchanged. Remove any code that imports the class or filters warnings by it. (Issue #390, PR #395)
Added
python -m finlab cloud deployandcloud schedule setadd--frequency(daily,weekly,monthly,custom),--days-of-week,--day-of-monthand--cron, so cloud schedules can run weekly, monthly or on a custom cron. Times are always Asia/Taipei; dates missing from a month are skipped, and market holidays are not skipped automatically. Existing commands behave as before, andcloud schedule list/getshow the saved frequency. (PR #386)
Fixed
-
In markets that define no prefetch datasets, such as
USMarketandUSFundMarket,sim()skipped the data version check when reading prices, index and company data, so after a historical data correction it kept using the old local cache and could mix it with the newer data you read throughdata.get(). Backtests in every market now check data versions the same waydata.get()does; within onesim()/sim_batch()run, a cache already confirmed is reused without another check or download. Taiwan-market backtests, and results when no data was corrected, are unchanged. (Issue #391, PR #392) -
When the sub-strategies of
finlab.portfolio.Portfoliostart or rebalance on different dates,positionzeroed every date on which only some sub-strategies had positions. Each sub-strategy's positions are now aligned to all rebalance dates, carrying its latest target forward and holding cash before it starts, while a cleared holding is not carried forward;creturnstill covers only the common period. (Issue #384, PR #385)Behavior change Results may change
-
On Apple Silicon Macs,
DNNModel,TabnetModelandSFMModelinfinlab.ml.qlibcrashed during training or construction because sklearn and PyTorch each load their own OpenMP runtime, and importing torch first madeLGBModelcrash instead. macOS arm64 now trains with one thread by default, so all eight models train and predict, more slowly; other platforms are unchanged.get_models()now includesSFMModeland skips only a model whose construction fails; installed PyTorch is no longer reported as missing; andTabnetModelvalidation scores include the final partial batch weighted by sample count, so a validation set smaller than one batch no longer fails. (Issue #387, PR #388)Behavior change Results may change
2.2.8 (2026-10-09)
Fixed
verify_strategy()now raises aTypeErrornaming the returned type and showing how to return asim(..., upload=False)Report when a strategy returns a position DataFrame or another non-Report value, instead of a missing-tradeserror. Valid Report-returning strategies and their verification results are unchanged. (Issue #381, PR #382)
2.2.7 (2026-10-09)
Changed
-
Data downloaded by free accounts is now kept in a
freesubfolder of the data cache folder, shared by Python programs and Jupyter kernels on the same computer, instead of being downloaded again by every run. When the cloud data has not changed, the local cache is read directly, with no download and no daily download quota used; a newer cloud file is downloaded once. Paid accounts never read thefreesubfolder, and when the folder cannot be written, data stays in memory as before. Data contents are unchanged. (PR #376) -
When a large full table (a dataset name without a colon) is republished and the cloud provides a change file for it,
data.get()downloads only the changed dates and merges them into the local cache. The merge is used only when it matches the published table's hash; otherwise the whole table is downloaded as before. Without a change file, or with a free account, behavior is unchanged. Data contents are unchanged. (PR #378) -
data.indicator()always calculates with market prices, so custom price inputs such ashigh=,low=,close=,open=,volume=ordata=still raise an error; the message now explains this and shows how to calculate one stock at a time withtalib.abstract. The message for misspelled parameter names is unchanged. (Issue #350, PR #356)
Added
-
The
stop_lossandtake_profitparameters ofsim()andhold_until()accept, besides numbers, a DataFrame indexed by date with stocks as columns, so each stock can use a different stop threshold each day, such as an ATR-multiple stop. Each bar uses the latest threshold dated on or before it, and intradaytouched_exit=Trueexits use only thresholds dated up to the previous bar, never the same day's value; each cell behaves like the same number, and NaN, dates before the first row, and stocks missing from the DataFrame have no stop that day. With a DataFrame,report.stop_loss/report.take_profitareNone, and stop exits are still recorded inreport.actions.trail_stopandtrail_stop_activationstill accept numbers only; passing a Series, array or other unsupported type to a stop parameter raises aTypeErrorbefore the backtest that names the parameter and the alternative, instead of a pandas truth-value error. Backtests with numeric thresholds are unchanged. (Issue #358, PR #359, #361) -
Added
finlab.backtest.metrics_from_nav(): pass a daily NAV Series produced by another backtesting framework or your own ledger to compute curve metrics such as annual return, Sharpe ratio and maximum drawdown the same way assim(). The NAV must already be net of trading costs and exclude deposits and withdrawals; the function creates no Report, trades, holdings or orders. (PR #367)
Fixed
-
When
sim()received a DataFrame, Series, DatetimeIndex or list of dates asresample(for example, rebalancing on monthly revenue release dates),resample_offsetwas silently ignored. Each rebalance date is now shifted by the offset, and the report's next rebalance date uses the same shift. Strategies that use both parameters will get different rebalance dates and backtest results; stringresamplevalues are unchanged. (Issue #362, PR #363)Cloud schedules receive this change with this runtime upgrade; no manual package upgrade is needed. If you run strategies yourself, upgrade the package, restart Python/Jupyter, and recheck strategies that combine custom rebalance dates with
resample_offset.Behavior change Results may change
-
When the backtest authorization connection of
sim()is interrupted, it is retried once after 3 seconds, and the request now has a 30-second timeout, so a single dropped connection no longer fails the whole backtest and an unresponsive server no longer blocks it indefinitely. If both attempts fail, the error asks you to check your network and rerunsim(). (Issue #368, PR #369) -
When the local data cache is in a read-only folder (for example, a read-only mount),
data.get()no longer raisesLocalDataReadErrorbecause it cannot create or open the.lockfile; writing the cache still requires a writable folder. (Issue #372, PR #373) -
With pandas 2, boolean positions containing missing values (for example, a boolean DataFrame after
.shift()) no longer makesim()print aFutureWarningaboutDowncasting object dtype arrays on .fillna; missing values still mean no holding, and backtest results are unchanged. (Issue #364, PR #365) -
show_alerting_stocks()for order execution now prints a disposition/warning stock alert heading and notes, and prints a confirmation when no pending order is on the disposition or credit warning lists, instead of printing nothing. (PR #357)
2.2.6 (2026-10-05)
Fixed
-
Fix
data.get()failing in cloud schedules when it cannot update the revision timestamp of a read-only cache; the existing cached contents are now used as-is. Data values and backtest calculations are unchanged. (Issue #344, PR #346) -
Use the system temporary directory for the default data cache on Google Cloud Functions, Cloud Run services/jobs, and AWS Lambda. Other environments also fall back to temporary storage when the home cache cannot be written, avoiding failures during imports or
data.get(). Writable local homes, Colab, and explicitFileStorage(path)locations are unchanged. (Issue #344, PR #347)
2.2.5 (2026-10-05)
Changed
-
When a strategy holding short positions has a holding stopped out between rebalances,
report.weightsenlarged the remaining short weights, so live positions built withPosition.from_report()were oversized. The remaining shorts now keep their original weights, matching what the backtest actually holds: with five shorts at -0.2 each and one stopped out, the other four return from -0.25 to -0.2. Long-only strategies are unaffected, and backtest returns and trade records are unchanged. (Issue #229, PR #338)Cloud schedules receive this change with this runtime upgrade; no manual package upgrade is needed. If you run strategies yourself, upgrade the package, restart Python/Jupyter, and check live targets before placing orders.
Behavior change
-
After a data correction, default
data.get()/data.gets()calls receive the corrected data within about 5 minutes, without waiting for the next scheduled update or usingforce_download=True. An up-to-date local cache needs no online check. After the scheduled update time but before new data arrives, FinLab checks at most once every 5 minutes instead of adding an online request to every call. A DataFrame already returned remains a snapshot; call again to use the corrected data.import finlabno longer queries the data update time in the background. (Issue #304, PR #336)
Added
-
Added
data.describe()to look up a dataset or field's description, columns, market, category and update schedule without downloading data. It shares the catalog cache withdata.search(). Unknown names raiseKeyError. (Issue #88, PR #313) -
Added the
FINLAB_MAX_DOWNLOAD_WORKERSenvironment variable to set how many downloadsdata.get()/data.gets()run at once: 8 by default, and 1 downloads one at a time for environments that limit threads. Also addeddata.free_memory()to release memory held by loaded data; the next read loads from the local cache. DataFrames you still hold are not released, sodelvariables you no longer need first. (Issue #119, PR #337) -
finlab aiadds--history [N]to list recent questions (10 by default),--show <question ID>to read a question with all its replies (including later progress updates), and--jsonoutput for your own scripts or AI agents. (Issue #328, PR #329)
Fixed
-
With pandas 3, the first return after a gap in a stock's prices was dropped. This affected weights from
position.weight.inverse_volatility(),risk_parity(),correlation(),target_volatility()anddrawdown_control(), stock and benchmark returns inevent_study(), and margin allocation in leveragedPosition.from_weight(). Prices are now carried forward across the gap before returns are computed, matching pandas 2. Users on pandas 2 are unaffected. (Issue #330, PR #331)Results may change
-
python -m finlab cloud deploy --helpand the CLI documentation listed the small tier (s) as 2 GiB memory with a 120-second timeout. They now show 1 CPU, 4 GiB and 300 seconds. The actual runtime specification is unchanged. (Issue #332, PR #333)
Documentation
- Added Upgrading and reproducing results, covering conditions since 2.0.19 that change stock selection, backtests or live targets, with migration code, downloadable before/after cases, and how to pin environments, data versions and backtest settings. JSON and Atom release-impact feeds let you filter changes that affect your strategy programmatically. (Issue #334, PR #335)
2.2.4 (2026-10-04)
Changed
- Reverted the off-schedule historical-correction cache checks introduced in 2.2.3 to avoid an extra online request on every default
data.get()/data.gets()call. Other 2.2.3 changes are retained. After a data correction, the local cache expires at the next scheduled update; fetch the data again then to receive the new values. US data updates at 05:30 Taipei time. To obtain the corrected data immediately, download the related inputs withforce_download=True. The existing cache behavior is retained, and no additional fix is planned for this behavior. (Issue #304, PR #326)
Added
- Added
finlab.portfolio.TradeReviewto rebuild daily holdings, cash, NAV and reports from actual fills, dividends, stock splits and cash flows, and compare actual positions with targets.from_account()queries history from supported broker integrations. Fees, cash flows and events unavailable from a broker may be estimated, use a holdings-based calculation, or be listed inlimitations; review those limits before use. (Issue #82, PR #322) - Added opt-in
data.indicator(..., strict=True): TA-Lib indicators accept only numeric parameter names, and TA-Lib price-input selectors are rejected. pandas_ta indicators raiseNotImplementedErrorin strict mode. Only keyword names are checked; TA-Lib still validates the values. The default mode is unchanged. (Issue #134, PR #318)
Fixed
- In
finlab aichat, multi-line pastes stay in a single question even when the last line has no trailing newline, instead of being split into two questions and charged twice. (PR #310)
2.2.3 (2026-10-04)
Changed
-
After stop-loss/take-profit exits,
report.next_weightsandPosition.from_report()now follow the backtest engine's allocation to the remaining holdings at the next rebalance. For example, with five equally weighted stocks and three stopped out, the remaining two targets change from 20% each to 50% each when no position cap applies. This fix does not change backtest returns or trade records; the next live rebalance may create larger positions.position_limitstill caps each stock's weight. (Issue #207, PR #319)Cloud schedules receive this change with this runtime upgrade; no manual package upgrade is needed. If you run strategies yourself, upgrade the package and restart Python/Jupyter. To retain cash after stops, convert boolean positions to floating-point weights before backtesting: five stocks at 0.2 each keep the remaining stocks at 0.2 after three stops. The example only scales rows whose total absolute weight exceeds 1. Exclude stocks with missing prices before computing the weights.
position = position.astype(float) position = position.div(position.abs().sum(axis=1).clip(lower=1), axis=0) report = sim(position, resample="M", stop_loss=0.1)Behavior change
-
With
resample=None, between a rebalance and the next position change,report.next_weightsandPosition.from_report()no longer add back stocks excluded at that rebalance by stop-loss/take-profit, so live trading no longer buys them back immediately, matching the backtest. Live targets change for both bool and float positions: for example, a float position of 3 stocks at 1/3 each with one stopped stock excluded at the rebalance now targets 2 stocks at 1/3 each with the remaining 1/3 in cash, instead of 3 stocks at 1/3 each; a bool position now targets 2 stocks at 1/2 each instead of 3 at 1/3. Backtest returns and trade records are unchanged. After upgrading, check live targets before placing orders. (Issue #228, PR #314)Behavior change
-
Online
data.get()/data.gets()now check for historical corrections outside the usual refresh schedule, even when the cache has not expired. Previously returned DataFrames remain snapshots; fetch the relevant inputs again before recalculating. This adds online queries and cache validation work. (Issue #304, PR #305)
Added
- Added
finlab aito ask the FinLab AI assistant from the terminal. (PR #308) - Added the first stage of stock futures backtesting:
TWStockFuturesMarketcomputes integer-lot P&L, fees, taxes and margin usage in NTD. Prices use adjusted underlying stock prices, and users supply margin ratios. Basis, contract rolling, margin calls/liquidation, actual futures prices and live orders are not yet included. (Issue #219, PR #292) - Added download and search support for 1-minute futures data across contract months, including night sessions spanning weekends. (PR #309)
data.universe()supports explicit stock lists; Taiwan OHLCV datasets support English aliases. (PR #316, #320)data.search(details=True)exposes dataset permissions and the free-data cutoff, and default searches flag VIP datasets. Free-account warnings and docs state the Taiwan historical cutoff of 2023-12-31 and trial-expiry restrictions. (Issue #162, PR #306)
Fixed
- Fixed parsing historical revision timestamps containing both whole and fractional seconds. (PR #312)
- Shares-outstanding
data.readiness()now checks the latest officially available month for TWSE, TPEx and emerging stocks. Missing, unknown or stale periods return not ready; per-market diagnostics are available insources[0].periods. Other monthly and quarterly datasets retain the existing source-schedule diagnostics. (Issue #133, PR #321)
Documentation
- Explained that
next_weightsis a rebalance preview, demonstratedreport.is_rebalance_due(), and distinguished a due rebalance, complete data and actual execution. (Issue #163, PR #317) - Redirected
/docs/getting-start/to/docs/getting-started/.
2.2.2 (2026-10-03)
Changed
-
data.indicator()with TA-Lib now reports misspelled or unsupported parameters and lists the valid ones, instead of silently using defaults. For adjusted prices, useadjust_price=True(adj=Truealso works);adjusted=Trueandadjust=Trueare invalid. pandas_ta indicators are unaffected. (Issue #274)# Use timeperiod, not the typo timperiod sma = data.indicator('SMA', timeperiod=10, adjust_price=True)Behavior change
-
When rebalancing with
PortfolioSyncManager.update(), if strategies keeping their holdings have already used up the total budget, the update now raises an error and preserves existing holdings to avoid accidental liquidation. The entire update stops, including other strategies' stop-loss and take-profit checks. Increasetotal_balanceas the error suggests, allow enough for purchases and spare cash, then rerunupdate(). Save or sync only after it succeeds. Updates that only handle stops or remove strategies, without rebuilding holdings, are unaffected. (Issue #164)The amount in the error is only a lower bound: check that the remaining budget can buy the target stocks. A positive balance too small to buy one share is still tracked in Issue #296.
Behavior change
Fixed
-
sim()and lazy rebalance schedules: Fixed newer pandas rejecting legacy frequency names with day shifts such asresample='M+2'and'Q+1'. Frequency names are converted for the installed pandas version before parsing the shift.'M+2'and'ME+2'produce identical rebalance dates; unshifted'M'and'W-FRI+1'keep their behavior, and pandas 2.1 still uses the legacy names. (Issue #269) -
When IPython is unavailable,
report.display()now gives environment-specific guidance: in Studio/Pyodide, putreportby itself on the last line of the cell; in a Python script, save withreport.to_html("report.html")and open the file in a browser. Text summaries and display behavior are unchanged. (Issue #283)
Documentation
- Restore the Chinese and English API reference overview, including the destination of
https://doc.finlab.tw/reference/, with links to each module. (Issue #282) - US index universe documentation and examples explain that
us_universe(index='SP500'/'NDX')filters by the latest constituents. Historical backtests must apply constituent records by date; the existing API still uses the latest list, and production data is unchanged. (Issue #239) - Backfill data access, quarterly date conversion, host timezone, and factor analysis behavior changes from 2.0.19–2.2.0 under the versions where they took effect. (Issues #223, #274)
2.2.1 (2026-09-30)
Changed
-
hold_until()addsexit_mode. The default,'state', keeps the previous exit condition active, restoring the exit behavior from before 2.1.0. Use'event'to keep the 2.1.0–2.2.0 behavior of exiting only on signal dates. (Issue #224)# Revenue or financial conditions: stay active until the next update position = entries.hold_until(exits) # One-off events: exit only on dates listed in exits position = entries.hold_until(exits, exit_mode='event')For example, Monday's exit signal is True. Tuesday has an entry signal but no new exit data.
'state'blocks the entry;'event'allows re-entry on Tuesday. Both modes agree when exit data is available every day.For
'event', keep the exit condition's original dates instead of mixing it with daily conditions first; in lazy strategies, call.collect()on the exit condition separately. For'state', sort dates from oldest to newest; a final True blocks entries until a False appears. Other backtest fixes remain, so results may not exactly match an older release.Results may change
Fixed
-
data.get(): Fix repeated failures to fetch data after the cache expires when using finlab 2.0.19–2.2.0 with pyarrow < 16. If an error saysRecordBatchhas nocastorselectattribute, upgrade and restart Python or the Jupyter kernel. There is no need to delete the cache. (PR #241, Issue #232)If you cannot upgrade finlab yet, use
pip install -U "pyarrow>=16"and restart instead. -
groupby_category()on quarterly data now computes industry statistics from reports already public on each date, excluding peers' undisclosed values. The result index changes from quarters to disclosure dates. Daily and monthly inputs are unchanged. (PR #220, Issue #212)This change requires updating
.loconly when querying quarterly results aftergroupby_category()..loc['2025-Q1']now selects disclosures from January–March, not first-quarter reports, and may return data without an error. Query the latest data available by a date instead:industry_mean = quarterly_factor.groupby_category().mean() # Industry mean as of 2025-05-15; an empty table if no data is available latest = industry_mean.loc[:'2025-05-15'].tail(1)Companies that have not disclosed the new quarter still contribute their previous-quarter values.
Results may change
-
If you call
stack().reset_index()on the original data after quarterly date conversion or ranking and read asymbolcolumn, setdf.columns.name = "symbol"first. These operations no longer name the original data's columns automatically. -
data.get()download errors now explain the problem and what to do, without exposing a URL that grants access to the data when you share the error for help. (PR #258, Issue #247)from finlab.exceptions import DataError try: close = data.get('price:收盤價') except DataError as error: print(error)Existing
except requests.RequestExceptionhandlers still work for download errors; in the browser (Pyodide), useexcept DataError. For custom handlers, readerror.statusfor the HTTP status anderror.kindfor the error category.error.requestand response URLs, headers and bodies are no longer available;error.responseis None for non-HTTP errors. -
When running several Jupyter kernels or parallel backtests,
data.get()could keep returning old data after a historical correction. It now downloads the data that needs updating on the next read, without requiring you to clear the cache. (PR #260, Issue #255) -
Fix results or ordering changing after a Python restart with identical inputs in
industry_rank(),weight.cap_industry(),create_custom_report()anddrop_duplicate_cols(). Results are now consistent across runs. Tiny numerical differences after upgrading may still change selections at a cutoff. If you saved results for comparison, save a new baseline after upgrading. (Issue #225) Results may change
2.2.0 (2026-09-28)
Added
-
data.get(dataset, as_of=...)/data.get(dataset, version=...)downloads historical versions;data.versions(dataset)lists what is available.as_ofuses Taipei time if no timezone is supplied, and a date alone means 00:00. Useversionfor a specific, reproducible snapshot. Paid members only; downloads count toward the daily quota, cached reads do not. Retention is about two weeks, with plans for 30+ days. For financial statements, usedata.get('financial_statement_versions'). Choose eitheras_oforversion; neither works withstart/end, tick/minute data oruse_local_data_only. The API may still change. (PR #211) Experimental -
data.getsupports index conversion and quarterly masking for TW*_revised(corrected) tables. When a backtest is truncated,revised_atdates later than the truncation date are capped at it, and value items emit a warning that corrected values cannot be rolled back to what was known then. These tables are not yet available. (PR #209)
Changed
finlab.tools.event_study.create_factor_data()now includes make-up trading Saturdays instead of moving them to Monday, so IC and quantile returns may change. Non-trading dates still move to the next trading day. This does not affectindex_str_to_date();.deadline()also keeps make-up trading dates (see PR #210 below for revenue data). (PR #210; added 2026-09-30, Issue #223) Behavior change Results may change
Fixed
-
Fix quarterly values with missing disclosure dates appearing too early in backtests. This applies to
index_str_to_date()(including the default insim()) and.deadline(). In a monthly “top 20 by ROA (.deadline())” strategy, untradable stock 7947 no longer takes a slot: total return changes from 128.2% to 139.5%, and trades from 2465 to 2575. The 2026-09-27 TW audit found missing dates only for.deadline(), not disclosure dates forindex_str_to_date(); other markets were not checked. (PR #208; corrected 2026-09-30: this entry previously saidindex_str_to_date()was unaffected, Issue #274)Audit details
A 2026-09-27 audit of all 211 TW quarterly fields, including Emerging Stock Board stocks, covered 2013-Q1 through 2026-Q2 and 2,870 stock codes. Of 15,178,900 non-null values, 136 lacked the cutoff date used by
deadline()(0.000896%). All belonged to stock 7947 (補丁科), 2026-Q2: 92 financial statement fields and 44 fundamental indicators. Each record means one stock × quarter × field, not a separate company. That quarter has a disclosure date of 2026-09-17; the audit found no non-null values missing the disclosure date used byindex_str_to_date(). The percentage measures source records before date conversion, not the historical cells affected after forward filling.Results may change
-
A monthly revenue deadline on a make-up trading Saturday (2009-01-10, 2016-09-10) previously moved incorrectly to Monday; it now takes effect that Saturday. Deadlines on non-trading holidays still move to the next trading day. In example strategies: revenue momentum total return 471.8% → 459.7%, PEG 1421.9% → 1427.7%; the other 7 price and quarterly example strategies are unchanged. (PR #210) Results may change
2.1.1 (2026-09-24)
Fixed
- Fix
OutOfBoundsDatetimewhen pandas 3 calculates TW market close times for distant dates, such as year 2999. Normal dates and timezone behavior are unchanged.
2.1.0 (2026-09-24)
2.0.22 and 2.0.23 were yanked from PyPI
These versions changed how plain pandas DataFrames combine with FinlabDataFrame, leaving positions in common templates entirely NaN and causing cloud strategies to fail. Upgrade from 2.0.21 or either yanked version to 2.1.0. Other improvements, including sim(end_date=...), search aliases and quarterly date alignment, remain in 2.1.0.
Rewrite (cond * score).is_largest(N) as score[cond].is_largest(N)
(cond1 & cond2) * score gives stocks that fail the conditions a score of 0. When fewer than N stocks qualify, is_largest(N) fills the remaining slots with those 0-score stocks in column order (1101, 1102, 1103, ...), so the holdings include stocks that fail the conditions. Use score[cond].is_largest(N) (or score.where(cond).is_largest(N)) instead: stocks that fail become NaN and are not ranked, and when fewer than N qualify only those are held. 2.1.0 emits TopNPaddingWarning when this padding happens; the selection is unchanged. The official 小資族資優生 template is being updated the same way, so its holdings can drop below 10 stocks when fewer than 10 qualify.
# Before
position = ((cond1 & cond2) * score).is_largest(10)
# After
position = score[cond1 & cond2].is_largest(10)
If a position is aligned with reindex(<monthly revenue dates>) without a fill value, add fill_value=False so the position does not start with NaN rows (see PR #194 below).
If you already write score[cond] and still see the warning, qualifying stocks are tied at the N-th place (for example all scoring 0) and are picked in column order; add a secondary score if you need a stable choice.
Keeping the 2.0.21 behavior
The old form of each behavior change is below. The bug fixes (NaN handling in sim(), look-ahead in quarterly cross-sections, drawdown_control / target_volatility, the from_weight weight sum) have no old form; reproducing old results exactly requires pip install finlab==2.0.21, which is not recommended because those results contain the bugs.
| Change | How to keep the old behavior |
|---|---|
position[timing] now goes to cash (PR #191) |
position.loc[timing.reindex(position.index, fill_value=False).to_numpy()]: drops the False dates, so sim() carries the previous holdings |
set_universe() without filters now removes the filter (PR #192) |
data.set_universe(exclude_sector=[]): restricts to every security in the current market's catalog |
| Studio does not upload by default (PR #187) | sim(..., upload=True) |
hold_until exits are events (PR #177) |
position = entries.hold_until(exit.reindex(entries.index, method='ffill')) |
| Universe filters that match nothing raise (PR #181) | No old form; they used to fall back to the whole market silently, so fix the filter |
Added
-
Reports returned by
sim()now exposeupload_status:skipped,pending,success, orerror.StrategyUploadWarningsurfaces upload failures and environment variables overriding an explicitly chosen report name. Native Python uploads remain asynchronous; the status updates when the upload finishes. (PR #178) -
TopNPaddingWarning: whenis_largest(N)/is_smallest(N)fills N places with tied stocks, the warning lists affected dates, counts and how to rewrite the selection. Only ties at the selection boundary trigger it; results are unchanged. (PR #200) -
PandasLeftOperandWarning: emitted when a plain pandas DataFrame on the left and aFinlabDataFrameon the right are combined with+ - * / // % ** & | ^and their dates or stocks differ. pandas alignment is used in that case (no forward fill of dates, union of stocks, plainDataFrameresult), which differs from putting theFinlabDataFrameon the left. Results are unchanged; filterable by class. (PR #193) -
ZeroShareAllocationWarning: for board-lot orders,Position.from_weight()/from_report()now lists stocks allocated 0 shares because the budget cannot buy one lot. Useodd_lot=Trueor increasefund. Order quantities are unchanged. (PR #190, order_executor PR #23) -
data.reset_universe(): removes the universe filter, so laterget()/gets()calls (including cached reads) return complete data. Exiting a nesteduniverse()still restores its previous selection. (PR #186) -
data.get_calendar()anddata.readiness(): check the TW trading calendar and whether your strategy’s data has updated. For TW daily data,data.is_tradable()returns True only after the sources update; other markets or unavailable calendars keep the cache-expiry check. Reports show the planned rebalance date, holding progress, pending data and when to rerun. (PR #182, #195, #198) -
The backtest dashboard adds a support panel: a report shown with
display()in a notebook carries the cached FinLab credential (valid for at most 1 hour), so users can contact support without logging in again. Files exported withto_html()do not contain it. Keep this in mind before sharing an.ipynbwith outputs. (PR #197)
Changed
-
sim()'s defaultupload=Noneuploads the report only in cloud-scheduled runs (FINLAB_FORCED_STRATEGY_NAMEis set). FinLab Studio uploads only with an explicitupload=True, so repeated AI experiments no longer overwrite the online report on every backtest.FINLAB_STRATEGY_NAMEstill sets the strategy name used when uploading. (PR #187) Behavior change -
data.set_universe()with no filters (or with every filter set to"ALL") now equalsdata.reset_universe()and removes the filter; so dowith data.universe():,set_us_universe()andwith data.us_universe():. It used to restrict reads to every security in the current market's catalog, silently dropping identifiers outside it (for example non-standard TDCC codes and historical ids). (PR #192) Behavior change -
universe filters that match no securities (for example a misspelled sector) raise
ValueErrorand leave the current universe unchanged; they used to fall back silently to the whole market. When an active universe has no overlap with a dataset, aUserWarningis emitted and the data is returned unfiltered. Stock ids are stripped of surrounding whitespace before matching. (PR #181) Behavior change -
Python 3.10 is now the minimum (
requires-python >= 3.10). No Python 3.9 wheels have been published since 2.0.3; the package metadata now says so. Behavior change -
Remove
MissingDisclosureWarning, added in 2.0.23. Users cannot act on a quarter that lacks an alignment date; the data pipeline now fills those dates instead of warning users. -
When a
data.get()/data.gets()download returns HTTP 404 or 412, the download URL is requested again and the download is retried; it used to raise immediately. There is one retry only, and when the retry also fails, the error raised is the one from the retry. Other HTTP errors (for example 403, 500) are not retried and raise immediately. Downloads that succeed the first time are unaffected. (PR #185; added 2026-09-30, Issue #223) -
(exception type only): when
sim()'sresampleis a list of dates (DatetimeIndex, DataFrame, Series or list) and none of them falls within the backtest period,sim()raisesValueError: resample has no dates within the simulation range; it used to raiseIndexError: list index out of range. Whenresampleis a string (for example'M') and the backtest period contains no rebalance date, it is stillIndexError. Code that must handle both cases should useexcept (IndexError, ValueError). (PR #185; added 2026-09-30, Issue #223) Behavior change -
sim()now uses market time to set the data cutoff for positions without a timezone (Taipei for TW, UTC−5 for US). This fixes the latest signal and holdings lagging by a day on UTC hosts such as Colab when running at 00:00–08:00 Taipei time. The same instant now gives the same result across host timezones. Only the latest period is affected; UTC users upgrading from 2.0.23 may see a two-day advance when combined with the non-trading-day fix before 15:00. Custom markets withtzinfo()returning None still use host time. (PR #196; added 2026-09-30, Issue #274) Behavior change Holdings may change
Fixed
-
TW market close timestamps preserve their input resolution, avoiding
OutOfBoundsDatetimewhen the calendar fallback sees signal dates outside pandas' nanosecond range (for example year 2999). Normal dates and timezone behavior are unchanged. -
sim()exits delisted assets and excludes them from later rebalances. Taiwan stocks use exchange delisting notices: exit on the first available price bar after the announcement. If the announcement comes after the last price and the delisting is already within the data period, exit at the last available price. Without a notice, at least 20 trailing price bars with no price trigger the same last-price exit; this is an inference from missing prices, not confirmation of delisting. Withresample=None, the portfolio rebalances on exit; scheduled rebalancing holds the proceeds as cash until the next rebalance.DelistedHoldingWarninglists affected assets and exit dates. Previously these holdings could remain at a frozen price, be bought again, and appear with NaN trade returns. This changes historical returns, drawdowns, trades, and current/next holdings. (PR #183) Results may change Holdings may change -
Live rebalancing(
PortfolioSyncManager,Position.from_report()): for TW daily strategies the rebalance window opens at 15:00 of the last trading session on or before a weekend or holiday signal date, and the target switches to the new period at the same time. Evening-only schedules used to rebuild on Friday evening with the previous period's target while recording the rebalance as done, so weekly strategies traded one week behind the backtest every week, and monthly strategies whose month-end fell on a weekend kept the old holdings for the whole month, with no warning.sl_enter/tp_entertemporary exits end at the same time. (PR #185, order_executor PR #24, #25) -
Live rebalancing:when
PortfolioSyncManager.update()finds holdings still built for a superseded target (the schedule missed the rebalance window, for example because the host was down or cron did not fire), it logs a warning and catches up to the report's current target. It used to keep the old holdings silently until the next rebalance. Position records gainapplied_signal_at, the signal date of the target actually built; a weekend or holiday signal that executes on the same session as the previous weekday's signal is not treated as missed. An unparseable older record only skips this check and no longer stops other strategies from updating. Runupdate()on every schedule, not only whenis_rebalance_due()is True. (PR #185) -
TW daily live rebalancing:
is_rebalance_due()stays True until the next signal replaces it, so schedules running after the open or close can still rebalance. If the trading calendar is unavailable, the signal date’s close determines timing. The dashboard stops showing “Rebalancing” after the execution day closes. (PR #185, new_website PR #103) -
sim(): when a TW daily strategy runs before 15:00 Taipei time on a non-trading day (weekend or holiday), the data cutoff is no longer moved back to the previous day, so a signal dated that day is included, andnext_weightsand the current/next holdings can differ from 2.0.23 and earlier. Any run before 15:00 used to move the cutoff back, even when the market was closed that day; on trading days the cutoff still moves back before 15:00. Without a trading calendar the original rule applies. What is affected is the latest period at run time. (PR #185; added 2026-09-30, Issue #223) Holdings may change -
Historical backtests (
sim(end_date=...)) no longer query the trading calendar or dataset metadata, andreport.is_tradable()is always False for them. (PR #185) -
The trading calendar, the dashboard's stock data and symbol logos, and the reports embedded in the docs are served from the production domain
r2.finlab.financeinstead of the rate-limitedr2.devURL. The trading calendar is refreshed every three hours; a calendar past its expiry stays in use while it lists future sessions, and a failed download keeps the calendar already fetched. (PR #185, new_website PR #103) -
position[timing](withtiminga date-indexed boolean Series) keeps every date and holds cash on dates wheretimingis False (False for boolean positions, 0 for weights), the same asposition & timing. It used to drop those dates, sosim()forward-filled the previous holdings and a timing filter stayed fully invested through its risk-off periods. Stock-indexed boolean Series (including object dtype) still select columns.DateMaskSelectionWarning, which only existed in unreleased versions, is removed. (PR #191, #173) Behavior change Results may change -
sim()now treats NaN in a position as not held, once, at its input (the same asfillna(0)/fillna(False)). Object columns produced by merging two sleeves (pos_a + pos_b,combine_first,pd.concat) become bool or float. Withresample=None, entries and exits through NaN used to be skipped, a position whose first rows were all NaN raisedIndexError, andavgNStockcounted NaN as held. Withresample=None, a position that starts with NaN rows can now start its backtest earlier. In a replay of 1,089 cloud-scheduled strategies, 275 had NaN in their positions, 0 changed their latest target weights (next_weights), and 79 changed their metrics, mostly because the first rebalance used to be skipped. (PR #194) Results may change -
quarterly cross-sectional operations align values to each company's disclosure dates first and then compute with the values public on each date:
rank(axis=1),is_largest()/is_smallest()(including lazy), everydf.csanddf.sectormethod, andneutralize()on all-quarterly inputs. Early filers are no longer compared with same-quarter values that peers have not disclosed yet, removing lookahead bias;cs.rank()no longer returns all NaN for quarterly data. Tie handling and missing undisclosed values are preserved. (PR #184, #188) Results may change -
hold_until(exit, ...)treats exits as events, like entries, effective only on their own dates. Whenexithad sparser dates than the entries (event dates, weekly/monthly/quarterly), the last exit used to be forward-filled and the stock could never be entered again. To treat a low-frequency exit condition as a persistent state, writeexit.reindex(entries.index, method='ffill'). (PR #177) Results may change -
weight.drawdown_control()/target_volatility()first convert boolean selections or weights with gross exposure above 1 into the weights used bysim(), then apply risk scaling. This prevents simulation normalization from undoing exposure reductions. Scaled exposure above full investment remains subject to the simulation cap. (PR #180) Results may change -
With
resample=None, future position rows are kept in the report as the planned rebalance date and never enter executable holdings. Future rows also no longer change the backtest period of sparse positions or bypass the daily signal cutoff. (PR #196) -
Position.from_weight()scales weights whose total absolute value exceeds 1 down to full investment, the same assim(), and logs a warning; it used to raiseAssertionError: Unexpectedly insufficient funds.(PR #174, order_executor PR #21) -
finlab.tools.factor_metrics.ic()/calc_metric()now group correctly by date even when the date index is unnamed or has a name other thandate. (PR #176) -
Convertible-bond reports use
債券簡稱fromcb_published_info, with compatibility for legacynameand identifier schemas, selecting the latest nonempty name instead of displaying only the bond code. (PR #159) -
SinopacAccount.update_order()raisesOddLotRepriceErrorif replacing a cancelled odd-lot order fails or returns no order ID, instead of merely logging the failure and appearing to succeed. (order_executor PR #18) -
OrderExecutor.show_alerting_stocks(): fix pandas 3 compatibility and consistently use strings for alerting-stock codes. (order_executor PR #20) -
Restore the 2.0.21 behavior: arithmetic and logical operations with a plain pandas DataFrame on the left and a
FinlabDataFrameon the right (pd_df * fdf,pd_df + fdf,-,/,//,%,**,&,|,^) again use the pandas alignment from 2.0.21: union of dates and columns, no forward fill, and a plainDataFrameresult. Version 2.0.22 usedFinlabDataFramealignment instead (intersection of columns, forward-filled dates). Templates such as 「小資族資優生」 multiplyempty_df = pd.DataFrame(1, index=..., columns=...)by par-value-change ratios (par_value_change_*) to calculate market capitalization. That change left only stocks with par-value-change events, then zero stocks after multiplying the TWSE and TPEx tables, producing all-NaN positions and aValueErrorfromsim(). Comparisons (<,>,==, etc.) already usedFinlabDataFramealignment in 2.0.21 and remain unchanged. (PR #171) Behavior change -
Plain DataFrames with quarterly string indexes can again be combined with date-indexed data, including through
FinlabDataFrame.align(), without theTypeErrorintroduced in 2.0.22, restoring the 2.0.21 results. QuarterlyFinlabDataFrameinputs still convert to disclosure dates automatically when aligned to dates withreindex()/reindex_like()/align(). (PR #171) -
Backtest reports (
report.display()/report.to_html()): fix incorrect font sizes and spacing. Monthly returns scroll horizontally on phones so numbers no longer overlap. -
Backtest report: with the stock panel open (⌘K stock search), the report reflows beside the panel on wide screens instead of being covered. The "Strategy" legend chip on the ratio page was invisible in dark mode and now uses the bar color.
-
plot_tw_stock_radar(): in the defaultline_polarmode the radial axis ends atcut_bins(10 by default); it used to be multiplied by the number of stocks (30 for three), squeezing the plot into the center. The stackedbar_polarmode is unchanged. -
plot_tw_stock_candles(): the title is centered so it no longer overlaps the range selector.
Docs
-
The PyPI description is rewritten in English, with the actual output of every example and screenshots of each report page.
-
US-market examples now import
from finlab.markets.us import USMarket(from finlab.market import USMarketraised an error). -
The order docs add "Error 5: Signal but No Orders", explaining whole-lot flooring and
ZeroShareAllocationWarning. (PR #190) -
The
is_largest()/is_smallest()docstrings and the DataFrame reference showscore[cond].is_largest(N). (PR #200)
Engineering
- Tests now reproduce the template with offline
sim()and verify that operations with pandas on the left match 2.0.21. (PR #171)
2.0.23 (2026-09-23)
Fixed
- Quarterly date conversion (
index_str_to_date()and automatic alignment with daily data) now uses the latest disclosed quarter with a value. Older reports filed late no longer overwrite newer quarters; when several quarters are filed on one day, the newest is used. The date index stays the same. A comparison with production data found corrected EPS values across about 200 stocks and 13,000 trading days. (PR #169) Behavior change
Added
index_str_to_date()now warns withMissingDisclosureWarningwhen quarterly values lack an alignment date and are skipped, listing counts and examples. Previously it silently kept the earlier quarter’s value. Output is unchanged. (PR #170)
2.0.22 (2026-09-23)
Added
-
data.search()now searches dataset names, descriptions, aliases and columns. For example,data.search('市值')findsetl:market_value, anddata.search('集保')findsinventoryandetl:inventory:*. Matching tolerates case, character width and common wording differences; the return type is unchanged. (PR #165) -
data.get(): a missing dataset now raisesfinlab.exceptions.DatasetNotFoundError(aRuntimeErrorsubclass, so existingexcept RuntimeErrorhandlers still work) listing the closest valid keys and any measurement-difference note, without exposing internal storage paths. Registered equivalent legacy keys (for examplemargin_transactions:融資餘額,financial_statement:營業活動之淨現金流入(流出)) emit aUserWarningand fetch the correct key instead; this also applies todata.gets()andstart/endreads. (PR #165) -
sim(end_date=...): set an inclusive backtest end date; open positions use prices available by that date only. Also supported bysim_batch()and lazy positions. Slicing monthly positions could still extend returns to today; usesim(position, end_date="2025-12-31")to set the cutoff explicitly. Calls without it are unchanged. (PR #166) -
report.get_stats()/get_benchmark_stats(): Add the camelCase names used byget_metrics()(annualReturn,sharpeRatio,sortinoRatio,calmarRatio,volatility,maxDrawdown,avgDrawdown,avgDrawdownDays,m12WinRate) as aliases with the same values as the ffn keys. Existing keys are kept.win_ratiohas no alias because it counts closed trades only, whileget_metrics()'swinRatealso counts open trades. (PR #166) -
finlab.tools.factor_metrics.ic(method=...): Choose'pearson'(default, unchanged results) or'spearman'(rank IC). (PR #166)
Changed
-
report.trades['stock_id']now contains the code only (e.g.2330), so it can join directly with price data. Read the newnamecolumn for the name (e.g.台積電, empty if unknown), orsymbolfor “code name”. Also applies toCloudReport.trades; website, Studio and HTML report displays are unchanged. (PR #166) Behavior change -
Quarterly string indexes (e.g.
2013-Q1) now align with dates automatically.FinlabDataFrame.reindex()/reindex_like()/align()onto a date index first convert the quarters to each company's disclosure dates withindex_str_to_date(). Previouslyreindex(close.index)returned all NaN andreindex(..., method='ffill')raisedTypeError. Operations with a plain pandas DataFrame on the left (pd_df + fdf,pd_df & fdf, ...) now useFinlabDataFramealignment (forward-filled union of dates, intersection of columns) and return aFinlabDataFrameinstead of pandas' NaN-padded result. A plain (unwrapped) quarterly DataFrame combined with dated data now raisesTypeErrorthat points toFinlabDataFrame(df)orindex_str_to_date(), instead of silently producing NaN. (PR #166) Behavior change -
finlab.tools.event_study.create_factor_data()now labels<name>_factor_quantilegroups 1–5, matchingdf.cs.bucket(5). Ten stocks are split into groups of two, fixing the previous 1/2/3/1/3 split with labels 0–4. (PR #166) Behavior change -
report.get_stats(resample=...):resamplenever had an effect. Passing it now emits aDeprecationWarningand results are unchanged; stats are always computed on daily returns. Thestatssent through the legacy upload API gains the alias keys above. (PR #166) -
Operations between a
FinlabDataFrameand apd.Seriesnow align on the axis the Series index labels. A Series indexed by stock ids (e.g.pd.Series({'2330': 1.2}),close.iloc[-1]) applies to every date; stocks missing from it areFalsein&/|and NaN in arithmetic. Previously the stock ids were aligned against the dates, giving all NaN or allFalse. A Series indexed by dates (or quarter/month strings) behaves as before. When the index matches both the dates and the stocks, or neither, aValueErrornow suggestsdf.mul(s, axis=0)(align on dates) ordf.mul(s, axis=1)(align on stocks).df[stock boolean Series]now selects columns. (PR #168) Behavior change -
df[date boolean Series](e.g. market timing viaposition[timing]) still drops the False dates, whichsim()fills with the previous holdings instead of cash; it now emits aFutureWarningrecommendingposition & timing. The next minor release will keep those dates asFalse. Row filters on long tables without a date index (e.g.df[df['stock_id'] == '2330']onbroker_transactions) are unaffected. (#112; PR #168)
Fixed
-
finlab.login(): when browser login failed, was rejected or timed out, it used to return silently as if logged in; it now raisesfinlab.exceptions.LoginError(aRuntimeErrorsubclass) explaining how to retry and how to usepython -m finlab token --envin headless environments. (PR #165) -
finlab.tools.factor_metrics.calc_ic(rank=True)ranked the factors but not the returns, so the result was neither Pearson nor Spearman. It now ranks both on each date and computes true Spearman on pairwise-valid values, matchingscipy.stats.spearmanr. Values change. (PR #166) Behavior change -
calc_ic(rank=False)raisedIndexErrorwhen any factor column was all NaN. It now starts at the first date where every factor that ever has an IC has one; all-NaN columns stay as NaN. (PR #166) -
sim()raisedIndexErrorfor positions with fewer than 3 rows. They are now held to the last price date. (PR #166) -
report.display()raisedImportErrorwhere IPython is not installed (e.g. Pyodide without dependencies, plain Python scripts). It now prints thereport.to_text()summary and suggestspip install ipythonorreport.to_html().save_report_pathis still written first. (PR #166) -
data.indicator(): When neither TA-Lib nor pandas_ta is installed, the error lists the exactpip install TA-Lib/pip install pandas-tacommands and each package's naming convention. In the browser (Pyodide), it says FinLab Studio preloads TA-Lib (reload the page) and that pandas_ta is not preloaded in Studio. When TA-Lib lacks an indicator, pandas_ta is tried next (previously, installing TA-Lib hid every pandas_ta indicator). When neither package has the indicator, aValueErroris raised with a case hint such as'rsi'→'RSI'. (PR #166)
Docs
-
Login docs now describe the current flow:
python -m finlab loginon your own machine; for servers and schedulers runpython -m finlab token --envlocally and setFINLAB_REFRESH_TOKEN,FINLAB_SESSION_ID,FINLAB_API_KEY;finlab.login()in Colab;FINLAB_API_TOKENis deprecated. The authentication order is documented. (PR #165) -
Document a 2.0.0 behavior change that was never written down:
sim()'suploaddefaults toNoneand uploads the report only whenFINLAB_STRATEGY_NAMEorFINLAB_FORCED_STRATEGY_NAMEis set (FinLab Studio, cloud-scheduled runs). Local, Jupyter and Colab runs do not upload by default; passupload=Trueor callreport.upload()to upload. (PR #165)
Engineering
- tests: Add an offline
sim()harness (synthetic market and licence stub) so unit tests run the real backtest engine and report assembly without credentials or data downloads. (PR #166)
2.0.21 (2026-09-21)
Fixed
-
Quarterly boolean conditions now work correctly with daily data: expressions such as
~maskandA & ~Bno longer select the wrong stocks. Recreate condition tables after upgrading. Values before the first disclosure remain unknown and are treated as not held in backtests. Usefillna(False)before.astype(bool)ornp.where. (PR #160) Results may change -
Report.to_pickle(): fixRuntimeError: dictionary changed size during iterationwhen saving a report while its background upload is still running. (PR #161) -
Pyodide after-market tick/minute data: browser downloads that hit an HTTP error keep the server's error detail (for example an unavailable trading day or missing permission) instead of a generic HTTP error.
2.0.20 (2026-09-18)
Added
data.get("tw_tick:2330", start=..., end=...)anddata.get("tw_minute:2330", start=..., end=...): download Taiwan after-market ticks and regular-session one-minute OHLCV. Both dates are required, up to 31 calendar days per call; incomplete trading days raise an error. Timestamps use Taipei time and simultaneous trades are preserved. Minute bars omit empty minutes and keep the 13:30 auction separate; 14:30 fixed-price trades appear only in ticks. Available to Taiwan data subscribers, with a free 2330 sample for 2026-06-01. Historical coverage is being added. See intraday data.
Changed
data.gets()does not support the intraday tick and minute datasets: a list containingtw_tick:*ortw_minute:*raisesValueError; usedata.get()withstart/endinstead.data.gets()is unchanged for other datasets. (added 2026-09-30, Issue #223)
2.0.19 (2026-09-16)
Added
data.universe.membership(): Add daily boolean listing-eligibility masks forTSE,OTC, andTSE_OTC, for filtering before cross-sectional ranking with expressions such asscore[eligible].is_largest(20). Reconstruct effective intervals from currently available listing and termination records, including listing dates and excluding termination dates, with support for exchange transfers and repeated listing periods. Unverified periods remainFalse; warnings andattrs['membership_missing_history']identify incomplete histories. This does not provide historical version snapshots or a complete ETF universe. (#140)data.get(dataset, start=..., end=...): regular datasets accept the keyword argumentsstart/endto read a date range; either may be omitted. The filter compares against thedatecolumn stored in the table, not the converted index: for a table indexed by quarter (for example2024-Q1), the date stored for that row is compared, not the quarter itself. A bare date forstart/endmeans 00:00 that day, and rows exactly equal tostartorendare included; for a table whosedatecolumn carries a time, rows after 00:00 on theendday are not returned. A dataset without adatecolumn, orstartlater thanend, raisesValueError. Calls withoutstart/endreturn the same data as before. (added 2026-09-30, Issue #223)
Fixed
CloudReport: Fill missing execution defaults when reading legacy holdings, including market, trailing-stop and weight settings, matching canonical reports while preserving explicit settings and holding actions. (#155; PR #156)Position.from_weight()/from_report(): Honor the suppliedallocationcallable instead of always using greedy allocation. Default allocation and lot/odd-lot conversion remain unchanged. (#114; PR #151, order_executor#17)sim(): Reject empty or all-NaN positions early with aValueErrorexplainingadd(fill_value=0)for combining different assets and explicit zeros for cash. Normal zero and sparse positions still pass shape validation. (#65; PR #149)- Backtest engine: Reject zero, negative or infinite trading prices for positions still tracked by the engine before division, with an asset/row-specific
ValueError, preventing division errors or NaN equity curves. Never-held assets and existing NaN handling are unchanged; upstream prices are not modified. (#45; PR #150) Report: Export custom Series/DataFrame trading prices as acustomholdings-config marker, fixing JSON/HTML export failures and displaying Custom/自訂 in the viewer. The live Report retains its price matrix; execution metadata cannot reconstruct it and falls back tocloseon read-back. (#76; PR #152, new_website#80)- Pyodide downloads: Convert ArrayBuffers in chunks of at most 8 MiB before assembling Python bytes, avoiding JavaScript single-string length limits for large datasets. All byte values are preserved; overall browser memory limits still apply. (PR #153)
Report.upload(): Fix cloud runs using the legacy upload API reporting success before checking the server response. Non-200 HTTP responses, unparseable JSON, and server-reported errors now return an error result. WhenFINLAB_FORCED_STRATEGY_NAMEoverrides a different explicit report name, emit a warning identifying the deployment strategy that receives the upload. (#141)CloudReport.from_cloud(): Load the complete website report when itsmetrics.backtest.updateDateis newer than the canonical report, avoiding stale performance and holdings. Equal, older, missing, or nonnumeric dates retain canonical loading; this comparison does not distinguish reruns covering the same data date. (#137)ml.feature.TalibIndicatorFactory.generate_feature_names(): Preserve first-sampled order when deduplicating feature names, preventing Python hash randomization from changing feature column order across processes with the same random seeds. (#135)data.universe(): Include table rows whosesymbol/stock_idmatches the active universe only after trimming leading or trailing whitespace, and emit aUserWarninginstead of silently excluding those rows. Returned identifiers retain their original values; the source data still needs whitespace cleanup. (#132)ml.DEnsembleModel: Fix feature selection deduping withset(...), which let Python hash randomization reorder selected feature columns across processes and could change model outputs even with the same data and seeds. Deduping now preserves the sampled order.ml.DEnsembleModel: Fix pandas 3 Copy-on-Write compatibility when filling NaN feature-importance values; the chainedinplace=Truereplacement is now an explicit assignment.- Behavior change; backtest results may change:
data.indicator(..., adj=True)now really calculates with adjusted prices. Through 2.0.18,adjwas passed unchanged to TA-Lib, which silently ignores keywords it does not know, so the indicator used unadjusted prices without an error;adjust_price=Trueused adjusted prices in both versions.finlab.ml.featurewithadj=Truelikewise now calculates indicators on adjusted prices: through 2.0.18 the indicators used unadjusted prices, yet the normalized ones (moving averages,MACD,MOM, and so on) were divided by the adjusted close. Code written withadj=Trueon 2.0.18 or earlier may get different indicator values, signals, and backtest results on 2.0.19 and later. Unsupportedadj_open/adj_high/adj_low/adj_close/adj_volumekwargs now raise a clear error instead of silently accepting adjusted-price intent that would not take effect. (Behavior change documented on 2026-09-30, Issue #274) MaeMfeAnalysis.display(): Remove the dependency on Plotlyfigure_factory.create_distplotand build the distribution traces withgraph_objects, restoring compatibility with Plotly 7.ffn_core: Use compatible month-end/year-end frequencies for monthly conversion andfreq="m"/freq="y"plots, fixing pandas 3ValueErrorwhile preserving existing calls and sampling dates on pandas 2.1 and later. (#70; PR #144)Report.position_info2(): Fix assigning adjusted-price fallbacks into an immutable Index when convertible bonds lack raw closing prices. Assets with raw prices continue to use them. (#64; PR #143)- Report numbers may change
report.metrics.sharpe_ratio(riskfree_rate=...),sortino_ratio(riskfree_rate=...)andreport.get_metrics(riskfree_rate=...): every call is computed with theriskfree_ratepassed in. A report used to keep theriskfree_rateof its first calculation and return the same numbers whatever was passed afterwards: once computed at the default (0.02), a different value passed later had no effect; when the first call passed another value, later calls at the default returned the numbers computed with that value too. The affected metrics are those that use the risk-free rate, such as Sharpe and Sortino; annual return, max drawdown, volatility and Calmar do not change withriskfree_rate. (#127; added 2026-09-30, Issue #223) PortfolioSyncManager.update(): Fix the stock-list drift warning to nameforce_override_difference=Trueinstead of the nonexistentforce_override=True; update decisions are unchanged. (#75; PR #142)
Documentation
- Multi-strategy portfolios: Clarify that every
update()call needs the chosenodd_lot/margin_tradingoptions, which apply only to strategies rebuilt on that call. (#62; PR #145) - Trailing stops: Clarify the entry-cost denominator for ordinary long positions and distinguish exit signals from fills. Entry 100, peak 150 and
trail_stop=0.2give a threshold of 130. (#69; PR #146) - Custom benchmarks: Use a
Market.get_benchmark()override before backtesting. Changing the benchmark requires rerunningsim(); assigningreport.benchmarkafterward does not update all charts and statistics. (#77, #80; PR #147) - Market timing: Use
FinlabDataFramemultiplication,position *= timing, instead of deleting cash-day rows. Follow native alignment rules without unnecessaryreindexor casts. (#112; PR #148)
Engineering
- Pyodide wheels: Build only CPython 3.13 for the current website runtime, avoiding incompatible Python 3.14/3.15 wheels selected by newer cibuildwheel releases. (PR #157)
- tests: Mark all five CloudReport freshness regression cases as
unitso CI runs them withpytest -m unit; fix Ruff lint and formatting issues in the feature-name ordering tests. (#137, #135)
2.0.18 (2026-08-19)
Fixed
data.get()/data.gets(): Improve Pyodide browser compatibility. Runtimes with WebAssembly JSPI retain parallelpyfetchdownloads; runtimes without stack switching automatically fall back to sequential synchronous XHR inside the Web Worker. If the metadata JSON POST fails CORS preflight, FinLab retries each item with a CORS-safelisted form POST instead of aborting the strategy on non-JSPI Safari / WebKit runtimes.FinlabDataFrame.rank(axis=1): Fix tie handling. The cross-sectional numpy fast path assigned ordinal ranks (equivalent tomethod='first'), disagreeing with pandas' defaultmethod='average', so tied cells on low-cardinality / discrete factors received distinct percentiles (a tied group had non-zero cross-sectional std). Tie handling is now delegated to pandas while keeping the pre-index_str_to_dateperformance win (quarterly data is no longer expanded to daily before ranking). Behaviour change: strategies that threshold onrank(pct=True)over factors with many tied values may select different stocks than before.sim(..., upload=True): Fix uploaded reports wiping the tier set bycloud deployand the schedule set bycloud schedule set. The Firestore write omittedupdateMask, which means whole-document replacement, so tier and schedule fields absent from the report payload were deleted; the write now touches only the report's own fields. This only bit when the report name matched the deployment SID — the previously recommended workaround of using different names is no longer needed from this release on.
2.0.17 (2026-08-02)
Added
finlab status/finlab auth status: Add a concise account and data-status summary showing the current plan, core Taiwan-price data freshness, consumed/total daily download quota, and the next daily quota reset (08:00 UTC+8), while retaining the existing active-session details.- Standalone strategy reports: free-plan users now see a VIP upgrade prompt, including the offer deadline when available. The upgrade link carries report-source attribution and opens the payment page in a new tab, making full backtests, current data, and cloud scheduling discoverable from the report.
report.get_benchmark_stats(): Add a benchmark performance-stats API. Returns the same ffn stats fields asget_stats(cagr,daily_sharpe,max_drawdown, ...) computed on the backtest-period benchmark (report.daily_benchmark), so strategy-vs-market comparisons can be done programmatically without recomputing frommarket.get_benchmark().
Fixed
finlab/core: Fix standalone strategy reports mixing Traditional Chinese and English during the initial render and across analysis sub-tabs. Locale selection now completes before the first render and consistently covers profitability, risk, risk/reward, win-rate, liquidity, trade-detail, and holdings labels, dates, pagination, empty states, and chart tooltips.data.get()/data.gets(): Fix free users continuing to receive the truncated, end-of-2018 free cache after upgrading to VIP in the same Python interpreter. Normal free-cache hits now perform a lightweight entitlement metadata recheck; when a paid role is detected, the session's old in-memory free cache is cleared, the insufficient.recentmerge path is skipped, and paid full data is downloaded directly. Existing fast paths for paid caches,prefer_local_if_exists, anduse_local_data_onlyare unchanged.cli: Much faster CLI startup.import finlabno longer imports pandas/numpy at load time (FinlabDataFramebecame a lazy attribute), cuttingfinlab --helpstartup from ~1.3s to ~0.2s;from finlab import FinlabDataFramecontinues to work unchanged.data: Removepyarrow.featherdeprecation warnings on pyarrow 24+. Data-cache reads/writes now usepyarrow.ipc(Feather V2 is the Arrow IPC format, so files are unchanged and fully interoperable across versions); backtests anddata.get()no longer emitpyarrow.feather.read_table is deprecatedFutureWarnings.
2.0.16 (2026-07-19)
Added
cli: Restore thefinlabshell command (console script). Afterpip install finlab,finlab --helpworks directly without typingpython -m finlab; also addfinlab auth login/finlab auth logout/finlab auth statussubcommands for compatibility with the old CLI interface.cli:finlab logoutis now idempotent. With no prior login it prints "Not logged in. Nothing to do." and exits 0; when credentials come from environment variables it instead explains which ofFINLAB_REFRESH_TOKEN/FINLAB_SESSION_ID/FINLAB_API_KEYto unset.
Fixed
auth: Fix a stale-credential bug where a process could keep using the in-memory id_token for up to an hour after logout (clear_session()now also invalidates the in-memory token cache).auth: Token refresh now retries transient failures (network errors and HTTP 429/5xx, up to 3 attempts with exponential backoff and shorter retry timeouts) and distinguishes a rejected refresh token (400/401/403 — prompts rerunningfinlab login) from transient server/network errors; error logs no longer echo response bodies.cli: Harden the legacy Google/Firebase login flow (finlab.cli.login): optional dependencies (flask, google-auth-oauthlib, pywebview) are imported lazily, so importing the module without thefinlab[cli]extra no longer fails and missing deps raise an actionablepip install 'finlab[cli]'message; the local callback uses a dynamically assigned port instead of hardcoded 5000; the login wait has a 180s timeout with guaranteed webview subprocess cleanup (no more indefinite hangs); missingpywebviewfalls back to the system browser; stdout redirection is restored via try/finally; a corrupteduser.txtstore recovers automatically and credential files are written with 0600 permissions.
Engineering
- CI: unit tests now run on a Python 3.10–3.13 matrix; add a packaging job (build sdist/wheel,
twine check, then a clean-venv install runningfinlab --helpandfinlab auth logoutsmoke tests). - tests: add unit tests for CLI commands, login-flow hardening, auth retry behavior, and packaging smoke tests that block optional dependencies via an import hook to simulate a minimal environment.
2.0.15 (2026-07-18)
Added
df.sector(by=...): Thesectoraccessor is now callable with a custom classification.byaccepts a dict (symbol → sector name), apd.Series, or a time-varyingpd.DataFrame(index=dates, columns=symbols); symbols not listed are excluded (NaN). Works with everysector.*method (rank,zscore,mean, ...), e.g.roe.sector({"2330": "AI", ...}).rank().df.sector.map(): Add sector-value broadcasting. Broadcasts sector-level scalars (such as sector weights) into a DataFrame with the same shape as the original data, for composing with factor scores, e.g.roe.sector.rank() * roe.sector.map({"半導體": 1.2}).df.weight.by_group(): Add group (sector) weight allocation. Each day, holdings within each group are normalized so the group total equals the specified share, e.g.selected.weight.by_group({"半導體": 0.5, "金融保險": 0.3}). Accepts the same custom-classificationbyargument assector(by=...)and adefaultshare for unlisted groups. Shares summing below 1 leave the remainder in cash, as does a group with no holdings that day; shares summing above 1 raiseValueError.backtest.sim_batch(): Add a formal native batch screening API. Pass a mapping of compatible positions and each is backtested with the same formal Cython engine assim, sharing the package/data freshness check, market dataset prefetch, and prepared market price inputs across the batch. A failing position does not cancel its siblings; each result is a versioned, JSON-safe item with provenance (schemafinlab.agent-research.v1) and structuredprogress_callbackevents are supported. Metrics for all completed items use a common evaluation window (the overlap of the strategies' input periods on the shared market-price index, exposed ascomparison_period_start/comparison_period_end) so entry-timing differences cannot skew comparisons; a batch whose valid positions share no overlapping price period raisesValueError.sim(..., metrics_only=True): Add a lightweight metrics-only path. It skips buildingReport, trades, MAE/MFE, HTML, and upload payloads, but runs the same validation, market preparation, rebalance calendar, and backtest engine, returning the sameMetricstype exposed byreport.metrics.data.get()/data.gets(): Addprogress='display' | 'event' | 'silent'andprogress_callbackfor structured download-progress events ({phase, dataset?, completed?, total?, cache_hit?, download_bytes?, elapsed_ms, timestamp}), so agents and workers outside Jupyter can avoid HTML display objects.dataframe.neutralize_industry_batch()/factor_alignment_profile(): Add batch industry neutralization and factor-alignment profiling. Factors with identical dates and symbols share category mapping, date conversion, and vectorized industry passes, and the output preserves the input container shape. Default categories use a cache tied to the active market and data lifecycle, withdata.clear_category_cache()to invalidate it anddata.category_cache_stats()to inspect hits.
Changed
data.search(): Return type changed fromlist[str]topd.Series(object dtype, name"dataset"), enabling vectorized filtering such as.str.contains(); iteration andlist()conversion behave as before.
Removed
sim()/sim_batch(): Removefast_mode. Sparse rebalance-day returns cannot produce reliable Sharpe ratios or maximum drawdowns, so existingsim(..., fast_mode=True)andsim_batch(..., fast_mode=True)calls now raiseTypeError; use the formalmetrics_only=Truepaths for lightweight screening.
Fixed
online: Fix Sinopac broker compatibility with shioaji 1.7.0, which removed thefetch_contractparameter fromlogin()in favor of on-demand contract loading. Replace the buggy_resolve_exchangemechanism (which manually guessed TSE vs OTC by trying both exchanges via snapshots) with_get_contractthat delegates exchange resolution to shioaji's own contract lookup (api.contracts.get()on 1.7+,api.Contracts.Stocks[]on <1.7). Fixes OTC stocks (e.g. 8042) silently disappearing fromget_stocks()results. Compatible with shioaji 1.3–1.5 and 1.7+.hold_until(): Fix handling of NaN exit values. The exit frame previously went through an extra.ffill(), so once a stock's exit turned True and later values became NaN (e.g. missing data), the True was forward-filled indefinitely and the stock could never re-enter. NaN exit values are now treated as "no exit signal" (matchingexit_when()); index gaps still carry the last observed exit state forward, so sparse-frequency alignment behavior is unchanged.data: Revert 2.0.14's full-table dataset bucket routing. No-colon full-table datasets such assecurity_categoriesandtw_business_indicatorsactually live in each market's item bucket, so routing them to table buckets made downloads read the wrong GCS bucket; they now resolve to item buckets again.
2.0.14 (2026-07-03)
Added
cli: Addpython -m finlab blogcommands for reading FinLab blog/tutorial content throughtop,search, andshow;show --paidreuses the Firebase auth session fromfinlab.login()for VIP content without printing or asking users to paste tokens.
Fixed
data: Fix bucket routing for no-colon full-table datasets. Full-table datasets such astw_business_indicatorsandus_fundamentalsnow use each market's table bucket, while item datasets such asprice:收盤價andus_price:closecontinue using item buckets, avoiding wrong GCS bucket reads during batch table downloads.finlab/core: Update the strategy holdings dashboard price displayed next to position P&L. Open Taiwan-market holdings now display the realtime raw market pricecinstead of a stale report-snapshotcurrentPrice; P&L / cumulative return calculations still use adjusted priceawithentryAdjPrice, preserving the split/dividend-adjusted return basis.online: Fix Masterlink odd-lot price updates so intraday odd-lot, after-hours odd-lot, and emerging-market odd-lot orders cancel the remaining quantity and recreate an odd-lot order at the new price instead of directly modifying the odd-lot order price through the broker API.report.position_info2()/CloudReport.from_cloud()/cloud_functions/report_projection/finlab/core: Fix dashboard holdings display when canonical holdings and legacy position documents are temporarily out of sync, which could show a profitable exited position as stopped out. The dashboard now prefers uploaded report holdings, including the canonical cloud read path, and avoids labeling positive regular exits as stop-loss rows when no trailing stop is configured.
Engineering
ci: Limit wheel build workflows tostablepushes or manualworkflow_dispatch, somaincommits no longer trigger cross-platform wheel builds. The regular wheel workflow also dropscp38-*fromCIBW_BUILD, matching cibuildwheel 4.x's removal of Python 3.8 support.process: Add repo-level agent instructions requiring every future commit to update bothdocs/change-log.md/docs/change-log.en.mdand thepyproject.tomlpackage version.
2.0.13 (2026-06-05)
Fixed
data/storage.py: When legacy.pickleand current.feathercache files coexist for the same dataset,FileStoragenow chooses the newest file by mtime and removes the stale sibling, preventing refreshed data from being shadowed by an older cache file.
2.0.12 (2026-06-01)
Added
sim()/hold_until(): Add thetrail_stop_activationparameter, allowing trailing stops to activate only after a position first reaches a configured profit threshold. The defaultNone/0preserves existing behavior;sim()supports both long and short positions, whilehold_until()covers the existing long-only rotation flow.finlab/core: Add a unified settings modal to the strategy analytics dashboard top bar, grouping language, light / dark / system appearance, and rise/fall color preferences (default, East red-up/green-down, West green-up/red-down).finlab/core: Standalone HTML reports embedded in an iframe now support parent-pagepostMessagesync for language, light / dark theme, and rise/fall color preferences, including querying the report's current preferences.finlab/core: Standalone HTML reports still open the built-in right-side stock panel by default inside an iframe; when loaded with?stockPanel=parent(orstockPanelTarget=parent,parentStockPanel=1), stock clicks sendfinlab:openStockPanelto the parent page so the outer app can open its own stock panel.report.to_html(): Standalone HTML reports now set the browser tab title and FinLab favicon; usereport.to_html(path, title="Strategy Name")to customize the tab title.
2.0.11 (2026-05-31)
Fixed
report.display()/report.to_html(): Fix the holdings dashboard dropping current-period positions that have already exited viastop_loss/take_profitand now have zero weight when monthly rebalancing usesresample_offset. Current-period exited positions now remain visible in the holdings table and pie/bar charts, with the table action column marking them as stopped out or profit-taken.report.display()/report.to_html(): Stopped-out / profit-taken positions now display realized report return and exit price instead of recalculating cumulative return from the latest price, preventing a stopped-out row from turning positive after a post-exit rebound.report.display()/report.to_html(): Pie and bar charts still include current-period exited positions, but chart labels no longer append stop/take-profit status text to avoid overflowing labels. The top Analysis / Holdings active tab style also removes the underline while keeping the outer border visible.
2.0.10 (2026-05-28)
Fixed
report.display(): Fix the dashboard current holdings panel dropping short positions with negative weights. Chart position sizing now uses absolute exposure, and canonical report projections preserve short holdings.
2.0.9 (2026-05-27)
Changed
data.set_market("rotc"): ROTC is now a first-class market code. It synchronizesROTCMarketand resolves generic Taiwan dataset names torotc_tables, sodata.get("price:收盤價")anddata.get("monthly_revenue:去年同月增減(%)")work directly in the ROTC context with the same strategy style as listed/OTC and other markets.data.search(market="rotc"): ROTC market search now returns onlyrotc_catalog entries, avoiding listed/OTC financial-statement and other Taiwan-market tables in ROTC discovery results.ROTCMarket.sim_datasets(): Add ROTC backtest prefetch datasets keyed bytrade_at_priceandtouched_exit, loading the required ROTC OHLCV tables sosim()works with the default market afterdata.set_market("rotc").
Tests
- Add unit coverage for ROTC market context, catalog mapping/search, and
ROTCMarket.sim_datasets(), plus live-data validation that ROTC price, monthly revenue, and overlapping Taiwan financial-statement columns can build positions and run backtests.
2.0.8 (2026-05-18)
Changed
finlab/core: Rebuild the chart / strategy analytics web component bundle (everything.js,style.css) so the 2.0.8 wheel ships the latest dashboard and chart frontend assets.report/CloudReport: Canonical report payloads now include the originalholdings/position_info2data. The legacy projection prefers canonical holdings when buildingposition2, preventing dashboard fields such ascurrentPrice,profit, andentryAdjPricefrom being lost when onlyexecution/tradesare used for reconstruction, and removes the unusedrsv20placeholder from synthetic legacy positions.
Fix
data.transform.process_data(): Fix the close-price dataset key used when aligning PMI / NMI data to trading dates. The lookup now uses canonicalprice:收盤價, avoiding escaped-unicode keys being passed into data retrieval and tests.cloud_functions/report_projection: Preserve website strategy display names. When a website-triggered cloud run uploads with the strategy UUID as canonicalsid/name, the legacy dashboard keeps the existing user-facingstrategies.<sid>.nameinstead of overwriting it with the UUID.report.position_info2(): Fix the position dashboard cumulative return / P&L basis whentrade_at_price="open"or another non-close trading price is used.entryAdjPricenow follows the actual trading price basis instead of always using the entry-date adjusted close, preventing cases such as 2492 華新科 where the table displayed a 5/13 open entry price while cumulative return was calculated from the 5/13 close, showing+3.00%instead of the return implied by the visible entry price.
Engineering
build: Backfill the stable-branch wheel build configuration to main. Add cibuildwheel GitHub Actions for Windows, macOS universal2, Linux x86_64 / aarch64, and Pyodide wheels;pyproject.tomlnow also defines the Pyodide wheel repair command so wasm wheels strip Cython source files.
2.0.7 (2026-05-12)
Changed
dataframe: Keep raisingAttributeErrorwhen external code assigns directly toFinlabDataFrame.index, and expand the error message to explain that the guard prevents financial statement / monthly revenue data from being used before it was available, avoiding lookahead bias. The message now lists safe migration paths: use.deadline(), use.index_str_to_date(), or convert topd.DataFrame(df)first when users intentionally want to manage pandas timing semantics themselves. FAQ entries now also document the recommended AssetGrowth-style factor pattern and external-data integration path.pandas: Remove Pandas 4 compatibility warnings._safe_ffill()no longer reads the deprecatedfuture.no_silent_downcastingoption on pandas 3+, while pandas 2.x checks that the option exists before using it;Report,Metrics, report projection, and marketget_freq()implementations now use pandas' recommended uppercase daily frequency"1D"to avoid'd' is deprecatedwarnings duringresample/ offset parsing.finlab/core: Rebuild dashboard and strategy analytics web component assets so the 2.0.7 wheel ships the latest report frontend bundle.
Fix
auth:FINLAB_ID_TOKENis now accepted as a canonicalid_tokenauth source, allowing Pyodide / browser runtimes to pass the new auth token through the environment; legacyfinlab.login(api_token)no longer writesFINLAB_ID_TOKEN, preventing an API token from being misrouted as a Firebase id token.report.upload(): Preserve hyphens in strategy names so website strategy UUIDs / Firestore document ids are not converted to underscores during report upload.data/storage.py:FileStorage.save_expiry()now records the actualexpiry.pklfile mtime in_expiry_loaded_time, preventing another process's expiry update from being skipped when wall-clock time and filesystem mtime differ.
Engineering
backtest/sim.py: Remove the duplicate_validate_sim_inputs()call inside_prepare_simulation_inputs(), keeping validation at thesim()entrypoint.portfolio: Stabilize the rebalance + stop trigger test by forcing the second report's next rebalance into the future, avoiding sensitivity to live data lag.
2.0.5 (2026-05-02)
Fix
finlab/core: Rebuild standalone report assets so clicking a position opens the stock panel correctly outside the embedded dashboard context. Standalone HTML reports now dispatch the stock-panel open event in the top-level page, include the close control for the panel overlay, and allow normal vertical scrolling instead of locking the report body height.
2.0.4 (2026-05-01)
Fix
backtest/sim.py: Moved_prefetch_market_datasets()out of_prepare_simulation_inputsand now call it beforesim()enters_backtest_context(). Previously prefetch ran inside theprefer_local_if_exists=Truecontext, causingdata.gets()to skip expiry checks and.recentmerges. If a sim_dataset's local pickle (e.g.etl:adj_close) had not yet been refreshed at session start, sim would silently use the stale cache andreport.creturn.index[-1]would be cut off earlier than user code'sdata.get('price:收盤價').index[-1]. Hoisting prefetch out of the context lets every dataset listed bysim_datasets()run through the normal expiry / recent merge path before backtest cache lockdown.
2.0.3 (2026-04-28)
Performance
data.gets(): Parallelize the stale-cache.recentmerge loop withThreadPoolExecutor(up to 8 workers). For a 4-dataset all-stale fetch at ~200ms RTT, wall-clock collapses from ~1030ms to ~420ms (2.4× speedup).data/auth.py,data/get.py: Unify the download path to a single GET → pre-allocatedbytearray(content_length)+memoryviewslice writes →feather.read_feather(pa.py_buffer(raw))zero-copy parse →set_dataframe_bytesverbatim persist. Drops the old stream-to-disk per-chunk callback, feather metadata validation read, and pandas re-encoding round-trip. Chunk size raised from 64 KiB to 1 MiB, cutting progress callbacks for a 100 MB download from ~1600 to ~100. Local micro-benchmarks show consistent 1.21-1.28× speedup across small / medium / parallel scenarios.
Refactor
data/get.py: Remove_download_persist_file_storage_misses,_download_file_storage_misses_to_storage,_download_persist_one_file_storage_miss,_validate_feather_file, and theisinstance(target_storage, FileStorage)branch in_materialize_full_misses. FileStorage and CacheStorage now share one_download_full_miss_frames→_persist_full_miss_framespath.data/get.py: Extract_run_parallel_tasks(keys, worker)generator, shared by_resolve_recent_candidatesand_download_full_miss_frames_threaded(samemin(8, n)ThreadPoolExecutor +as_completedpattern). Single-task case falls back to in-thread execution.data/auth.py: Delete_download_ipc_file, add_download_feather_bytes(streaming with optional progress lives here), and have_download_dataframereturn(df, raw_bytes).data/storage.py: AddFileStorage.set_dataframe_bytes(name, payload, expiry)— writes raw feather bytes verbatim, skippingdf.to_featherre-encoding. Removeset_feather_file,set_feather_bytes, and the unusedvalidate_fnparameter from_atomic_file_replace.set_dataframeandset_dataframe_bytesshare a_persist_featherhelper.- Net -213 lines, 162 related tests still green.
2.0.2 (2026-04-28)
Changed
markets/tw.py:TWMarket.sim_datasets()prefetch list now reincludes the five always-loaded TW tables (benchmark_return:發行量加權股價報酬指數,security_categories,price:成交股數,etl:is_flagged_stock,etl:market_value) so they ride a single batched request instead of trickling in via on-demand fetches during sim. This partially reverts 2.0.1's on-demand split — peak RSS rises slightly in exchange for a steadier batched-quota profile.finlab/core: Rebuildeverything.jsandstyle.cssweb component dashboard assets.
Fix
finlab/core: Throttle dashboard iframe height updates withrequestAnimationFrameand comparegetBoundingClientRect()height to preventResizeObserver/scrollHeightfeedback loops in certain layouts.
2.0.1 (2026-04-22)
Changed
finlab/core: Update web component dashboard assets.finlab/core: Bundle the Cython core and dashboard assets directly in the main repo instead of cloning thefinlab_coresubmodule.
2.0.0 (2026-04-04)
View the FinLab 2.0.0 feature introduction
Engineering
exceptions: Addfinlab.exceptionsmodule with structured exception hierarchy (FinlabError,DataError,BacktestError,BrokerError,AuthError,PortfolioError,ConfigError)backtest: Refactor 500-linesim()into 5 independently testable stage functions (_validate_sim_inputs,_prepare_price_data,_normalize_position,_execute_simulation,_build_sim_report), passing data between stages via_NormalizedPositionand_SimulationResultdataclassesdata: AddDataContextclass encapsulating 13 module-level globals into a single object, withoverride()context manager for safely overriding state temporarily, laying groundwork for parallel strategy executionoptimize: Removeeval()call incombinations.py, replace with AST-based safe condition parser to eliminate arbitrary code execution riskexceptions: Narrow 47except Exceptionclauses across 20 files to specific exception types (requests.RequestException,OSError,KeyError, etc.), improving error observability in the trading systemci: Addpytest-covcoverage threshold (fail_under=50), enforced in CI, excluding vendored/submodule coderuff: Re-enableBLE001(blind except) andC901(mccabe complexity) lint rules, with per-file-ignores for functions pending refactor
Features
data: Add pip-style real-time download progress todata.gets(), supporting multi-row concurrent updates during parallel downloads. Auto-detects environment: terminal shows ANSI colored progress bars (with speed and size), Jupyter Notebook shows HTML progress bars. Correct CJK character width alignment, zero extra dependenciespolars_frame: Add.csaccessor toPolarsFramefor cross-sectional data transforms (aligned withFinlabDataFrame.cs):rank()— percentile rankzscore()— standardizationdemean()— de-meanwinsorize(lower, upper)— percentile winsorizationbucket(n)— equal-quantile bucketingpolars_frame: Add.sectoraccessor toPolarsFramefor within-industry data transforms (aligned withFinlabDataFrame.sector):rank(),mean(),std(),median(),sum(),min(),max(),count()zscore(),demean(),winsorize(),bucket()polars_frame: Add 5 statistical methods toPolarsFrame.rolling(n):std()— rolling standard deviationvar()— rolling varianceskew()— rolling skewnesskurt()— rolling kurtosismedian()— rolling mediandataframe: Add 7 within-industry aggregation methods todf.sectoraccessor:mean()— industry meanstd(ddof)— industry standard deviationmedian()— industry mediansum()— industry summin()— industry minimummax()— industry maximumcount()— industry valid countdataframe: Adddf.weightaccessor for portfolio weight construction:cap_industry(max_weight)— cap max weight per industry, redistribute excess proportionallyclip_by_volume(total_fund, max_participation_ratio)— clip individual stock weights by average daily volumeinverse_volatility(window)— inverse-volatility weighting, lower-volatility stocks get higher weightrisk_parity(window)— risk parity weighting, equalizing risk contribution per holdingcorrelation(diversify)— adjust weights by correlation, diversify or concentratetarget_volatility(target, window)— scale weights to achieve target annualized volatilitylimit_turnover(max_turnover)— cap two-way turnover between rebalancesdrawdown_control(max_drawdown)— automatically reduce exposure when drawdown exceeds thresholddataframe: Adddf.csaccessor for cross-sectional data transforms:rank()— percentile rankwinsorize(lower, upper)— percentile winsorizationbucket(n)— equal-quantile bucketingzscore()— standardizationdemean()— de-meandataframe: Adddf.sectoraccessor for within-industry data transforms (same 5 methods asdf.cs, computed within industry)online: AddPositionStreamMixinfor real-time position streaming:- Hybrid mode: initialize via
get_position(), update in real-time via Fill events, periodic reconciliation polling (default 30s) - Add
PositionUpdatedataclass withsnapshot_key()deduplication RealtimeProviderauto-inheritssubscribe_positions()/on_position()interface
Refactor
dataframe: Extract_fetch_refined_categories()as shared helper, eliminating duplicate industry name cleaning logic betweenFinlabDataFrame._get_column_categoriesandPolarsFramesector accessorpolars_frame: Extract_build_grouped_op()and_build_sector_agg()helpers, unifying expression construction logic in_CrossSectionand_SectorOpdataframe: RefactorFinlabDataFrameinto a package (finlab/dataframe/), containingcore.py,weight.py,cs.py,sector.py, existing import paths unaffecteddataframe: Addfrom finlab import FinlabDataFrametop-level exportdataframe: Extract_get_column_categories()as shared helper, eliminating duplicate logic betweengroupby_categoryand industry weight methods
Fix
analysis: Fixcalc_shapley_valuesandcalc_factor_returnalways raisingValueErrordue to using Pythonnoton pandas boolean Series instead of element-wise~operator (#71)
Docs
market: Fix non-existent dataset nameetl:us_adj_closein docs, corrected tous_price:adj_close(#68)
Chore
- Add
polarsas optional dependency (pip install finlab[polars])
Tests
- Add 34 tests covering rolling extensions, cs accessor, sector AST nodes and expression construction
- Add 68 tests covering all new weight / cs / sector methods
- Add unit tests for
calc_factor_returnandcalc_shapley_values, verifying mask operations and input validation
1.5.13 (2026-03-22)
Features
data: Addindexparameter touniverse(),us_universe(),set_universe(),set_us_universe()for filtering US stocks by market index (S&P 500, NASDAQ 100). Combinable withsector,industry,exchange(#55, #57)market: Add Taiwan convertible bond market (TW CB Market) withto_htmlsupport infinlab_core(#64,399e789)
Fix
storage: Fix_check_and_apply_reset_timebeing redundantly checked across multiple Python processes. Use a marker file to record applied reset timestamp and expiry.pkl mtime, shared across processes. Also fix bug wheresave_expiry()failure still wrote the marker, causing subsequent resets to be permanently skipped (34c5055)analysis: FixMaeMfeAnalysis.calc_edge_ratio()ZeroDivisionErrorwhenentry_priceis zero in US market backtests, and deduplicate edge ratio computation indisplay()(#45, #66)compat: Replace deprecated pandas resample aliases ('M','Y','A') with'ME'/'YE'(pandas ≥ 2.2), centralize tofinlab.compat.MONTHLY_END/YEARLY_ENDconstants, and fix string-based version comparison bug (#63)portfolio: FixPortfolioSyncManager.from_cloud()/to_cloud()using wrong token parameter name (hardcodedapi_tokeninstead oftoken_type), and fixnotebook.pypassing tuple instead of string (#52)hold_until: FixValueErrorinhold_until()under pandas 3.0 Copy-on-Write mode (#67)report: Fixposition2not populated with trade data and incorrectentryAdjPricecalculation (d89f782)report: Fix"hold"actions not normalized in cloud report round-trip, and auto-rebuild stale Cython modules (c476cba)report: Fixupdated_atnot propagated topositionConfig.createdinCloudReport.__init__(26eb765)online: Update submodule — fixtoken_typenot routed correctly in dashboard API calls (87ecf68)data: Fix trailing delimiter inIndicatorName.decodefor parameterless indicators (87e3c80)data: Fix universe not reset afterbig_investments(), causing column mismatch in subsequent strategies (0c84806)compat: Fix_pd_versionparser failing on pandas prerelease version strings (5399d97)
Refactor
deprecation: Improve deprecation warnings to show once, extract deadline constant, deduplicate auth imports (b7f6072)
Docs
backtest: Updatesim()docstrings and fix batch order example — progress applies to remaining delta (#56)portfolio: Remove non-existentcreate_report()andget_returns()from Portfolio examples (#51)
Tests
- Fix 14 test failures across auth, projection, and E2E suites (
a194ea1)
Chore
- Bump Docker base image to Python 3.12 and update dependencies (
66a44df) - Add
twineto dev dependencies (28bb8eb)
1.5.12 (2026-03-17)
Fix
data: Fixdata.get()returning stale data in long-running sessions (e.g. Google Colab) —_loaded_datasetswas completely skipping expiry checks. Now checks local expiry time and triggers merge/download when expiredonline: Fix Sinopac broker hardcoding all stock contracts to TSE exchange, causing OTC stock real-time quote subscriptions and tick backfill to fail. Now dynamically detects TSE/OTC via snapshot API and caches results (e21f7fc)
1.5.11 (2026-03-11)
Features
report: Migrate report payload to canonical Firestore flow (users/{uid}/reports/{sid}) with cloud function projection for legacy docs (4719592)data: Adddata.get_role()anddata.is_vip()for querying user role (e282344)
Fix
backtest: Fixsim()upload using daemon thread, causing upload to be killed on script exit (9b0b4c5)data:is_tradable()now returnsFalsewhen no expiry data available (conservative: can't verify freshness → don't trade) (9b0b4c5)data:is_tradableandnext_future_expirynow only check session-loaded datasets (b16eda4)report: Fix report projection legacy parity (daily_mean,return_table,drawdown_details,trail_stop) (83a4e33,dea8afa)report: Strip time-series arrays from Firestore legacy docs to avoid 40K index limit (9af94d9)report: Fix market-close timestamp timezone normalization (91ba4dc)report: Keep cloud report schedulers and execution in sync (11ef166)report: Tighten cloud report migration validation (297de5e)storage: Make file storage writes atomic across processes (8436ab8)analysis: FixLiquidityAnalysismutatingreport.tradescausing intermittentKeyError(a14d9fb)docs: Fix i18n alternate links and redirect Location headers (784e4a1,e58b085)
Tests
- Add E2E parity test verifying report projection legacy docs match old
write_databaseoutput (5d893a3) - Tighten E2E parity test: compare stats, trades, all position fields and strategy summary (
f2e5929,d702a84) - Add firebase admin report migration test coverage (
7358a45) - Add market close timestamp normalization tests (
d747ccc) - Fix
test_data_freshnesstests to align with scoped expiry behavior (9b0b4c5)
Chore
online: Update submodule — addStock.pct_changeacross all brokers (94032d5)
1.5.9
Refactor
analysis: RemoveReportsubclass (~270 lines);to_text()andto_terminal()are now provided directly by theReportclass infinlab_core.from finlab.analysis import Reportstill works (#43)
Features
data: Add an 80% quota-usage warning sodata.get()notifies users before the quota is exhausted (#40)schemas: Addedfinlab.schemaswith formal dataclass/TypedDict contracts (PositionEntry,OrderEntry,PortfolioData, etc.)online:OrderExecutor.generate_orders()now supportsas_entriesandquantity_type, with a typed convenience APIgenerate_order_entries()portfolio:PortfolioSyncManagernow provides typed data access APIs:get_data()/set_data()andget_data_typed()/set_data_typed()
Fix
dataframe: Fix path-matching failure inFinlabDataFrame.__setattr__on Windows that caused internalset_indexcalls to be incorrectly blockedbacktest:sim()uses market-specific defaultfee_ratioandtax_ratioinstead of fixed defaults (#38)market:get_market_by_name()supports market short-code fallback (MARKET_CODE_TO_CLASS) (#37)analysis: Fix the candlestick chart spinning indefinitely when a Firestore API request fails; show an error message with a retry button instead (#23)
Docs
- Remove the git-clone install guide for the not-yet-open-source repository (#27)
- Improve
Market.get_price()docs: add full parameter descriptions, required return values, and implementation examples (#26) - Added a typed data migration guide and
symbol/stock_iddeprecation policy notes (docs/details/typed_data_interfaces.md)
Chore
- Add Claude Code GitHub Actions workflow (#25)
1.5.8
Features
verify: Addverify_strategy(), an automatic detector for lookahead biasdataframe: Forbid assigning directly toFinlabDataFrame.indexto prevent lookahead biasdataframe:rank()gains avalidparameter for filtering on valid valuesdata: Addnext_future_expiry()to automatically skip expired datadata: Add a data-freshness check to make sure data is up to date before tradingterminal: Addreport.to_terminal(), a terminal chart tool for viewing backtest results without Jupyterauth: Switch to Firebase browser login, with automatic session naming, env-var authentication, andFINLAB_API_TOKENmigration promptscompat: Fullstock_id→symbolmigration;stock_idremains available for backward compatibilitypyodide:report.upload()now runs synchronously in Pyodide environments
Performance
- Overall strategy execution speedup of 3.4x (12.9s → 3.8s, covering data loading, signal computation, and backtest)
| Step | Before | After | Speedup |
|---|---|---|---|
data.get (4 calls) |
0.40s | 0.00s | ∞ |
vol.average(20) |
0.69s | 0.62s | 1.1x |
pb.rank() |
1.57s | 1.04s | 1.5x |
rev.rank() |
1.49s | 0.50s | 3.0x |
is_smallest(10) |
5.25s | 0.40s | 13.3x |
sim() |
3.30s | 1.08s | 3.1x |
dataframe:is_largest/is_smallestswitched to numpyargsort(kind='stable')instead of pandasapply(nlargest/nsmallest), preserving consistent column-order tie-breaking (13.3x)dataframe:rank(axis=1)uses a numpyargsortfast path and ranks beforeindex_str_to_date()expansion, avoiding ranking after quarterly data (51 rows) is expanded to daily (2110 rows) (3.0x)dataframe:reshape()adds a fast path that skips reindex when two DataFrames already share the same index/columnsbacktest:sim()usesprefer_local_if_existsinternally to avoid redundant cloud expiry checksbacktest: Position normalization uses numpy array operations instead of pandas.div().fillna()backtest:uploadruns asynchronously on a daemon thread so it does not block backtest result returnbacktest: CacheTWMarket._categories_cacheto avoid repeateddata.get('security_categories')callsdata: Add_finalized_cacheto cache post-processed results fromdata.get(), skippingprocess_dataconversion on repeat callsdata: Check expiry before reading the DataFrame to avoid unnecessary disk I/Odata:FileStorage.get_dataframe()skips theos.path.getmtimesyscall on memory-cache hits
Fix
backtest: Fix a bug wheretouched_exitgap-open take-profit/stop-loss triggers multiplied position value bycr_old, inflatingcreturn. When the open price already exceeded the take-profit (or stop-loss) level, thetouch_openpath formulapos *= open_r / rdiverged fromtouch_high/touch_low; corrected topos = entry_pos * open_rbacktest: Fix floating-point drift inpdays(profitable days) calculationanalysis: Fix missingdisplay()method onAlphaBetaAnalysisthat causedrun_analysis('AlphaBetaAnalysis')to produce no outputdataframe: Fixhold_untilcompatibility under Pandas 2.0+ Copy-on-Writedataframe: Fix conversion error inis_largest/is_smallestwhen encounteringpd.NAdataframe: Replaceeval()withast.literal_eval()inindustry_rankto fix a security issuedata: Fixtruncate_endquarterly-financial truncation logic by using a disclosure-date mask, preventing accidental removal of already disclosed datadata: Fix_finalized_cachenot includinguniverseandtruncatein its cache keydata: Fix_get_expiry_dictnot filtering already-loaded datasetsstorage: Fix missing mtime cache validation when sharing across multiple processesportfolio: Fix Portfolio inconsistency when a strategy weight is zeroportfolio: Fix close-strategy incorrectly selling positions during open sync when historical data is emptyauth: Fix unnecessary browser login on failed token refreshauth: Fixlogin()not checking the cached token firstauth: Switch to a stable machine ID instead of the unreliableuuid.getnode()auth: Supportid_tokenfor backtest uploads and cloud data requestssim: Fix pandas 3.0 compatibility whentrade_at_priceis a DataFrame
Docs
- Add tutorials for
neutralize/neutralize_industry - Add complete API reference and strengthen documentation structure
- Add an introduction to the FinLab AI Skill (finlab.finance)
- Fix incorrect take-profit / trailing-stop parameter names in the docs
- Fix the erroneous description that
trade_at_price='close'is a future-function - Fix ML strategy workflow docs that referenced a non-existent API
1.5.7 (2026-01-28)
Features
data: Improvedata.search()— uses the Firestore API and simplifies output to a list of strings directly usable indata.get()data: Support US stock data search (US catalog)
Refactor
data: Improve data-fetch and storage modules; extract shared helpers for readability
Fix
backtest: Fix pandas 3.0 ArrowStringArray compatibility — ensure DataFrame columns convert to a numpy array correctlydata: Narrow the exception scope inindicator()to avoid misleading TA-Lib install messages for invalid indicator namesdata: Fix duplicate entries in the data catalogtests: Fix randomness in thecalc_regression_statstest by adding a fixed seed for stable results
Chore
- Drop Python 3.8 support; minimum required version is now Python 3.9
1.5.4 (2025-11-21)
Fix
core: FixReporterror inis_rebalance_duewhenresampleis a stringdata: FixFileStorage.get_time_expiredfailing to auto-update data when shared across multiple processes
Features
doc: Streamlined example documentation
1.5.4 (2025-11-14)
Refactor
data.universe: Refactor theuniversemodule
Fix
core: Fix backward-compatibility issue inReportwhenis_rebalance_dueis called
1.5.3 (2025-10-03)
Features
dataframe: Implement a truncation-window feature that accelerates data fetching based onfinlab.dataconfiguration
Refactor
data: Restructure the storage architecture and split modules (introducestorage.py, reshape__init__.pyanduniverse.py) and integrate the truncation flowportfolio: Streamline the trade-update flow; asset-value calculation now uses market-specific prices for greater accuracy
Fix
data.indicator: Correctadjust_pricetype validation to require booleans and improve error messageslabel:excess_over_median/meanusessubtractfor more consistent and readable arithmeticbacktest: Improveargumentsresampling logic, unifying howresample_datesandpositionindex types are handled
Docs
factor_analysis: Fix variable naming and add clearer examples; update navigation and tutorial contentdocs: Remove navigation entries that no longer exist; update example docs and mkdocs settings
1.5.0 (2025-08-01)
Features
factor_analysis: Add factor-trend analysis along with short example documentationplot: Add a unified plotly style with multiple color ranges and marker support; adds configurable color bands for line chartsplot: Enhanceplot_linewith advanced customization options and comprehensive examples; addplot_line_exampleandplot_line_financial_exampletests: Add support for serializing backtest results to pickle
Refactor
factor_analysis: Rename functions and parameters for clarity; update docs to reflect the changesdocs: Update the changelog and adjust module structure / naming
Fix
data: Adduse_local_data_onlyandforce_cloud_downloadoptionsfeature: Improve market-configuration logic to correctly restore the original market setting in multi-core environmentsliquidityAnalysis: Ensure trade-price and volume shapes stay aligned and convert them to DataFrame formatbacktest: Fix position-filter logic so only valid market data is retained and converted to floatsdoc: Fix plotly rendering issue (switch to static png)
Docs
- Add the factor-crowding analysis page to navigation
- Add simplified tutorials and refresh the practical-example tutorial for clarity
- Update the practical-example documentation
- Add KaTeX support for math rendering and remove outdated factor-crowding analysis
- Add Yuanta Securities setup instructions to the order-API docs and remove the outdated PE-PB chart image
- Fix Yuanta Securities references in the order-API docs
1.4.0 (2024-05-29)
Features
portfolio: Addget_total_positionto return current holdings as a DataFramepyproject: Addshioajisupport to the dev group
Refactor
backtest: ReplaceExceptionwithValueErrorandTypeErrorfor better error handlingmarket: Convert theMarketclass to an abstract base class and update thetwmarket
Fix
qlib: Improve error handling for a missingfinlab[qlib]module with a more detailed messagedata: Adduse_local_data_onlyandforce_cloud_downloadfeaturesdata: Update thegetreturn type toFinlabDataFramedata: Include additional exports in__all__pytorch_nn: Remove a broken import and addICLossfor information-coefficient lossportfolio: Improve the remove-position logic and tighten update conditionsdocker: Pin thefinlabversion to 1.4.0
1.3.0 (2024-05-24)
Features
online: Add pocket-account integrationfubon: Add the Yuanta Securities order-entry systemdata: Add entry names for financial-data indicatorsdataframe: Add a lookahead warning to therankmethodsetup: Add classifiers for Python 3.7 and 3.8requirements: AddJinja2andipythontest: Addpytestas a dev dependency with aslowmarker- Migrate to
uvfor dependency management
Refactor
alphalens: Remove thealphalensmodule and related featuresportfolio: Improve position-removal logs inPortfolioSyncManagerbacktest: Comment out unused global-report setup codeplot: Remove obsolete plot tests fromplot_test.pyml: Improve factory initialization and type hints in thecombinefunction
Fix
setup: Enablefind_namespace_packagesand improve path handlingliquidityAnalysis: Fix disposition-stock detection logic and the date baseline for limit-up/down evaluationdocker: Updatepandasandta-libversionsbacktest: Update the backtest-upload messageml: Fix variable reference in thecombinefunctionanalysis: Fix the end date used bydrawdownparallel: FixParallelExtcompatibility with newerjoblibversionspyproject: Pin theCythonversion for compatibility
Docs
docs: Renamegetting-start.mdand updatemkdocs.ymlorder_api: Update certificate acquisition and package installation instructions
Type Safety
core: Add type hints forFinlabDataFrameand thebacktestmodule
1.2.27 (2025-05-15)
Features
- Add the Yuanta Securities order-entry system
finlab.ml.feature.combinesupports a dictionary of functions for feature generationfinlab.dataframe.FinlabDataFrame: Refactorrankto automatically handle cross-sectional comparison on financial reports, preventing potential lookahead biasfinlab.dataframe.FinlabDataFrame: Refactorrankto warn users when lookahead is possible
Fix
- Fix qlib model reference
1.2.24 (2025-05-02)
Features
- Integrate the full qlib feature set into
finlab.ml; installingfinlabis now enough to use qlib features (pip install finlab[qlib])
Refactor
report: Supportreport.to_html(path)for exporting the report to an HTML filereport: Supportreport._repr_html_()for direct display inside Jupyter notebooks- Move
dashboardout of thereportmodule into a standalonefinlab.core.dashboardmodule
Fix
- Fix minor data errors in
report.tradesrelated to warning stocks, disposition stocks, and full-delivery stocks - Fix
report.weights.nameandreport.next_weights.namepossibly beingNone
1.2.23 (2025-03-25)
Fix
- Fix
report.uploadraising whenresample=pd.DatetimeIndex
1.2.22 (2025-03-25)
Features
PositionSchedulersupports multiple strategies and rebalance logicbacktest.simautomatically computes the next trading day whenresample=None
Fixes
- Update the macOS build architecture setting to keep only
universal2 - Fix
resampleparameter handling so it can be used directly - Adjust
PositionScheduler.from_reportto match the newresamplelogic - Update and fix associated unit tests
1.2.21 (2025-03-12)
Features
report.is_stop_triggered()detects whether stop-loss / take-profit has firedreport.is_rebalance_due()detects whether a rebalance is duereport.to_pickle('PATH')saves the report as a pickle filereport.from_pickle('PATH')loads the report from a pickle filebacktest.simbacktest-speed optimization
Fixes
PortfolioSyncManagerhandles theweight = 0casedata.getretries automatically on download errorsbacktest.simskips symbols that throw exceptions automaticallybacktest.simfixes incorrect short-return display
1.2.20 (2024-12-09)
Refactor
MarketInfois moved into thefinlab.marketmoduleTWMarketInfois moved intofinlab.markets.twand renamed toTWMarket- fix(market_info): rename loop variable for clarity in shared memory creation
- Use
finlab.config.set_marketfor setting the global market info - Remove
ml.get_marketandml.set_market; usefinlab.config.set_marketinstead - Remove
data.indicator(...market)in favor offinlab.config.set_market - Remove
df.hold_until(...market)in favor offinlab.config.set_market
1.2.17 (2024-10-19)
Features
- Rename
report.weight.nameto the backtest's last date for easier inspection - Rename
report.next_weight.nameto the rebalance date for easier inspection - Support
numpy>=2.0; note thattalibdoes not yet support it, sotalibusers must wait Positionsupports optionalweightstorage and calculation for quick comparison between lots and weightsFugleAccountswitches to websocket for order connection and updatesOrderExecutorgainsprogress, which interpolates between "current positions" and "target positions" for smoother rebalancing
Fixes
finlab.online.sinopacno longer errors when a stock has no trades for the day
1.2.16 (2024-10-02)
Fixed
data.getreduces unnecessary network callscli: Fix an unnecessarywebviewimport errorfinlab.online.sinopac: Fixget_tradesconverting timestamps to UTC when computing timesfinlab.portfolio: FixPortfolioSyncManager.to_cloudandto_jsonpossibly producing incompatible formats
1.2.15 (2024-09-08)
Fixed
position.from_report: Fixweight.indexbeing non-stringfinlab.portfolio: Fix error raised when holding untradeable stocksfinlab.online: Fix error raised when attempting to trade untradeable stocks (now raises a warning instead)finlab.data: Fixdata.searchreturning empty resultsfinlab.portfolio: Fix stop-loss / take-profit being skipped at certain moments
1.2.12 (2024-8-27)
Features
- dashboard: Add "market correlation" panel with a selectable time range
- portfolio: Add
PortfolioSyncManager.get_position(at='open')for planning the day's open positions - online.position: Improve position display
- online.position: Add the ability to iterate over position info with a for-loop
- tool.factor_analysis: Add factor-analysis features — in addition to IC, supports
get_metricfor computing various factor metrics - portfolio:
PortfolioSyncManagerauto-detects whensmooth_transitionshould kick in — no smoothing on init, smoothing afterwards - doc: Improve the
portfolioorder-placement tutorial - sinopac: Sinopac Securities supports
get_tradesfor reconciling account trade records
Fixed
- data: Fix a circular import triggered by
data.geton some operating systems - cli: Fix the
finlab nbcommand triggeringimport webview
1.2.11 (2024-8-19)
Fixed
- backtest: Fix stop-loss / take-profit occasionally using future data
- test: Use historical data when testing the backtest system
- report: Fix incorrect industry display in the backtest report
- online: Fix too many decimal places being shown when placing orders
- schedule: Fix strategy execution using the wrong token
- portfolio.cloud_report: Support
int-typed returns (used in rare cases) - backtest: Hide out-sample data when backtesting in-sample
- data: Auto-abort downloads that exceed 5 minutes with an error message
1.2.9 (2024-8-3)
Feature
finlab.portfolio:PortfolioSyncManager.updateacceptscustom_positionto set per-stock lot countsfinlab.portfolio:PortfolioSyncManager.updateacceptsexcluded_stock_idsto ignore specified stocks
1.2.8 (2024-8-1)
Fix
- Fix backtest errors in certain conditions
1.2.7 (2024-8-1)
Fix
- Fix return calculation error when stocks settle at a non-close price
finlab.portfolio: Suppress unnecessary warnings fromPortfolioSyncManager.update
1.2.5 (2024-7-29)
Feature
- Stock dashboard: Add an option to hide stock names
Fix
- Fix the stock dashboard so
open_close_avgis labeled "average price" - Fix return calculation: inter-period transitions no longer accrue trading fees
- Fix
NaNhandling in the dashboard
1.2.4 (2024-7-28)
Feature
finlab.portfolio:PortfolioSyncManagergainsto_fileandfrom_filefor loading and saving data
Fix
finlab.portfolio: FixPortfolioSyncManagererror when handling odd lotsfinlab.portfolio: FixPortfolioSyncManagerodd-lot values displayed to four decimal places
1.2.2 (2024-7-27)
Fix
finlab.cli: Fix thenbcommand erroring when not yet initializedfinlab.portfolio: Raise a more detailed error when the cloud strategy download cannot find the strategyfinlab.portfolio: Fix error raised whenlive_trading_performanceisNonefinlab.portfolio: Fixcreate_multi_asset_reportpath and return-value issuesfinlab.portfolio: Fix errors in sync manager during stop-loss / take-profitfinlab.backtest: Whenresample=None, compute returns using holding-period returnsfinlab.backtest: Fix NaN introduced by close-price settlement during intraday simulationfinlab.report: Fix exit-reason display when per-period returns are split
1.2.0 (2024-7-27)
Features
finlab.backtest: Add intraday dashboard and cumulative-return dashboardfinlab.backtest: Regardless oftrade_at_price, all settlements now occur after market closefinlab.backtest: Open-price buy/sell with stop-loss / take-profit evaluated at the closefinlab.report: Add industry category toreport.tradesfinlab.portfolio: Add a new module for applying strategies to real portfoliosfinlab.portfolio: Add tutorial documentation for using the module in real portfoliosfinlab.cli: Add a CLI module for command-line operations and order placementfinlab.cli: Add tutorial documentation for using the CLI- Add docs describing how to use regex for industry selection
finlab.online: Enable automaticFugle APIlogin without manual password entry
Fix
finlab.sinopac: Fix account-balance calculation settling too early in the early hours of Sunday
1.1.6 (2024-7-11)
Fix
- Fix offline-version data-cache reads during backtests
- Fix Windows
datetime→int32conversion not being supported in backtests
1.1.4 (2024-7-9)
Features
- Add strategy expiration time to remind users when data is stale and must be rerun
- Backtest panel: Show entry and current prices together
- Backtest panel: Reverse the default return display (most recent first)
- Backtest panel: Improve holdings-price display
Fix
- Fix
finlab.tools.factor_analysiserror caused by misaligned factor dates - Fix warning emitted under Pandas 2.2.0 when bridging to Pandas 3.0.0
ffn_core: Fix Tabular construction errorffn_core: Fix Pandas 3.0.0 compatibility- Fix incorrect entry-price display on the backtest page
- Backtest page: Fix stock history records
- Backtest page: Account for recent maximum drawdown
- Fix plot "annual average return" to handle the no-backtest case
1.1.3 (2024-6-4)
Features
- Fix
finlab.tools.factor_analysisand add multi-factor support - LINE Notify: Include the strategy name in notifications
Fix
- Fix
show_alerting_stocksnot working
1.1.2 (2024-5-23)
Feature
- Add the
finlab.tools.factor_analysismodule for factor analysis
Fix
- Fix handling of NaN in prices
- Fix multi-core processing logic in
finlab.ml.feature.tathat raised on single-core machines - Fix the reference timing for the next rebalance: use the current close-price date instead of the position-change date to avoid referencing the wrong price on the next rebalance
- Fix
finlab.ml.qlib.get_modelsmodels producinginforNaNlabels during training
Refactor
- Refactor bank-balance and settlement messaging
1.1.1 (2024-4-25)
Refactor
- Add "Required installation" and "Model library (optional)" sections to
machine_learning.mdto clearly separate install commands - Standardize and correct
market_close_at_timestampandtzinfoon theMarketInfoandTWMarketInfoclasses inmarket_info.pyfor readability and consistency - Convert some methods from dynamic to static implementation to improve efficiency and reduce dependencies
qlib.py: Handle thesegmentsparameter inWrapperModel.fitso that custom data segments are processed correctly
Fix
- Fix logical-condition error in
market_info.py, correctly distinguishing the conditional branches - Adjust NaN handling in
data.pyso that invalid data is excluded from indicator computations - Adjust the multi-core processing logic in
feature.py'stafunction based on core count to work correctly inside Docker - Fix
label.pyerrors when callingmaximum_adverse_excursionto ensure correct function behavior - Fix
label.pyerrors when callingmaximum_favorable_excursionto ensure correct function behavior
1.1.0
Features
- Add support for Microsoft Qlib by adding the pyqlib dependency to the Dockerfile, letting users run Qlib directly inside the Docker container.
Fix
- Fix inconsistent version numbers across multiple files, unifying the version to 1.0.10.
- Refactor code in
finlab/ml/qlib.py, removing outdated comments and unused code to improve clarity and maintainability. - Update the
finlab/onlinesubmodule reference to pick up the latest features and fixes. - Simplify
run_online_test.shandrun_all_test.sh, removing unnecessary comments and code for a leaner, more efficient test process.
1.0.6
- fix(report): Fix
importlib.resourcesnot working on python<=3.6, python==3.8,3.7, python >=3.9
1.0.5
- fix(report): Fix
importlib.resourcesnot working on python<=3.8
1.0.4
- feat(backtest): Add tests related to backtests and instruments
- fix(backtest): Fix the issue of removing
next_weightforce to 1 - fix(us_market): Fix support for
market_close_at_timestampsupportingNone - chore(pandas 3.0): Support the new Pandas >= 3.0.0 new resample string format
- feat(backtest): Add
sl_enterandtp_enterto mark immediate stop loss / take profit held until the next rebalance
1.0.3
Fix
- Support running backtests from the web frontend
- Fix
report.current_tradesso that when a stock name is duplicated, the latest position takes precedence - Fix the dashboard's monthly revenue display: revert from 1st-to-31st back to the previous behavior (previous month 31st to this month 31st)
- Fix
trail_stopexiting too early when buying the same stock again - When entering a position the next day while simultaneously hitting stop loss / take profit, fix the
report.actionandreport.next_weightsdisplay forstop_trading_next_period, and add unit tests.
1.0.2
Refactor
- Remove the tailwind default css and switch to the
style.cssoutput by vite - Remove the web server and switch to an iframe docsrc (verified to work on all platforms)
Fix
- Fix the web server port being unusable when displayed on docker
1.0.1
Features
- Display trailing stop loss
- Control light/dark mode from the top-right of the dashboard
- Save the dashboard to a specified path
Fix
- Fix the issue caused by the default read/write encoding of
reportbeing cp950 on Windows. - Fix stop loss not being displayed
- Adjust CSS fonts and layout on Colab
- Make chart axis colors responsive to light/dark mode
- Fix chart memory not being released, which caused delayed tab switching
1.0.0
Features
- Add unit tests for
Position.from_report - Test
report.next_weightsunder a variety of conditions - Launch the brand new dashboard interface
- Support Python 3.12
Fix
- Fix normalization of
report.next_weightswhen stop loss or take profit is enabled - Fix categorical columns not being indexed correctly by Pandas's
reindexfunction - Fix plotting errors for categorical columns when adding operators
0.5.13
Features
- Integrate Pytest (commit e7a487c): Add Pytest to make testing more efficient.
finlab.mlintraday return calculation (commit 8804fb1): Add a label for computing intraday returns.finlab.ml.feature.tamultiprocess feature computation (commit 1ab0463): Introduce multiprocess support in the feature computation module for better performance.
Fix
- Optimize the
get_locationfunction (commit 253e98c): Improveget_locationindata.pyto handle failure cases better. - Timezone support in market info (commits aa2756b & 79b3671): Add timezone handling in
market_info.py. - Trailing stop loss fix (commits 6522bf7 & e7fc1f3): Adjust trade calculations in
backtest.pyand fix trailing stop loss behavior. - Dataframe import fix (commit 626ada1): Fix the import issue in
dataframe.py. - Feature initialization fix (commit 772d659): Fix the initialization issue in
ml/feature.py. - Force cloud download (commit 7ec28a3): Fix the
force_cloud_downloadimplementation indata.py.
Other updates
- Refactor (commit 26d1518): Refactor the
onlinemodule for a better code structure. - Documentation updates (commits 44a93d4, 9f5683f, cd4a894): Update the documentation for
backtest.py,data.py, andml/feature.py. - Version update (commit d361d7f): Bump the version to 0.5.13.
- Increase test coverage (commit 350dfbc): Increase test coverage in the Docker configuration.
- Test system robustness (commit 1647479): Improve the robustness of the test system.
- Online test
Position.to_list(commit f3d086f): Updateonline_test.pyto add list-conversion coverage.
0.5.12
- Fix online test price update in
run_online_test.shto include FUGLE-configured environment variables. - Adjust
test_update_priceinonline_test.pyto compare floats correctly. - Modify
event_study.pyto work with non-trading days by reindexing against business days.
0.5.11
- Fix an error being thrown when position is zero
0.5.10
- Fix Sinopac API orders larger than 1000 NTD failing to place
- Fix the
FinlabDataframememory leak error - Fix the login UI style to avoid errors when logging in from Colab
- Fix
data.search
0.5.9
- Fix the reshape issue in dataframe operator arithmetic
- Fix
df[bool_selector]not working
0.5.8
- Improve the speed of
data.get - Improve Sinopac API order-placement efficiency (though this introduced some issues, which have been fixed)
0.5.7 (2023-11-4)
Fix
FinlabDataFrame: Fix the__hash__method to correctly use a UUID when none is set.hold_untilmethod: Fix thereindexhandling ofself,exit, andrank.market_info.get_market_info: Correct thereindexlogic to ensure the updated index is used.fetch_data: Fix the daily-average calculation in the report section.data.py: Add aUser-Agentheader to HTTP requests to prevent fetch errors.data.py: Fix saving an emptyDataFrame.ffn_core.py: Fix the divide-by-zero bug incalc_sharpe.
Features
tools: Addfinlab.tools.event_studyfor event-trading research and related features.backtest.py: Introducefast_modefor fast backtests that skip stop loss / take profit.dataframe.py: Cache theindex_str_to_datemethod for better performance.report.add_trade_info: Use Cython to speed up the computation.setup.py: Enable O3 optimization when compiling the Cython extension.backtest.py: Add documentation for thesimfunction covering the new features and parameters in detail.dataframe.py: Implement index optimization, converting string indices into dates and caching the result.- Adjust
setup.pyto change the Python requirement from >=3.7 to >=3.6. - Update submodules to their latest commits.
Tests
- Add tests for the
report_test.pymodule to guarantee correctness of the query functionality. - Add multiple test cases covering different data types in
report_test.py. - Update the
run_test_docker.shscript to include the new test cases.
0.5.2 (2023-09-28)
Fix
- Fix a recursive-import issue that appeared on a few operating systems
- Fix liquidity analysis where NaN values were being silently cast to float by Pandas
trade_at_pricefix- Fix excessive memory consumption when attempting to merge
0.5.0 (2023-09-14)
Features
- Add a trailing stop loss feature via
sim(..., trail_stop=0.1)to configure the trailing stop. data.universesupportsmarket='etf', and also categories such asdomestic_etf,foreign_etf,leveraged_etf,vanilla_futures_etf,leveraged_futures_etf.data.getsupports updating data from both US and Taiwan servers, automatically choosing the closer server.data.getautomatically detects and links Google Drive to store downloaded data.data.getnow fully supports local-side data updates: instead of re-downloading everything, patches are fetched and applied locally, and a full reset only runs when necessary (enabled by default).sim(..., trade_at_price)lets you simulate trades at other prices:'close','open','open_close_avg','high_low_avg', or'price_avg'.- Add underlying features to ensure compatibility with the new website.
Fixes
- Fix stop loss / take profit for short positions
FinlabDataFrame.hold_untilnow returnsbool(previouslyint)FinlabDataFramehas broader support for auto-reshape and fill arithmetic, e.g.&=and+=are now supported- Remove the force-configured logger
finlab.online: Fix the timezone issue when fetching total balance.data.FileStorage: Fix file-path errors on Windows.ffn: Remove soon-to-be-unsupported syntax and update to pandas 2.1 compatible syntax.- Auto-detect and convert
position.indextodatetimeinsidebacktest(position) - Fix upload failures when
report.tradesis too long - Fix a backtest bug where an instrument that exited last period was not re-entered this period
0.4.6 (2023-08-11)
Fixes
- Fix aggressive-pricing orders (
extra_bid_pct) failing when the order price exceeds the daily price limit; the order price is now capped at the upper/lower limit. - Fix the order-placement API mis-judging stop loss / take profit positions in
Position.from_report. - Fix the Sinopac API's failure to modify the price of odd-lot orders by auto-cancelling and re-placing the order.
- Change the monthly-revenue index to
datetimeformat, fixing the issue wheredata.set_storagemode forced a re-download of monthly revenue data. - Prevent NaN values from appearing in
finlab.dataframe.industry rank().
Features
- Add strategy-return statistics plotting via
finlab.plot.StrategyReturnStats. - Fugle Market Data API v1.0 is now compatible with
finlab.online; v0.3 is not supported. Users are reminded to upgrade the Market Data API from v0.3 to the latest v1.0, as Fugle will deprecate the old version. Note that v1.0 uses a different authentication scheme, so v0.3 API keys cannot be used with v1.0 HTTP and WebSocket APIs. You will need to re-apply for an API key to use the latest API; please visit the key application page. - Add aggressive-pricing (
extra_bid_pct) support to the order-placement API's "update order price" flow: buy at x% above the last trade price, sell at x% below the last trade price.
0.4.5 (2023-07-05)
Fixes
- Fix
hold_untilranking breaking when therankparameter isinf. - Fix the alphalens incompatibility with pandas 2.0.
- Fix the Sinopac API failing to place odd-lot market orders; these are now placed at the daily price limit instead.
- Fix the title display offset on the candle plot.
- Fix
report.positionbeing reused insimto produce consistent results. - Fix the edge-ratio time-series sorting; results can now be sorted in chronological order.
Features
- Add
us_universeto select US sub-industries. - Add aggressive-pricing support to the order-placement API: buy at x% above the last trade price, sell at x% below.
Doc
- Add a US-market strategy example for users to reference and learn from.
- Add documentation for the
marketparameter indata.indicatorwhen using US data, making it easier to understand how to use it.
0.4.3 (2023-05-11)
Warnings: Backtesting result and performance will change.
Features
- Improved RAM usage by removing sorting and swaplevel for machine learning features (e081c924).
- Optimized backtest report (8babbd7a).
- Implemented cross-validation for machine learning (e55828c0).
- support infomation for stop loss and take profit assets (5f9272f3).
- Hold_until functionality made available for US data (8437d62).
- Added indicators control in plot_stats (7364d58).
Fixes
- Fixed deadline considering business date for DataFrame (312f2fe1).
- Fixed the issue where a float object had no attribute 'month' (d51a5901).
- Fixed run_all_test (6203f56f).
- Prevented deletion during map iteration in backtest_core (2d1d9b05).
- Fixed login info (d177ed47).
- Fixed alpha annualize estimation in analysis (26a8becf).
- Fixed issue with short selling in online module (919521b9).
- Synced win_ratio in maeMfeAnalysis (a9a31189).
- Added rotc deadline in DataFrame (ac10597).
Refactoring
- Refactored f_disclosure_date to_business_date, removed date too far, and improved speed (7dcb203b).
- Refactored market selection according to DataFrame columns (039cbc96).
- Fixed bugs and future warnings in the analysis module (ec93088f).
- Refactored environment name capitalization in login module (635caed4).
- Refactored login to use FINLAB_API_TOKEN (a5c9064d).
- Informed free users that data is limited (bbabad14).
Tests
- Added test_hold_until (ccc4ba89).
Doc
- Updated daily_sortino documentation (b7d1d91d).
- Updated get_data documentation (276862c2).
0.4.2 (2023-04-27)
New features
- Add label trade-price support in machine learning
- Add usage documentation for
report.display()in reports - Add US market information to market info
Fixes
- Fix display-string issues in the online module
- Revert the backtest feature to a stable version
- Adjust how the backtest feature updates holdings based on entry/exit prices
- Fix detection of
qlib.init - Fix benchmark-index timezone in reports
- Handle pandas 2.0.0 compatibility issues
- Handle the talib dependency issue
- Fix entry/exit ratios becoming NaN in the backtest feature
- Fix the
fugle Account numpyreference in the documentation
Other
- Add related docs in the machine-learning module
- Add related docs in the market-info module
0.4.1 (2023-04-14)
- (fix): fugle np not found
0.3.19 (2023-04-04)
- (improve): memory usage in machine learning feature creation
- (improve): machine learning feature for different freqnecy index
- (fix): data.indicator support almost all talib functions
- (fix): 2023/4/10 monthly revenue reveal change to 2023/4/12
- (fix): sinopac calculate account balance
0.3.18 (2023-03-28)
- (Breaking): update shioaji >= 1.0.0. Please refer to finlab.inline module
- (feat): finlab.ml (document are not ready yet)
- (feat): support qlib data
- (feat): report add yearly and monthly return table
- (fix): backtest entry exit signal both activate conflict
0.3.12 (2022-11-29)
- (Breaking): report.get_stats() has new format
- (feat): data.get now read expire date from server and update data dynamically
- (feat): add a bunch of analysis for backtesting report
- (feat): getpass password to hide api_token
- (fix): report.weight and report.next_weights remove considering of price changed
- (fix): backtest(..., resample_offset='10d') fix backtesting display date
- (fix): finlab.online.order_executor show_alerting_stocks fix price
- (fix): fugle order status not change even when it is filled
- (fix): support update price of fraction lot size
0.3.11 (2022-11-10)
- (Fix) finlab backtest asset shorting fee miner adjust
- (Fix) finlab.report.weights and finlab.report.next_weights not defined
- (Fix) fugle-trade remove flag accoding to 0.4.0
- (Fix) fugle-trade fetch other source when stock.close is nan
- (Fix) fugle-trade FugleAccount.get_position not latest position
- (Feat) finlab.data.universe('STOCK_FUTURE') subset of TWII stocks
- (Feat) add yearly return when upload report
- (Feat) add fugle_trade version not compatable warning
0.3.10 (2022-10-13)
0.3.9 (2022-10-13)
- (Fix) finlab.online.sinopac_account order_lot position zero
- (Fix) finlab.online.fugle_account order_lot position duplicated
- (Fix) finlab.backtest weight display remove normalized
- (Fix) finlab.data windows FileStorage fix
- (Fix) finlab.data indicator exception error
0.3.8 (2022-9-27)
- (Fix) finlab.online.order_executor price +-10% limit order restricted
- (Fix) finlab.online.order_executor Position.from_report handle price=0, weight=0, and duplicated stock id
- (Fix) finlab.plot.StrategySunburst compatibility
- (Fix) finlab.report.display display multiple chart
0.3.8.dev1 (2022-9-27)
- (Fix) talib abstract module incompatible
- (Fix) backtest result: alpha and beta
- (Fix) finlab.online.panel
- (Fix) finlab.online.fugle_account stock not traded error
- (Fix) add cert custom password for sinopac
- (Fix) remove duplicated columns from unnumeric_dtype.
- (Fix) KY company disclosure deadline
- (Chore) Candlestick data format generalized
- (Chore) raise error when data.get not found
- (Chore) logger.info to logger.warning in finlab.data
- (Chore) fig return for report.display
- (Feat) add retain_cost_when_rebalance flag
0.3.7.dev1 (2022-8-8)
- More granular backtesting for
touched_exit: - Previously, a stock that touched the target and bounced back would not be marked as a touched sell; now it is simulated as a touched sell.
- Previously, a gap-down open meant the stock could not be sold at 0.07; now it will also be sold at the lower level.
0.3.5.dev (2022-7-18)
- Add
finlab.optimizefor enumerating parameter combinations in backtests - Add the
mae_mfechart mae_mfeshort-side analysis- Rename
report.get_trades()['position']toweighting backtest touched_exitfix- Fix bugs in
finlab.analysis
0.3.3.dev (2022-7-1)
Bug fixes
- Fix the weight display for short positions
- TreeMap plotting fix
- Fix historical financial-report publication times
Analysis — analyze backtest results
Add an Analysis framework that can fully analyze backtest results.
Future analyses will inherit from Analysis and render charts, making reports richer.
liquidityAnalysis — liquidity test
A strategy simulates buying and selling at the close or open price, but in reality a stock may not trade successfully — for instance a disposition stock, a fully-delivered stock, or a prolonged limit-down. liquidityAnalysis checks whether any such instruments were bought during the backtest and computes their proportion, so you can ensure the strategy's liquidity matches your capital requirements. See the tutorial article for more detail.
inequalityAnalysis — inequality test
If you already have a strategy and want to add additional technical, chip, or profitability indicators, inequalityAnalysis lets you inspect whether they improve stock-selection performance.
For example, to look at how the price-to-book ratio further filters stocks, use:
Refer to the tutorial article for how to read the results.
0.3.2.dev (2022-6-23)
SunBurst — inspect strategy positions
Display the industries represented in the current holdings and which industry is weighted most heavily. See details!
Short selling and per-stock weights
The FinLab Package now supports short selling. If you already have a good strategy, try shorting 0050 — returns may drop slightly, but risk shrinks substantially, which is great for allocating capital across multiple stocks.
Also worth noting: the new version allows position to use weights in addition to the previous boolean signals (True/False). So in the code below, 2330 is longed at 50% weight and 1101 is shorted at 50%.
This weighting scheme is fully backward-compatible with the previous True/False position style, because True is treated as 1 and False as 0 in computation. When all weights are 1, the program allocates capital evenly across assets as before.
from finlab import data
from finlab import backtest
close = data.get('price:收盤價')
position = close < 0
position['2330'] = 0.5
position['1101'] = -0.5
r = backtest.sim(position)
r.display()
Pandas_ta support for technical indicators
Pandas_ta has emerged over the last couple of years as a strong alternative for computing technical indicators, largely because TaLib can be quite hard to install and ships with a fixed set of roughly 100 indicators that rarely get updated. In contrast, Pandas_ta computes indicators in pure Pandas, which plays well across operating systems. It also covers many of the newer indicators, such as the supertrend indicator, along with less-common ones like "Even Better Sinewave", which sounds pretty interesting. You can find all of them in the Pandas_ta docs.