Straud — Instruction Manual

The reference. Every option, every finding explained, and the things you only need once you are past the first run. Use the contents table to jump to the section you need.

Contents

  1. Preparing your data
  2. The most important number: the trial count
  3. Reading the report
  4. Four ways to use Straud
  5. Telling it more (optional, but worth it)
  6. Sealed holdouts
  7. The ledger (counting trials automatically)
  8. Checking a data file (not a strategy)
  9. Checking a whole portfolio
  10. Every finding, explained
  11. Troubleshooting
  12. Security notes
  13. The scope
  14. Glossary

Straud works with results from any tool — vectorbt, backtrader, Lean, TradingView, MetaTrader, or a script you wrote yourself. If you can export a list of trades, you can use it.


Preparing your data

One row per trade. One column of returns. Everything else optional.

Here is the example file:

date symbol gross_return net_return signal_strength entry_price
2024-01-02 SYM25 0.015220 0.012720 −1.4768 53.47
2024-01-02 SYM18 0.000332 −0.002168 −0.3527 45.27

What each column unlocks:

Column Flag Why it helps
net return --column Required. Return after costs.
date/time --timestamps Era consistency; how much history you gave.
date/group --cluster The most valuable optional flag. See below.
gross return --gross Cost sweep: how much cost the edge can survive.
signal --signal Look-ahead check.
price --price Sanity checks for stock strategies.
daily volume --adv-column Capacity: how big you can trade.

The cluster column — read this one

If several of your trades happen at the same time or on the same day, they are not independent. A market-wide move hits all of them together, so twenty trades on one day carry closer to one day's worth of information, not twenty.

If you ignore this, your results look far more certain than they are.

Use whatever column identifies the shared moment — usually the date:

straud audit examples/example_trades.csv --column net_return --cluster date

Rule of thumb: if two rows could be affected by the same piece of news, they belong in the same cluster.

The entity column — read this one too

The cluster column answers when. It says nothing about who.

Those are different problems, and fixing one does not fix the other. A study can be perfectly clustered by date and still have one symbol firing fifty times and carrying the whole result.

This is not hypothetical. A real campaign produced this:

grouped by units mean median t
date 49 +192bp +60bp 2.62
symbol 30 +48bp −18bp 1.33

Same 243 events. Clustered by date it clears the usual bar and gets promoted. Clustered by symbol there is no edge at all — two symbols carried 96% of the profit. Only the date grouping had been checked.

So pass both:

straud audit trades.csv --column net_return --cluster date --entity symbol

Straud then reports n events, n dates and n entities, each entity's share of the profit, and whether the two groupings disagree about whether you have anything. If the top one or two entities carry more than half the winnings, your event count is fiction.

If your universe is narrow, expect Straud to be quieter here. A t-statistic grows with the number of groups, so an entity axis with 60 symbols loses significance against a date axis with 500 dates purely because it has fewer groups — not because anything is wrong. Crypto perps (~120 tradeable names) and FX (~30 currencies) sit in exactly that position. So Straud does not report a disagreement on significance alone: it requires the entity-level effect to actually be smaller, and to be smaller in the shape concentration produces — a few names pulling the average up above the median. An average dragged below its median is a few bad names in a small sample, which is noise, and it is left alone.

It also separates two things that look identical in the numbers:

Add --timestamps and --entity-window-minutes to also catch the same name re-entered before the previous position closed, which inflates your event count directly.


The most important number: the trial count

--trials is how many strategy variants you tested against this data before choosing this one. Every variant. Including those discarded.

This matters more than anything else you tell Straud.

Try 400 combinations of settings on pure random noise, keep the best one, and it will look excellent. That is not a flaw in your idea — it is arithmetic. The best of 400 coin-flip sequences always looks impressive.

Count everything:

If you are unsure, guess high. Guessing low is the mistake that costs money.

Straud cannot verify this number — it is the one input it must take on trust. So instead it tells you what your number is worth:

This result survives up to ~94 tested variants.

If the true count is higher than that, the result is not supported. You do not need to be honest with Straud; you need to be honest with yourself, and that sentence gives you something concrete to be honest about.

If you do not know at all, use a ledger — it counts for you.


Reading the report

The verdict

Verdict Meaning
Fatal Something is definitely wrong. Do not trade this.
Unproven No critical finding, but the evidence does not support the claim. This is the normal result for most strategies.
Conditional Real signal, but it depends on assumptions that probably will not hold.
Survives Passed everything Straud could test.

The strongest claim available is "survives the tests we ran" — which is why the "what we did NOT test" section is part of every report.

Getting Unproven is not failure. Most strategies are unproven most of the time. The tool is doing its job.

Findings

Every finding has two separate labels:

Severity — how bad if true

🔴 Critical Stop.
Serious Fix before risking money.
Caution Know about it.
· Note For the record.

Confidence — how sure Straud is

Confirmed It ran the test; the test failed.
Likely Strong indication, but an innocent explanation exists.
Possible A flag that needs your knowledge to resolve.

Two labels, not one, so Straud can raise a worry without overclaiming.

Why a finding sometimes says "Reported at Serious rather than Critical"

Straud's checks make two different kinds of claim, and they do not carry the same weight.

Some checks measure the thing you are claiming, or establish a fact. A placebo cutoff doing as well as your real one. An assumed cost below the venue's published fee. A trial-adjusted Sharpe of 0.001. There is no gap between what these measure and what they mean, so one of them alone is enough to condemn a result.

Other checks spot a pattern and infer a cause. Those can be right about the measurement and wrong about the reason. A real example: Straud once saw that grouping by date and grouping by symbol disagreed, and concluded the result was fake. The measurement was correct — but the true cause was that the edge lived in symbols that recur, which is a useful finding, not a critical one.

So a pattern-based check is not allowed to condemn a result on its own. If nothing independent agrees with it, it is reported at Serious instead of Critical, and it says so in the evidence. If something independent does agree — another confirmed finding, from a different check — the inference stands and Critical is kept.

This costs nothing in detection. On Straud's own corpus of planted defects, every single one that reaches Critical does so through a fact-based check, so none of them are affected.

Questions

Some findings ask you something:

Were these shortable at signal time? If not, this becomes Critical.

Straud does not know your market. When it cannot check something itself, it asks rather than guessing — and tells you what the answer would change.

Which version am I running?

straud --version

Worth knowing, because Straud's checks and thresholds change between versions: the same data can produce a different verdict under a different build. Every report records the version that produced it, so an old result can always be traced back to the code behind it. CHANGELOG lists what changed.

Exit codes (for scripts)

Code Meaning
0 Nothing at or above your failing level
1 Something at your failing level
2 At least one Critical finding
straud audit results.csv --column net --trials 50 || echo "needs attention"

Choosing how strict the gate is

How much severity should stop your pipeline is a risk decision, and it depends on what you are doing. Exploring a new idea is not the same as signing off a strategy that will trade real money. So it is yours to set:

--policy stops on use when
strict Caution and above live capital, or a result someone else relies on
standard (default) Serious and above normal work
lenient confirmed Critical only early exploration, where you want the findings but not the gate
straud audit results.csv --column net --trials 50 --policy strict

The report does not change. The same findings, the same evidence, the same remedies are printed under all three — only the exit code moves. The policy decides what stops a pipeline, not what you are told.

Two things it deliberately cannot do:


Four ways to use Straud

Pick one. They do the same thing.

Best for Needs Python?
A. Standalone download Trying it, or never touching Python No
B. Command line Regular use, Parquet files Yes
C. Python library Your backtest already runs in Python Yes
D. AI assistant Researching with Claude or similar Yes

A. The standalone download (easiest, no Python)

The simplest way to run Straud. Everything is inside one download — no Python, no pip, no setup.

  1. Download the zip for your system (e.g. straud-0.1.0-darwin-arm64.zip).
  2. Unzip it anywhere.
  3. Open a terminal in that folder and run:
./straud audit examples/example_trades.csv --column net_return

On Windows: straud.exe audit examples\example_trades.csv --column net_return

Then point it at your own file:

./straud audit /path/to/your_results.csv --column net_return --trials 40

Every flag in this manual works exactly the same way — just write ./straud instead of straud.

Three things to expect

The first run takes 15 seconds to about a minute. Every run after that is under a second. The folder holds ~140 code libraries and your operating system verifies each one the first time an unrecognised program runs. The result is then cached. Measured on an M-series Mac: 13–45s first, 0.37s after.

Get it over with before you need it by running the example once:

./straud audit examples/example_trades.csv --column net_return

./straud --help is not enough — it loads only a small part of the program, so your first real command would still be slow. Measured: --help first at 13.2s, then a real audit still cost 6.2s. Running the example audit first warms everything, and subsequent commands take ~0.5s.

Your system may warn that the developer cannot be verified. This build is not signed with a paid developer certificate. To allow it:

This build reads CSV files only. Parquet support needs the Python package (pip install 'straud[parquet]'). Straud will tell you this plainly if you point it at a .parquet file.

Why it is a folder rather than one file

A true single file has to unpack itself on every run, and because the build is unsigned your operating system re-verifies it each time — measured at 12 to 42 seconds per command. The folder version pays that cost once and then starts in about half a second. You still download one zip; only one of the two is pleasant to use afterwards.


B. Command line (pip-installed)

Best if your results are in a CSV or Parquet file and you have Python.

Minimum:

straud audit results.csv --column net_return

Realistic:

straud audit results.csv \
  --column net_return \
  --trials 120 \
  --cluster date \
  --timestamps date \
  --gross gross_return \
  --cost 0.0025

Save an HTML report you can read or send:

straud audit results.csv --column net_return --trials 120 --html report.html

The report is titled after the input file. Give it a clearer name with --name, which is worth doing whenever you will keep the report:

straud audit results.csv --column net_return --trials 120 \
  --name "reverse-split fade, 10-day hold"

Machine-readable output:

straud audit results.csv --column net_return --json

C. Python

Best if your backtest already runs in Python.

from straud.audit import audit

report = audit(
    returns,                 # your per-trade returns
    name="My strategy",
    n_trials=120,            # variants tested, including discarded ones
    cluster=dates,           # optional but valuable
    timestamps=dates,
    gross_returns=gross,
    assumed_cost=0.0025,
)

print(report.render())              # markdown to the terminal
print(report.verdict.value)         # "Conditional"

for f in report.findings:
    print(f.severity.label, f.title)

Save an HTML report:

from straud.render import write_report
write_report(report, "report.html")

⚠️ On Windows, before printing to a console

Straud's output contains arrows and severity glyphs. Windows consoles default to cp1252, where printing them raises UnicodeEncodeError. One line fixes it:

from straud.console import enable_utf8
enable_utf8()

The straud command does this for you; only the Python API needs it. Writing to a file is always safe — reports are written as UTF-8 explicitly.

Let it count your trials for you — add one line to your existing sweep:

from straud import Lab
lab = Lab("out/ledger.json")

@lab.trial("my_idea")            # ← the only line you add
def backtest(threshold=0.6, hold=60):
    ...
    return returns

Now every call is logged automatically, with the arguments it was actually called with. Re-running the same code does not inflate the count; editing the logic does. See the ledger.


D. With an AI assistant

If you research with Claude Code (or any MCP-compatible assistant), Straud can plug in as a tool the assistant must pass.

Why this matters: an AI has no memory across attempts of how many variants it already tried. The 47th backtest feels exactly like the first, so it will happily overfit and report the result with complete confidence. Straud gives it the memory it lacks.

Setup. Copy the block from mcp.json.example into a .mcp.json file in your project folder, adjusting the paths:

{
  "mcpServers": {
    "straud": {
      "command": "/full/path/to/.venv/bin/python",
      "args": ["-m", "straud.mcp_server"],
      "cwd": "/full/path/to/straud",
      "env": {
        "STRAUD_LEDGER": "/full/path/to/ledger.json",
        "PYTHONPATH": "/full/path/to/straud"
      }
    }
  }
}

Restart your assistant. It now has six tools:

Tool What it does
log_trial Record one tested variant; returns the running verdict
status How many variants have been tested so far
verdict Full trial-adjusted verdict on the best one
audit_artifact Check a data file for stale assumptions
seal_holdout Commit to one variant before looking
reveal_holdout Look, once — permanently recorded

Use one ledger per research programme. A fresh ledger every session would reset the trial count and hand back a flattering verdict.

⚠️ STRAUD_ROOT — set this deliberately

STRAUD_ROOT controls which files the MCP tools are allowed to read. It defaults to the directory the server starts in.

Set it to the research folder you intend to expose, and nothing wider.

The reason is specific: unlike the command line, where you type the path yourself, these tools take paths chosen by the assistant — and an assistant can be influenced by text inside the very data it is reading. Confining the root means a stray instruction in a downloaded CSV cannot make it read your private files and repeat them back into the conversation.

Never set it to / or to your home directory.

Optional but recommended: copy skill/straud-gate/ into .claude/skills/. It instructs the assistant to log every variant rather than only the ones it likes — which is the difference between a real trial count and a flattering one.


Telling it more (optional, but worth it)

Every flag below unlocks checks that otherwise cannot run. Anything you leave out appears under "what we did NOT test".

Costs

--gross gross_return --cost 0.0025

--cost is the round-trip cost as a decimal (0.0025 = 0.25% = 25 basis points). Unlocks the cost sweep: at what cost level the edge dies, and how much headroom you have. Under 1.5× headroom is fragile.

Look-ahead

--signal signal_strength

Checks whether your signal is secretly reading the outcome it claims to predict.

Crypto perpetuals

--domain crypto_perp --venue binance_usdm \
--hold-seconds 60 --funding-interval 8 --funding-debited

Checks whether your holds cross funding settlements (and whether you charged yourself for them), and whether your assumed cost is even achievable at that venue's published fees.

Shorting stocks

--domain us_equity_short --borrow 0.52 --hold-days 10 --price entry_price

--borrow is annualised borrow cost as a decimal (0.52 = 52%/year). Checks borrow plausibility and Reg SHO short-sale-restriction exposure.

Capacity — how big can you trade?

--adv-column daily_volume --notional 25000 --window-minutes 1 --event-multiple 5

⚠️ The event multiple dominates the answer, so Straud makes you state it rather than hiding a guess. Do not assume event moments are busier: on one measured strategy the trigger minute had 15% of normal depth, not more.


Before you run it at all — two design questions

These cost no data and both are cheap to answer wrongly.

Can this test detect what you are claiming? Pass the effect your theory predicts, in return units:

straud audit trades.csv --column net_return --expected-effect 0.004

If the sample is too small to detect an effect that size, Straud says so and calls the test incapable rather than failed. This matters because an underpowered test tells you nothing either way, and it still burns a trial against your deflation budget. One strategy in the fleet Straud was built against was paper-traded for 34 days when detecting its own claimed Sharpe needed roughly 727 — the eventual "it didn't work" was uninformative by construction.

Who is losing? An edge is somebody else's loss.

straud audit trades.csv --column net_return \
  --counterparty "index funds that must rebalance at the close and cannot defer past the effective date"

Straud cannot check whether your answer is true. It can check that you gave one, and it rejects the non-answers — "the market", "whoever is on the other side", "retail". If you cannot name who transacts at a worse price than they need to, how often, and what stops them from quitting, you have a pattern rather than a mechanism, and patterns stop working without warning.


Sealed holdouts

A holdout is data you promise not to look at until you have committed to one specific strategy.

This is the only way to earn a claim. Everything else is the best of some search; a sealed test is not.

The discipline:

  1. Do your research on part of your data.
  2. Pick one variant. Write down exactly what it is.
  3. Seal it, naming the data you have not touched.
  4. Run it on that data. Once.
  5. Report that number.

With an AI assistant, use seal_holdout and reveal_holdout. From the command line, point at a registry file:

straud audit holdout_results.csv --column net --holdout seals.json --family my_idea

Every reveal is recorded permanently. A holdout you looked at twice is not a holdout, and the record will say so. That is the point.


The ledger (counting trials automatically)

If you cannot remember how many variants you tested, stop guessing and let Straud count.

from straud import Lab
lab = Lab("out/ledger.json")

@lab.trial("momentum_idea")
def backtest(lookback=20, threshold=1.5):
    ...
    return returns

for lb in (10, 20, 30):
    for th in (1.0, 1.5, 2.0):
        backtest(lookback=lb, threshold=th)      # all 9 logged automatically

Check what it has recorded:

straud ledger out/ledger.json

Then use it in an audit — if the ledger holds more trials than you declared, the larger, more conservative number wins:

straud audit results.csv --column net --trials 20 --ledger out/ledger.json

Three things the ledger gets right:


Checking a data file (not a strategy)

Sometimes the problem is not your maths — it is the file.

straud artifact events.csv --date-column date

This looks for:

This is worth running on any results file you did not create today. In real use, a stale cost column understated a working strategy by 2.5× on Sharpe while looking completely plausible.

To silence a column you have checked, write a .meta.json beside the file recording what the assumption is:

{
  "columns": {
    "net_return": {
      "formula": "gross_return - 0.0025",
      "assumption": "25bp round-trip cost"
    }
  }
}

Documented columns are shown but not flagged.


Checking a whole portfolio

If you run several strategies together, two questions only exist at the book level.

from straud.portfolio import portfolio_stats, co_tail, to_daily

book = {
    "strategy_a": to_daily(returns_a, dates_a),
    "strategy_b": to_daily(returns_b, dates_b),
}

stats = portfolio_stats(book, campaign_trials=300)
print(stats["corr"])                  # correlation on overlapping days only
print(stats["book"]["deflated_sharpe"])
print(co_tail(book))                  # do they lose on the SAME days?

If the strategies do not overlap in time, Straud says the comparison is unmeasurable rather than comparing two different periods and calling the difference diversification.


Every finding, explained

Data integrity

Finding What to do
Non-finite values in the returns series Find what produced the NaN/infinity before trusting anything else.
The returns series has zero variance Your column is broken — a constant fill or a bad join.
Signal may be reading the outcome (look-ahead) Rebuild the signal from strictly earlier data.
Events may be fabricated by holes in the archive If your event is defined by something missing, prove the data exists first.

Statistical validity

Finding What to do
Trial-adjusted edge is not distinguishable from luck Commit to one variant, test on sealed data.
Sample is too short to support the claim Collect more data, or test fewer things.
Observations are not independent Use the --cluster flag and re-read the numbers.
The cluster key cannot reveal clustering Your key has one row per group — cluster by the shared driver.
The two independence axes disagree Report n events, n dates and n entities. Treat the weakest axis as the real sample size.
Overlapping positions counted as independent Keep one event per entity per window, and re-run.
Underpowered by construction Gather more observations, widen the population, or drop the candidate.
This result survives up to ~N variants Compare N against how many you really tried.
Too few observations to evaluate Nothing can be concluded yet.
No holdout has been sealed Seal one before deciding.
Holdout revealed N times It stopped being a holdout after the first look; say so.
The search has grown since sealing Re-seal, or report the originally committed variant.

Robustness

Finding What to do
The chosen operating point is a spike A real edge degrades gently off its optimum. This one does not.
The entire edge rests on a few clusters You are betting those episodes repeat. Size accordingly.
The edge lives in entities that RECUR Condition on recurrence and re-test, or report first-time and repeat entities separately.
A handful of entities carry the result Re-test with the dominant names excluded. If the edge does not survive, it is a claim about those names.
The average entity wins, the typical one loses Report the median beside the mean, and say which names supply the tail.
No loser is named Say who transacts at a worse price, how often, and what prevents them stopping.
A loser is named, but not what holds them there State the constraint, or size the edge as temporary.
Arbitrary cutoffs do as well as the real one Your threshold is not carrying the result.
The edge is not consistent across eras It is a statement about one regime, not a general effect.
The submitted sample spans a single era Submit more history if you have it.
Probability of backtest overfitting = N ⚠️ This measures whether your selection is informative — not whether the edge is real. Do not read a high value alone as a dead strategy.

Execution realism

Finding What to do
The edge does not survive its own cost assumption There is nothing to trade here.
The edge dies just above the assumed cost Under 1.5× headroom: measure real fills before sizing.
Declared gross and net returns are identical You passed the same column twice; cost is being charged twice.
Assumed cost is below the venue's published fee That fill is unavailable at any size.
Holds cross funding settlements, charge not debited Debit funding per crossing and re-run.
Borrow assumption below the observed band Sample borrow at signal time for your universe.
Borrow assumption far above the band You are understating your own strategy.
Shorts entering under Reg SHO SSR Model up-bid-only fills for those trades.

Capacity

Finding What to do
Trade size exceeds accessible liquidity Measure event-window depth, then re-size.
Limited capacity headroom Treat current size as near the ceiling.

Troubleshooting

straud: command not found If you pip-installed it, use python3 -m straud.cli instead — same arguments. If you downloaded the standalone build, you must be inside the unzipped folder and write ./straud (with the ./), not straud.

"straud cannot be opened because the developer cannot be verified" (macOS) The standalone build is unsigned. Right-click straudOpenOpen, or run xattr -d com.apple.quarantine straud. See A.

The standalone build is very slow Only the first run, which can take up to a minute while your system verifies ~140 libraries. After that it starts in under a second. If every run is slow, you are probably running a --onefile build — use the folder version, which is what ships here.

--column 'x' not found Straud lists the available columns in the error. Check spelling and case.

"Too few observations to evaluate" Fewer than 10 usable rows. Check for text or blanks in your returns column.

Everything comes back Critical Check your returns are decimals, not percentages. 2.5 means +250%, not +2.5%.

The verdict got worse when I added flags Working as intended. More information means more checks can run. The earlier verdict was not better — it was less informed.

Reading a Parquet file fails pip install -e '.[parquet]'

The AI assistant says a file is "outside the permitted directory" Working as intended. The MCP tools only read below STRAUD_ROOT (see C). Move the file into that folder, or restart the server with STRAUD_ROOT set to the folder you mean to expose. Do not widen it to your whole home directory.

"non-finite values in the returns series" Your file has NaN or infinity. An infinity usually means a division by zero or a mishandled total loss upstream. Worth finding.


Security notes

Verifying what you downloaded

Every release publishes SHA256SUMS.txt alongside the archives. Check it before running anything:

shasum -a 256 -c SHA256SUMS.txt

The binaries are not code-signed, so macOS will warn on first run and Windows SmartScreen may too. That is expected; the checksum is the verification path. START_HERE.txt in the archive explains the first-run steps.

If you use the MCP server, set STRAUD_ROOT

The MCP server reads files and hands what it finds to an assistant. Point STRAUD_ROOT at the research directory you intend to expose and nothing wider — it is a hard boundary, and paths outside it are refused, including via symlinks.

Straud treats your data as untrusted, and so should your assistant

Column names and ticker symbols come from exchanges, vendors and whatever wrote the file — not from you. Text inside them can be crafted to look like an instruction to an AI assistant, and an assistant that follows it could be talked into reporting a pass.

Straud defends against this: values copied out of a file are stripped of characters that could fake formatting, capped in length, labelled as data rather than instructions, and flagged when an identifier does not look like an identifier. If you see a suspicious_identifiers warning, treat the file's provenance as the question — it can equally mean an attack, a vendor feed change, or a column that shifted during a join.

Large files

Reads are capped so that an assistant cannot be induced to exhaust your memory. Parquet is checked by row and column count before it is opened, because a compressed file can expand thousands of times over. If your data is genuinely larger than the defaults, raise them:

STRAUD_MAX_CELLS=200000000 STRAUD_MAX_CSV_BYTES=2000000000 straud audit ...

Your ledger is your research

The trial ledger records every variant you have tested. On macOS and Linux Straud creates it readable only by your account, which matters on shared and cloud machines.

On Windows this does not apply. Windows uses ACLs rather than POSIX permissions, and Straud cannot set those portably, so the ledger inherits whatever its folder grants. Keep it somewhere already restricted to you — not a shared drive. Back it up as you would any other proprietary research file.


The scope

Stated plainly, because a tool about honesty should be honest about itself.

A clean report means "survived the tests we ran". It never means "safe".

Straud is not investment advice and carries no warranty. It has no market data, no broker connection, and no knowledge of your positions — it reads a column of numbers. Nothing it produces is a recommendation to buy, sell or hold anything. Trading involves substantial risk of loss, and backtested results are hypothetical.

Read DISCLAIMER in full before relying on any output.

Your data stays with you. Straud runs locally, transmits nothing, has no telemetry or analytics, and requires no account. Nothing you audit leaves your machine.


Glossary

Backtest — simulating a strategy on historical data.

Basis point (bp) — one hundredth of a percent. 25bp = 0.25% = 0.0025.

Borrow cost — what you pay to borrow shares to sell short, quoted annualised.

Cluster — a group of trades that share a driver (same day, same event) and therefore are not independent observations.

Deflated Sharpe — the Sharpe ratio after correcting for how many variants you tried. The single most useful number Straud produces.

Drawdown — a peak-to-trough fall in your account.

Holdout — data set aside and untouched, used once to test a committed strategy.

Look-ahead — accidentally using information that would not have been available at the time. Makes a backtest look far better than reality.

MinBTL (minimum backtest length) — the shortest sample that could support your claim given how many things you tried. Below it, no result is meaningful.

Multiple testing — the problem that testing many ideas guarantees some look good by chance.

Overfitting — tuning a strategy to historical noise rather than a real effect.

PBO (probability of backtest overfitting) — how often the in-sample best variant turns out below average out-of-sample. Measures your selection, not your edge.

Sharpe ratio — return divided by volatility. Straud works in per-trade Sharpe, which is much smaller than the annualised figure you may be used to.

Slippage — the difference between the price you expected and the price you got.

Survivorship bias — analysing only instruments that still exist today, which quietly removes the failures.

Trial — one tested variant of a strategy. Including the ones you threw away.