Thinking global, living local

How to use piotroski-liquidity-research: a point-in-time test of the Piotroski F-score and liquidity

October 19, 2026

piotroski-liquidity-research asks whether the Piotroski F-score premium is explained by firm size or by stock liquidity, using point-in-time backtests. Its headline is that the premium tracks illiquidity, with a capacity of about $250,000. I re-ran its US scripts on 23 October 2026 against a US price panel whose last session is 21 October 2026 and SEC EDGAR filings filed up to 20 October 2026. Not investment advice. Educational and research use only; historical averages on short samples, and the repo's own numbers are provisional.

Open-Source Tools · How-To · Markets research

How to use piotroski-liquidity-research: a point-in-time test of the Piotroski F-score and liquidity

Skip to article content
Text Size

Revised · v1.0.0 · what changed

16,333daily Darvas x F-score signals in the 23 Oct 2026 re-run (was 12,773)
1,802US tickers in that re-run (was 636)
15.0%of average daily volume at the repo's ~$250k capacity ceiling, re-measured
11derived metrics the repo documents as having failed validation

Code: github.com/herrrickshaw/piotroski-liquidity-research

Watch the walkthrough

Watch on YouTube: youtu.be/m8uKeoLRaBo

Why it is useful

  • Who benefits: students and independent researchers who want a worked, honest example of a point-in-time factor study, including the data bugs found along the way.
  • What it replaces: guessing whether an F-score screen survives size and liquidity controls; the repo ships the scripts, the paper and the result tables.
  • Evidence from the run: the US scripts (completeness audit, daily Darvas x F-score scan, weight-preset sweep, cost-versus-edge) all ran end to end on 23 October 2026 and regenerated their result files in under a minute each.
  • Evidence from the run: in the regenerated daily signal file (16,333 signals, 1,802 tickers) the median 12-month forward return rose from -2.8% for the lowest-turnover tier to +8.9% for the highest, the same direction as the committed file (-3.1% to +8.2%). Almost all of those signals fall in 2023 to 2026, so it is a short, regime-dependent window.

What it is for

The repo studies one question: is the Piotroski F-score premium conditioned by firm size or by liquidity? It double-sorts liquidity within size, uses point-in-time market cap and SEC EDGAR filing dates, keeps delisted names, and reports medians rather than means.

Its headline table says the premium is largest in large but illiquid firms and comes from weak-F names falling, not strong-F names rising. It then estimates execution costs and puts a capacity ceiling near $250,000.

The README opens with a warning that its own figures are being regenerated after data defects were found, so treat every number as provisional. That candour is part of what makes it worth reading.

The scripts need an EDGAR filings table and a US price panel that the repo does not ship. For this post I re-ran the US scripts on a US price panel (last session 21 October 2026) and an EDGAR filings table (filed up to 20 October 2026), with the hard-coded file paths pointed at those copies in a scratch clone. The EDGAR table is dense only from fiscal 2021, so the re-run covers roughly 2023 to 2026, not the repo's 2016 to 2025. I did not use the India scripts or file, which derive from screener.in data.

Who it is for

  • Students learning point-in-time backtesting, survivorship handling and why medians beat means
  • Researchers who want a template for liquidity-conditioned factor tests
  • Anyone sceptical of factor claims who wants to see the validation failures documented
Not for.
  • Investment advice or trading signals. Not investment advice.
  • Reproducing the headline tables without the EDGAR and price panels the README lists
  • Trusting any number as final: the README says the tables are provisional

What you need

RequirementWhyHow to get it
Python 3 with pandas, numpy and duckdbthe research scripts read Parquet through DuckDBpython3 -m venv .venv && pip install pandas numpy duckdb
A US price panel and an EDGAR filings table, both Parqueteach script has FUND and PX constants at the top that point at the author's machine; edit them. PX needs Symbol, Date, Open, High, Low, Close, Volume; FUND needs ticker, fy_end, filed, net_income, cfo, total_assets, current_assets, current_liabilities, long_term_debt, shares, revenue, gross_profit, ebit, equitybuild them from your own data; the repo does not ship them. Note the sweep script expects a column called equity
Reading time for the paperreports/PIOTROSKI_LIQUIDITY_PAPER.md holds the method and limitationsopen in any Markdown viewer

Get the code and an AI assistant

You do not have to be a programmer. Get the repository, give it to an AI assistant, and ask in plain language. Any of these routes works:

ToolWhat you needBest for
GitHubFree account to fork or star; git to clone, or the green Code button, then Download ZIP, or open in CodespacesGetting the code and keeping up with updates
Claude CodeClaude subscription or API key; run claude inside the cloned folderLetting the AI read and run the whole repo
Gemini CLIGoogle account; run gemini inside the cloned folderThe same hands-on workflow with Gemini
Claude.ai, Gemini or ChatGPT in the browserA chat account; upload README.md and the files you care about (or paste the output of git ls-files)Understanding the project and asking questions without installing anything
Other tools (Copilot Chat, Cursor, Codex CLI)Open the cloned folder in the tool and point it at README.md firstEditing and extending the code
git clone https://github.com/herrrickshaw/piotroski-liquidity-research.git
cd piotroski-liquidity-research
claude      # or: gemini

Install

  1. Clone the repo and make a virtual environment.
    git clone https://github.com/herrrickshaw/piotroski-liquidity-research.git
    cd piotroski-liquidity-research
    python3 -m venv .venv && source .venv/bin/activate
    pip install pandas numpy duckdb
    # then edit the FUND and PX constants at the top of each US script

Quick start: commands and what you should see

Check the sample-size claim before trusting anything (run this first, as the README says).

python3 data_completeness_audit.py

One line: fundamentals tickers, price tickers and their overlap. On 23 Oct 2026: 6,897 | 10,110 | 6,889 (100% / 68%).

Regenerate the daily Darvas x F-score signal file and the tier table.

python3 daily_breakout_combo_us.py

About 40 seconds. Prints signal counts, a tier table and a per-year table, and rewrites reports/daily_breakout_combo_us.csv.

Run the weight-preset sweep, then the cost test that reads its output.

python3 sweep_piotroski_plus_us.py && python3 cost_vs_edge.py

About 15 seconds each. The cost test prints net edge by tier at ten portfolio sizes.

Sample outputs

Real output from a run on the date shown in the sources line; trimmed to fit.

Data completeness audit (23 Oct 2026; US price panel last session 2026-10-21)

python3 data_completeness_audit.py
=== source reconciliation ===
  fundamentals  6,897 | prices 10,110 | overlap  6,889  (100% / 68%)

The committed script comments quote 9,278 price symbols and 4,459 overlap (97%). The fundamentals table here has 6,897 tickers, but most fiscal years before 2021 are sparse, so tickers overlap does not mean years of history overlap.

Daily Darvas x F-score scan, by liquidity tier (regenerated; winsorised median minus same-tier baseline)

python3 daily_breakout_combo_us.py
  raw first-cross breakouts: 471,700
  after 10-bar cooldown: 201,749 signals, 7,960 symbols
  signals WITH point-in-time fundamentals: 16,333 (1,802 symbols, 11 years)

  tier     base med    darvas      n   darvas×F7     n  darvas×F<=3     n
  SMALL        0.1%     -2.9%  5,445       +6.6%   850       -14.9%  1076
  MID          2.9%     +3.3%  5,444       +7.9%  1695        -7.7%   426
  LARGE        6.8%     +2.1%  5,444       +2.7%  1984        -6.5%   248

By signal year: 4 in 2016, 16 in 2017, 5 in 2018, 17 in 2019, 31 in 2020, 31 in 2021, 127 in 2022, 4,059 in 2023, 5,887 in 2024, 5,800 in 2025, 356 in 2026. Effectively a three-year sample, and signals cluster in rallies.

Median 12-month forward return by tier, then by tier and F-score band (re-aggregated from the regenerated file)

groupby('tier') and pd.cut(F9,[-1,3,6,9]) on reports/daily_breakout_combo_us.csv
          n     med    mean      sd
LARGE  5444  0.0894  0.2146  0.6170
MID    5444  0.0619  0.1774  0.6074
SMALL  5445 -0.0280  0.1438  0.7874

        F0-3     F4-6     F7-9
LARGE   0.0036   0.0855   0.1018
MID    -0.0487   0.0565   0.0882
SMALL  -0.1480  -0.0452   0.0609

The committed file showed no clean F-score gradient (for example SMALL -1.2%, -3.8%, -2.1%). In this 2023 to 2026 window every tier rises from F0-3 to F7-9. Cells range from 248 to 3,149 rows; 252-day windows overlap.

Weight-preset sweep, US (regenerated; 9 rebalances, but only 4 with forward returns)

python3 sweep_piotroski_plus_us.py
  fundamentals: 15,654 ticker-years | 3,865 tickers
  scored panel: 7,548 stock-years | 2,926 symbols | 9 rebalances

  vector         mean  median  vs univ  beat univ   worst
  [universe]    34.4%   15.0%       --         --  -32.6%
  canonical     29.4%   28.1%   +14.8%    4/4       13.0%
  quality       30.1%   28.1%   +15.6%    4/4       16.0%
  turnaround    24.2%   21.1%   +9.7%    3/4       11.6%
  safety        28.4%   28.1%   +13.9%    4/4        9.4%

  tier         n  F>=70 med  F<40 med  med edge
  SMALL    2,518       1.2%    -25.4%    +26.5%
  MID      2,516       5.5%      5.4%     +0.1%
  LARGE    2,514      13.9%     19.9%     -6.0%

Not comparable with the committed sweep (which had 3,294 observations): different universe, and only four rebalance years. The universe mean (34%) far above its median (15%) points to outliers, probably unadjusted corporate actions in the price panel, so read medians only. The small-tier edge is the opposite sign to the large tier here, the pattern Piotroski's original paper predicts, per a comment in the script.

Does the small-cap edge survive costs? (regenerated cost test, 2,923 symbols; prices through 2026-10-21)

python3 cost_vs_edge.py
  tier       n   turnover/day    spread   impact per $1M
  SMALL    975          $0.2M     1.07%          47.312%
  MID      974          $5.8M     0.48%           0.648%
  LARGE    974         $75.7M     0.23%           0.026%

  $    100,000  cost   2.01%/yr  (  6.0% of ADV)  best gross +13.3% -> net  +11.3%  SURVIVES
  $    250,000  cost   3.43%/yr  ( 15.0% of ADV)  best gross +13.3% -> net   +9.9%  SURVIVES
  $    500,000  cost   5.80%/yr  ( 30.0% of ADV)  best gross +13.3% -> net   +7.5%  SURVIVES
  $  1,000,000  cost  10.53%/yr  ( 60.0% of ADV)  best gross +13.3% -> net   +2.8%  SURVIVES
  $  2,000,000  cost  19.99%/yr  (119.9% of ADV)  best gross +13.3% -> net   -6.7%  DEAD

Two things. The README's capacity ceiling of about $250k is consistent with these costs (15% of daily volume at $250k). But the script defines a 20%-of-volume ceiling (ADV_CEILING_PCT) and never applies it, so it still prints SURVIVES at $500k and $1M, where positions are 30% and 60% of daily volume. I saved a patch (not pushed). The gross edges used are constants hard-coded in the script from the earlier sweep, not recomputed.

Ways to use it

MethodHow it works
Read the paperreports/PIOTROSKI_LIQUIDITY_PAPER.md and CAVEMAN_SUMMARY.md give method, results and limitations.
Re-run on your own dataPoint FUND and PX at your EDGAR and price Parquet files, run the audit first, then the daily scan, sweep and cost scripts.
Adapt the scoringpiotroski_plus.py holds a canonical 9-point F-score plus a 3-point ROCE block with tunable weights.
Colabnotebooks/colab_test.ipynb is linked from the README (not run).
Via an AI assistantAsk an assistant to explain the method and audit the caveats before you extend it.

Sample prompts for your AI assistant

Copy, paste and edit the parts in [brackets].

  • Read README.md and the paper in reports/ and explain, in plain English, why the author says the premium tracks illiquidity rather than size.
  • Using reports/daily_breakout_combo_us.csv, compute the median and win rate of wret by tier and by year and tell me which results are most regime-dependent.
  • List every data defect the README says was found, and for each say whether it is fixed and which results it could change.
  • Explain why the README treats median as the headline and what overlapping 252-day windows do to the effective sample size.
  • Write a checklist I could apply to my own backtest from the 'Metrics that failed validation' table.
  • Suggest what extra controls (beta, book-to-market) would be needed before treating this as evidence, and what data I would need.

Limits and honest caveats

  • Not investment advice. Educational and research use only.
  • The README states that numbers in reports/ are provisional and are being regenerated after eight defects were found; one (lender EBIT and ROCE) is documented but unfixed. My re-run does not settle any of them.
  • Data freshness: US prices run to the 2026-10-21 session and EDGAR filings to 2026-10-20, but the price panel is patchy: on several recent days only 2,000 to 3,000 of roughly 7,000 US symbols have a bar (for example 15 and 16 October). Anything that needs a clean daily history should be checked with the audit first.
  • The EDGAR table I used is dense only from fiscal 2021 (about 4,700 tickers in 2021 versus a few dozen per year before 2020). So the re-run is a 2023 to 2026 test with 98.6% of signals in those four years, not the repo's 2016 to 2025 sample, and the results are not comparable one-for-one with the committed tables.
  • Power is low and windows overlap: the sweep has only four rebalance years with forward returns, no standard errors are reported, and outliers (mean 34% versus median 15% in the universe) suggest unadjusted corporate actions. The India scripts were not run and the India file is not shown because it derives from screener.in data.
  • The cost test's gross edges are hard-coded constants, and its 20%-of-volume ceiling is defined but unused (patch saved, not pushed).

Sources: README, paper and scripts in github.com/herrrickshaw/piotroski-liquidity-research, re-run on 23 October 2026 in a throwaway virtual environment against a US price panel (last session 21 October 2026) and an SEC EDGAR filings table (filed to 20 October 2026). Underlying filings are SEC EDGAR public data; only derived aggregates shown; no vendor data.

Revision history.
  • v1.0.0 — 23 October 2026 — first published.

About this article: Researched, written and edited by Umashankar Triplicane Dwarakanathan, with AI research assistance; every figure is meant to trace to the primary source cited. See the Editorial Policy for how sourcing, AI use and corrections work.

Umashankar Triplicane Dwarakanathan
Contact Us
Umashankar Triplicane Dwarakanathan
Investment Promotion & Energy-Sector Leader · Chennai, Tamil Nadu, India
LinkedIn → GitHub → Email +91 78273 81696
How this site works

Data-led analysis of India's trade, currency and industrial policy. Every article is built from primary official sources, and every figure links back to the release, table or filing it came from.

Sources. DGCI&S TradeStat (imports/exports, HSN-wise) · PIB (government press releases, January 2017 to today, refreshed daily) · RBI (circulars, balance of payments) · MoSPI (CPI/WPI, IIP) · PARIVESH (environmental clearances) · CCIL (bond yields) · BIS (policy rates) · SEBI, NSE/BSE and SEC filings for company data.

Interpretation. Figures carry their vintage and retrieval date; estimates and press-reported numbers are labelled as such; where sources disagree, both are shown. Corrections are made visibly, never silently. Articles are written with AI assistance from the cited sources — AI-generated text can misstate figures even when working from real material, so verify any number that matters to a decision against the linked primary source.

footer

Browse all articles by topic

Every piece on this blog, grouped. Or read the full index.

Agriculture & FertilisersAI ToolsChemicalsClimate & CarbonEnergy & FuelsGas & LNGImport SubstitutionIndustrial PolicyMarkets & FinanceMobility & EVPrices & InflationTextilesTrade & Tariffs

Each topic is a live archive page that updates itself as pieces are labelled. It replaces a hand-kept list that had fallen 18 articles behind.