Collapse
kaya-collapseCorecollapse1 crVariants
One module per value type. They behave identically; pick the one matching the type you are wiring.
| Name | Type |
|---|---|
| Collapse Boolean | boolean |
| Collapse Double | double |
| Collapse Float | float |
| Collapse Integer | integer |
| Collapse String | string |
A sweep asks a question many times at once. Collapse answers it once.
Fan a strategy out over a basket of assets, or a range of indicator settings, and you get many parallel candidates. But an order can only be sent once. Collapse applies your rules and forwards exactly one, rejoining the branch into one the rest of the strategy can act on.
If Filter says "give me the best few", Collapse says "give me the one". It is the normal way to end a sweep.
The fan-out is described on the For Each page; ranking several survivors onto numbered outputs on the Filter page. This page is about choosing the winner, and it is mostly worked examples.
Where Collapse Sits
flowchart LR
S["Sweep<br/>5 combinations"]
subgraph FAN["Fanned-out branch"]
direction TB
C0["Candidate 0"]
C1["Candidate 1"]
C2["Candidate 2"]
C3["Candidate 3"]
C4["Candidate 4"]
end
CO["Collapse<br/>Take = 1"]
SEL["Selection"]
IDX["Selection index"]
D["Rest of the strategy<br/>one branch"]
S --> C0 & C1 & C2 & C3 & C4
C0 & C1 & C2 & C3 & C4 --> CO
CO --> SEL --> D
CO --> IDX
style CO fill:#1565c0,color:#fff
style SEL fill:#c8e6c9,stroke:#2e7d32
style IDX fill:#c8e6c9,stroke:#2e7d32
style D fill:#fff3e0,stroke:#ef6c00
Everything downstream of Selection sees one ordinary series, as if the sweep had never happened.
The Selection Rule
Let be the candidates in sweep order. Three stages follow.
Stage 1 — Eligibility. A candidate survives if it carries data and its Condition ends true:
Without Condition wired the second clause drops, and every non-empty candidate survives.
Stage 2 — Ranking. Survivors are ordered by the last value of their Sort series, :
Without Sort wired no reordering happens at all: survivors keep input order, and Order is irrelevant.
Stage 3 — Take. Take is fixed at : the first ranked candidate becomes the Selection, the rest are discarded. That fixed is the whole difference from Filter.
Both resolve stably: survivors sharing a Sort value keep their input order, so the earlier wins.
flowchart TB
A["All candidates<br/>in input order"] --> B{"Condition wired?"}
B -- "no" --> C["Every non-empty<br/>candidate survives"]
B -- "yes" --> D["Keep candidates whose<br/>Condition ends true"]
C --> E{"Any survivor?"}
D --> E
E -- "no" --> F["Selection empty<br/>branch dies"]
E -- "yes" --> G{"Sort wired?"}
G -- "no" --> H["Keep input order"]
G -- "yes" --> I["Rank by last Sort value<br/>Order = asc or desc<br/>ties keep input order"]
H --> J["Take the first<br/>Take = 1"]
I --> J
J --> K["Selection + Selection index"]
style F fill:#ffcdd2,stroke:#c62828
style K fill:#c8e6c9,stroke:#2e7d32
style D fill:#e3f2fd,stroke:#1565c0
style I fill:#e3f2fd,stroke:#1565c0
Inputs and Outputs at a Glance
| Wiring | Role |
|---|---|
| Items | The candidates, one per sweep combination, in sweep order. |
| Condition (optional) | One boolean series per candidate. Only its last value is read. |
| Sort (optional) | One numeric series per candidate. Only its last value is read. |
| Order | desc — highest Sort value wins. asc — lowest wins. Absent, it behaves as desc. |
| Take | Fixed at 1. |
| Selection | The winning candidate's series. |
| Selection index | Which candidate won, counting from 0 in input order. |
Two habits to form now. Only the last value of Condition and Sort matters — they are verdicts on a candidate, not bar-by-bar signals. And Items are in sweep order, which Selection index reports.
Worked Example 1: One Sector Out of Five
The idea. Rather than spread capital across every sector, hold the one with the strongest risk-adjusted momentum, and hold nothing that is falling.
The setup. Daily closes for five SPDR sector ETFs from Alpha Vantage over the 63 trading days from 2026-06-05 to 2026-09-04 (64 closes, 63 daily returns, roughly one quarter). One combination per ticker, in this order: XLE (energy), XLK (technology), XLF (financials), XLV (health care), XLU (utilities).
The Sort statistic. Quarterly return over annualised volatility — a simple risk-adjusted momentum score:
where is the sample standard deviation of the 63 daily log returns and annualises it. Order is desc; Condition requires a positive quarterly return — a gate, not a ranking. It sets who reaches the podium, not who tops it.
The Candidate Table
| Index | Sector | Start (06-05) | End (09-04) | Return | Ann. volatility | Sort value | Condition |
|---|---|---|---|---|---|---|---|
| 0 | XLE | 57.67 | 64.06 | +11.08% | 22.18% | 0.500 | ✅ true |
| 1 | XLK | 180.30 | 187.28 | +3.87% | 31.92% | 0.121 | ✅ true |
| 2 | XLF | 52.30 | 58.10 | +11.09% | 12.75% | 0.870 | ✅ true |
| 3 | XLV | 153.01 | 171.45 | +12.05% | 18.62% | 0.647 | ✅ true |
| 4 | XLU | 44.35 | 43.08 | −2.86% | 14.63% | −0.196 | ❌ false |
Check the arithmetic: , and .
What happens. XLU falls at Stage 1: it declined, so its Condition ends false. The four survivors rank descending — XLF (0.870), XLV (0.647), XLE (0.500), XLK (0.121). Take is 1, so:
- Selection = the XLF price series
- Selection index = 2
Why This Example Is Worth Studying
The sector with the highest return did not win. XLV rose 12.05% against XLF's 11.09%, but was 46% more volatile, and the score reversed the ordering. XLE is starker: XLF's return earned at 22.18% volatility instead of 12.75% lands two places lower.
The lesson: the module does not know what "best" means — your Sort input defines it. Wire raw return and XLV wins; wire risk-adjusted return and XLF does. That choice is the real work.
Notice how the optional inputs divide the labour. Condition is a hard veto — is this acceptable at all? Sort is a soft preference — among those, which is better? A veto through Sort alone fails when every candidate is bad: something still wins.
Worked Example 2: When Nothing Deserves Your Money
The idea. Sweep an indicator setting instead of an asset. Hold XLK only while its close sits above its -day simple moving average, letting the sweep choose — but refuse to trade unless the winner made money.
The setup. XLK daily closes from Alpha Vantage. Five lookbacks, , in that order. Averages warm up over earlier bars; the rule runs across the 49 trading days from 2026-06-26 to 2026-09-04. Sort is the rule's realised return, Order desc, Condition demands it be positive.
The Candidate Table
| Index | Lookback | Days invested | Winning days | Rule return | Condition |
|---|---|---|---|---|---|
| 0 | 10 | 19 / 49 | 42.1% | −2.80% | ❌ false |
| 1 | 20 | 21 / 49 | 38.1% | −9.99% | ❌ false |
| 2 | 30 | 24 / 49 | 45.8% | −0.62% | ❌ false |
| 3 | 40 | 28 / 49 | 42.9% | −6.07% | ❌ false |
| 4 | 50 | 31 / 49 | 45.2% | −3.53% | ❌ false |
Simply holding XLK over the same window returned +3.41%. Every swept rule lost money, and every one lost to doing nothing clever.
What happens. All five fail the Condition. The survivor set is empty, Selection is empty, everything downstream is dead, no position is taken. That is the module working, not failing: the honest answer to "which lookback?" was none of them.
The Same Sweep Without a Condition
Unwire Condition and re-run the same numbers. Nothing is eliminated, all five rank by Sort descending, and wins at −0.62% — Selection index 2. The strategy dutifully buys the least-bad loser.
Sort ranks; it never rejects. If your strategy must be able to stand aside, only Condition lets it.
Ties, and Values That Are Not Numbers
A Real Tie
Ties are rare with a statistic whose decimals never coincide, and the norm with counting statistics. Split the same five sectors over the same window into thirteen blocks of five trading days (the last runs three) and count the blocks that closed up:
| Index | Sector | Positive blocks (of 13) |
|---|---|---|
| 0 | XLE | 7 |
| 1 | XLK | 6 |
| 2 | XLF | 10 |
| 3 | XLV | 8 |
| 4 | XLU | 6 |
Wire this count to Sort with Order = asc — a contrarian sweep hunting the weakest sector — and XLK and XLU tie at 6. Ranking is stable, so input order decides: XLK wins, Selection index 1, being combination 1 against XLU's 4.
With coarse statistics, the order of your sweep is a silent tie-breaker. Use a finer statistic if that bothers you.
Unreadable Sort Values
A Sort value that cannot be read as a number — an empty series, or something non-numeric — is not an error and does not stop the strategy. The candidate is ranked last in both directions: under desc and asc alike, every readable candidate is placed ahead of it.
Two unreadable candidates keep their relative order. The rule is conservative on purpose: an unreadable score never sneaks to the front by posing as negative infinity under asc. It wins only when nothing readable is left to beat.
The Trap: Choosing the Winner Is Not Free
Selecting the best-performing candidate from a sweep and trading it is the classic overfitting mistake. The more combinations you sweep, the likelier the winner won by luck — and luck does not repeat.
The mechanism is multiple comparisons: search enough variants against one history and something looks excellent by chance. Bailey, Borwein, López de Prado and Zhu show in Pseudo-Mathematics and Financial Charlatanism that a few trials manufacture an impressive backtest from noise.
Defences that fit naturally around a Collapse:
- Sweep across assets, not parameters. Cross-sectional momentum is widely replicated; the parameter that best fit last quarter is not.
- Keep the sweep small. Five candidates is a choice; twenty is a search.
- Make the Sort statistic robust, measured over a longer window than you intend to trade.
- Insist the winner clears an absolute bar through Condition. Best of five bad options is no reason to invest.
Collapse answers "which one?" precisely. Whether the question was worth asking of this data is a judgement it cannot make for you.
Missing Data
Candidates arrive from a fanned-out branch, and branches die upstream: a source with no history, a filter that rejected everything, a computation that produced nothing.
- A candidate whose branch died arrives empty. It holds its place, so Selection index stays meaningful, but can never win.
- A missing or empty Condition series eliminates that candidate. No verdict is not a pass.
- A missing or empty Sort series eliminates nothing — it makes that candidate unrankable, so it goes to the back.
- Candidates at different frequencies align as everything in Systemic does: lower-frequency series up-sampled by holding the last known value, shorter ones padded by repeating their final point. Since only the last Condition and Sort value is read, a padded tail carries that verdict forward unchanged.
- If every candidate is eliminated, Selection is empty and the branch is dead — the intended way to express "no acceptable choice, so no position".
Edge Cases
| Situation | What Collapse does |
|---|---|
| One candidate, Condition false | Selection empty; branch dies. |
| Condition not wired | Nothing is eliminated on that basis; every non-empty candidate competes. |
| Sort not wired | No reordering; the first surviving candidate wins, and Order is ignored. |
| Neither Condition nor Sort wired | The first non-empty candidate wins — a plain "take the first branch that has data". |
| All Sort values equal | Stable ranking; the earliest surviving candidate wins. |
| Sort value unreadable | Ranked last under both asc and desc. |
| All candidates empty | Selection empty; branch dies. |
| One candidate, Condition true | It wins. Selection index 0. |
| Sweep at its cap | A sweep produces at most 20 combinations, so there are never more candidates. |
Further Reading
- Sharpe Ratio — the risk-adjusted return idea behind Example 1's Sort statistic
- Momentum in finance — why "hold the strongest" is a strategy family, not a hunch
- Jegadeesh & Titman (1993), Returns to Buying Winners and Selling Losers — the foundational momentum study
- Bailey, Borwein, López de Prado & Zhu, Pseudo-Mathematics and Financial Charlatanism — how fast sweeping makes false winners
- The multiple comparisons problem and overfitting — the statistics under that warning
- Sector rotation — the family Example 1 belongs to