Strategy builder guide
A strategy is a tree of blocks. Money flows from the top of the tree down to the tickers at its tips, and each block on the way decides how its share is divided. This page covers every block and setting; to see them combined into well-known strategies, read common patterns.
Blocks#
| Block | What it does | Use it when |
|---|---|---|
| Ticker | Something to hold, such as SPY. | You know what you want to own. |
| Weight | Splits money between the blocks inside it. | You want a mix. |
| If / Else | Checks conditions and sends money to one of two branches. | The answer depends on today’s market. |
| Enter / Exit | Holds one branch from the day an entry rule fires until an exit rule fires. | Getting in and getting out need different rules. |
| Rank | Scores its candidates and keeps the top or bottom few. | You want the strongest (or weakest) of a list. |
| Group | Wraps part of the tree under a name. | A large strategy needs structure. |
| Strategy | Uses a saved strategy, yours or a published one, as a building block. | You want to combine strategies. |
Add a block with the + on the block you want to put it in. Blocks can nest inside each other as deep as you need. Each block’s ⋯ menu (or a right-click) copies, duplicates, moves it up or down among its neighbours, wraps it in a new If, Weight, Rank, or Group in its place, or deletes it: wrapping SPY in an If is the quickest way to put a trend filter in front of it. A Weight’s or Rank’s menu can also copy its children, so the tickers in one Weight become a Rank’s members with one paste.
Ticker#
A ticker is a US-listed fund or stock that trades today; search by symbol or
by name. To add several at once, paste a list such as XLB XLE XLF into a
ticker row: the row takes the first and the rest follow it, and any symbol that
isn’t listed is named and left out. A backtest can only start once every ticker in it has prices, so one
young fund can shorten the whole test. The Window menu shows the span
you’ve got, and simulated history can extend it.
Leveraged funds (such as TQQQ) and inverse funds (such as SH, which rises when the S&P 500 falls) are held like any other fund, using their real price history. To earn interest on money you’re not investing, hold a Treasury bill fund such as BIL: money left as plain cash earns nothing in a backtest.
A ticker that is hard to trade shows a warning mark beside its name: it may be delisted, sold only in whole shares, or so thinly traded that its price goes stale. Hover over or focus the mark to see why.
To swap it, use Find similar tickers, the two-arrow button just left of the ticker’s ⋯ menu. It opens a table of up to ten tickers that would track it most closely at the same weight, judged by daily returns over the history both have, with the current ticker on top for comparison:
| Column | Meaning |
|---|---|
| Corr | Correlation of daily returns, from −1 to 1. |
| Vol | How much it swings compared with the current ticker: 2.0× swings twice as much. |
| Since | The first year Tactfolio has its prices. |
| $/day | Average value traded each day over the last month. |
Pick a row and that ticker takes the current one’s place, with the same weight
and spot in the tree. The table draws on the tickers Tactfolio already keeps
prices for, so an untested fund can be missing from it. AI assistants can do
the same through MCP with find_similar_tickers.
Weight#
A Weight block divides its money between the blocks inside it in one of three ways, switched in its head:
- Equal: every block inside gets the same share.
- Specified: you type the percentages, which add up to 100%: raising one takes the difference from the others in proportion. To hold cash, give an empty Weight its share.
- Inverse vol: calmer holdings get more. Each share is proportional to one over the holding’s recent volatility, so a bond fund that swings a third as much as a stock fund gets three times the money. The Window chip beneath the Weight sets how many days of returns measure it. A Weight, Group, or Strategy inside is measured by its own simulated value.
An empty Weight means cash, which earns nothing. When an Equal Weight has a block inside that holds nothing on some days, such as an If with an empty branch, that block’s share is cash on those days.
How closely a Weight keeps its split between trades is a strategy setting: see Rebalance under Strategy settings.
A Weight that only carries one block, with no name and Equal weighting, is shown as that block: the tree gives an If’s branches such a Weight, and it decides nothing. Its add button stays beneath the block, so adding a second block brings the Weight back, and the block’s ⋯ menu has Show its Weight, for example to name it.
If / Else#
An If / Else block checks one or more conditions. When they hold, money goes to the first branch; otherwise it goes to the second. With several conditions you choose whether all, any, or at least a number of them must hold.
This one holds the S&P 500 while it trades above its 200-day average, and Treasury bills otherwise:
Each condition compares a reading with a number or with another reading:
- The left side reads one ticker (for example, QQQ’s 200-day moving average), a ratio of two tickers’ prices, the rolling correlation of a pair of tickers, breadth, or US economy data.
- Baskets: either side of a ratio can be an equal-weight basket of 2 to 20 tickers, such as cyclicals (XLE+XLI+XLF+XLB) over defensives (XLU+XLV+XLP). Press + beside a ticker to add one. A basket starts at 1 on the first day all its tickers have prices, then moves each day by the average of their daily returns, as if rebalanced to equal weights every day.
- Breadth is the share of a list of 2 to 50 tickers whose price is above their own moving average, from 0 to 1: 0.5 means half of them. “Breadth of the 11 sector funds above their 200-day averages is above 0.5” is a common market-health check. A ticker too new to have its average yet is left out of that day’s count, and press + to add a ticker or × to remove one.
- Days or months: many readings can count months instead of trading days. They then use each month’s last close, with today’s close standing in for the current month, as papers that work from month-end data do: a 10-month average is the average of the last ten month-end closes, and 12-month volatility uses monthly returns.
- The comparison is is above, is below, is at least, or is at most.
- The right side is a number or another reading. A reading can be scaled with a multiplier, for example price is above 1.05 × moving average.
- Confirm for a number of days makes the condition hold only after it has been true that many days in a row. It’s a simple way to ignore a single day’s noise.
Numbers follow the reading’s own scale, and the editor shows a hint under each value. Returns and volatility are decimals (−0.05 means −5%, 0.20 means 20%); a maximum drawdown is a zero or negative decimal, like the backtest’s (−0.10 means 10% below the high); a VIX level is in index points (20 means 20%) and compares only with numbers and other VIX readings. A condition that could never change, such as RSI above 100 or a reading compared with itself, is flagged as an error: its number is almost always in the wrong scale.
Economic data
An Economy reading uses a US series from the Federal Reserve Bank of St. Louis (FRED): the unemployment rate, nonfarm payrolls, the civilian labor force, industrial production, real retail sales, housing starts, real personal income, consumer prices (CPI), the 10-year, 2-year, and 3-month Treasury yields, the 10-year minus 3-month and 10-year minus 2-year spreads, 5-year breakeven inflation, the Baa corporate minus 10-year Treasury spread, the Fed’s balance sheet, the Treasury General Account, overnight reverse repos, the M2 money stock, economic policy uncertainty, and the VIX and VIX3M volatility indexes. FRED publishes each VIX close the next day, so these read one session behind the VIX and VIX3M tickers; the two compare with each other.
Net liquidity is Tactfolio’s own series, not FRED’s: each Wednesday, the Fed’s balance sheet less the Treasury General Account and overnight reverse repos, in billions of dollars. Where the Treasury account or the reverse repo has no figure for that Wednesday, its latest earlier figure stands in. A week counts only once all three figures are out, and reads each of them as it was published by the decision day.
Sum of series builds your own: add up two to five series, each with a coefficient, such as the 10-year yield minus the 2-year yield, or the Fed’s balance sheet minus the Treasury General Account and reverse repos (net liquidity written out). Pick it at the end of the series list. The series must all be rates in percent, or all in dollars, which add up in billions; daily series can join weekly ones, read on the weekly dates. A sum counts on a day only once every series in it has a figure out, and it compares with a number in its unit or with the same sum.
A monthly count or level can be divided per another, month by month, such as payrolls per member of the labor force; the ratio counts once both figures are out.
A reading takes the latest value, the average of the last few values, the change from a few values earlier, or the growth in percent over them. Monthly series count months and daily series count days, so “unemployment is above its 12-month average” reads the unemployment rate twice.
Economic data is where backtests most easily cheat, so Tactfolio is strict about dates:
- Each value counts only from the day it was published. The August unemployment rate, released in early September, is unknown to your strategy throughout August.
- A backtest reads every month as it stood on the decision day, taking a revision only once FRED released it, so a reading never mixes figures from different releases or base years.
- Before FRED’s release archive begins (the 1920s to 1970s depending on the series, 2011 for real personal income), the earliest archived figures stand in, dated by the usual release lag.
- A backtest starts once every economic reading has a value.
Rates, spreads, and growth are in percent (4.5 means 4.5%) and can be compared with each other. Counts and index levels compare only with the same series. Economic readings work in If / Else conditions and in Enter / Exit rules.
Enter / Exit#
An If / Else block asks “is this true today?” An Enter / Exit block asks “has the entry rule fired, and has the exit rule fired since?” Its Enter when and Exit when rules sit in the block, and its two branches follow: it holds In from the day its entry rule fires until its exit rule fires, then holds Out again. It starts Out, and if both rules fire on the same day, entry wins.
This suits rules such as “buy when RSI drops below 30 and hold until it rises above 70”, which an If / Else can’t express, because RSI spends most of its time between the two:
Its rules are checked each time the strategy runs, so a monthly strategy checks them at each month end and remembers last month’s choice in between. A rule can read anything an If / Else condition can, economic data included, and can require several days in a row before it fires.
A rule can also hold several conditions. Open it, choose Add condition, and pick how they combine, as in an If / Else: All of, Any of, or At least a number of them. That expresses breadth: enter when at least 8 of the 9 sector funds rose 3% over ten days. With several exit conditions, Go out under If any condition cannot be read exits when any one of them has no reading.
The Exit rule also takes a Time stop: Exit after a number of days in leaves once the block has held In that long since it entered, even if the exit rule has not fired, so “buy the dip, sell on the recovery or after 5 days” needs no price stop. The count starts at the entry; if the entry rule still fires when it runs out, the block stays In until the first run it does not, as it would for any exit, and entering again starts the count over. The Exit rule’s chip reads “exit after 5d”; 0 turns it off.
Rank#
A Rank block scores every candidate and keeps the top or bottom few. This one, every quarter, holds whichever of stocks (SPY) or long-term Treasuries (TLT) had the better three months:
- Score: one reading over a lookback, such as three-month return. To blend return windows, pick 1/3/6/12-month unweighted momentum, the average of the four. Scores equal to within a billionth count as tied and the candidate listed first wins, so rounding noise never flips a pick.
- Ratio to: under the score, choose Ratio to and a ticker to score each ticker on its ratio to that one instead of its own price: sector funds by their 126-day high ratio against SPY keeps the sectors nearest a high of their own, apart from the market’s moves (“of each vs SPY”). Press + to score against an equal-weight basket. It takes the readings a ratio offers, over days, and every candidate must be a ticker other than the one you score against.
- Score from … days ago: under the score, read every candidate’s score that many days before the decision day, so its window ends there. A 231-day cumulative return from 21 days ago is 12-1 momentum, the 12-month return skipping the latest month, which avoids the short-term reversal of the last few weeks (“231d cumulative return 21d ago”). It works with month windows, Ratio to, and Weight blocks; 0 reads the score on the decision day.
- Candidates can be single tickers or whole Weight blocks, which are scored by their own simulated value over time. A candidate can also be scored on one ticker and hold something else, such as Treasury bills when that ticker fails a trend filter. The backtest starts once every candidate has the history its score needs.
- Keep, in the Rank’s header, sets how many win, top or bottom.
- Weighting: the winners split equally, by inverse volatility, or by place: Specified sets a percentage for 1st, 2nd, and so on, such as 40/30/20/10. A candidate is always sized by the ticker it was scored on, so one parked in bills keeps its share rather than looking riskless. If fewer candidates have a score than you asked for, an equal or inverse-volatility split shares the money among those that do, while a specified place no candidate fills stays in cash.
Group#
A Group wraps part of the tree under a name, such as “Defensive sleeve”, and passes its money straight through. It changes nothing about the result; it keeps a large strategy readable.
Strategy#
A Strategy block reuses a saved strategy inside another. It can be one of your own, or any published strategy in Discover, so you can combine ideas instead of rebuilding them. The arrow at the front of the block unfolds the rules it runs, read-only, right beneath it; the open button beside its ⋯ menu opens that strategy in a new tab.
- Your own strategies follow Latest anywhere in the tree, so your edits to the inner strategy flow through. Publishing freezes them at the version it publishes; once you save the inner strategy again, the block offers Follow latest.
- Other authors’ strategies are always pinned to one exact published version, so nobody else’s edits can change your results. When the author publishes a newer version, the block offers Update to vN for their newest version, with their note on what changed, and the choice is yours.
- Schedules: the saved strategy can run as often as this one or less often. A monthly strategy inside a daily one keeps trading monthly, holding its month-end choice as prices move, exactly as it does on its own.
Indicators#
Every reading uses only prices up to the day it is read. Readings marked in the Months column can also read month-end closes: switch a window from days to months. The month in progress uses the latest close.
The two session-of-month readings count the calendar instead of prices, for turn-of-month rules: to hold over the last 4 and first 3 sessions of each month, use an If that matches Any of session of the month from the end is below 5 and session of the month is below 4. A traditional strategy counts exchange sessions, which are scheduled in advance (a holiday is not one). The reading from the end needs the rest of the month’s sessions, so it counts as not met until the calendar holds them. Month of the year reads the session’s month, 1 for January to 12 for December, for seasonal rules such as sell in May. Day of the week reads its weekday, 1 for Monday to 7 for Sunday (exchange sessions run 1 to 5): to stay out on Thursdays, use an If that matches Any of day of the week is below 4 and day of the week is above 4.
Drawdown from the all-time high compares the latest close with the highest close of the ticker’s whole history, so it reads all of it.
Return autocorrelation tells a trending market from a mean-reverting one. Over a window of daily returns (10 days or more), it correlates each day’s return with the day before’s, from −1 to 1. Above 0, moves tend to continue; below 0, they tend to reverse. A 63-day window of 63 returns compares 62 pairs of days. It compares with a number or with another return autocorrelation, of another ticker, a ratio, or another window, and a Rank can score by it.
Price z-score says how far the latest close is from its moving average, in standard deviations of the closes in the same window: −2 is two deviations below the average, the lower edge of a 2-deviation Bollinger band. On a ratio it is the classic pairs trade signal: 60-day price z-score of ratio XLE/XLU is below −2 fires when energy is unusually cheap against utilities. It compares with a number or with another z-score, of a ticker, a ratio, or another window, and a Rank can score by it. A window whose closes do not move has no z-score.
| Indicator | What it measures | Values | Months |
|---|---|---|---|
| Current price | The latest adjusted closing price. | price | — |
| Moving average | Arithmetic mean of closing prices over the selected window. | price | Yes |
| Exponential moving average | Exponentially weighted closing-price average, seeded with an initial simple average. | price | Yes |
| Ultimate Smoother | Ehlers two-pole price smoother using full causal history, seeded at the current close after missing observations. | price | — |
| Cumulative return | Compounded close-to-close return over the selected horizon. | decimal · −0.05 = −5% | Yes |
| RSI | Wilder relative strength index over the selected period, on a 0–100 scale. | 0–100 | — |
| Trend Clarity | Rolling linear-trend R-squared of closing prices, on a 0–100 scale. Both rising and falling linear trends score highly; a constant window scores 100. | 0–100 | — |
| Volatility | Annualized sample volatility of daily returns over the selected window. | decimal · 0.20 = 20% | Yes |
| Price standard deviation | Population standard deviation of closing prices over the selected window, in price units. | price | — |
| Price z-score | Current close minus the average of the closes in the selected window, divided by their population standard deviation: −2 is two deviations below the average (the lower Bollinger band), +2 two above. | standard deviations | — |
| 1/3/6/12-month weighted momentum | Weighted average of 1-, 3-, 6-, and 12-month returns using 12/4/2/1 weights. | decimal · −0.05 = −5% | 12 months |
| 1/3/6/12-month unweighted momentum | Equal-weighted average of 1-, 3-, 6-, and 12-month returns. | decimal · −0.05 = −5% | 12 months |
| 13-point relative SMA momentum | Current close divided by the average of 13 monthly-spaced closes, minus one. | decimal · −0.05 = −5% | 12 months |
| 30-month optimal trend score | Trend score from thirty monthly returns with two-exponential weighting. | decimal · −0.05 = −5% | — |
| Rolling-high ratio | Current close divided by the highest close in the selected window. | ratio | Yes |
| Rolling-low ratio | Current close divided by the lowest close in the selected window: 1 at the low, 1.10 for 10% above it. | ratio | Yes |
| Maximum drawdown | Largest peak-to-later-trough decline inside the selected window, as a zero or negative decimal like the backtest maximum drawdown (−0.10 = 10% below the high). | decimal, zero or negative · −0.10 = 10% below the high | Yes |
| Mean daily return | Arithmetic mean of daily returns over the selected window. | decimal · −0.05 = −5% | Yes |
| Return autocorrelation | Lag-1 autocorrelation of daily returns: the correlation of each return in the window with the one before it, from −1 to 1. Positive when moves persist (trending), negative when they reverse (mean-reverting). | −1 to 1 · above 0 trending, below 0 mean-reverting | — |
| Absolute change | Current close minus the close at the selected horizon. | price | — |
| 21-session change z-score | Current 21-session price change divided by the trailing sample deviation of 21-session changes. | standard deviations | — |
| Shock cooling (63-session baseline, 21-session cooling) | Binary cooling flag after a two-standard-deviation loss versus its preceding 63 sessions. | 0 or 1 | — |
| Internal bar strength | Close position within the completed daily high–low range: (close − low) / (high − low). | ratio | — |
| Aroon-Up | Recency of the latest highest high within the selected period, on a 0–100 scale. | 0–100 | — |
| Aroon-Down | Recency of the latest lowest low within the selected period, on a 0–100 scale. | 0–100 | — |
| Aroon oscillator | Aroon-Up minus Aroon-Down over the selected period, from −100 to 100. | 0–100 | — |
| Drawdown from the all-time high | Latest close against the highest close of its whole history, as a zero or negative decimal (−0.10 = 10% below the all-time high). | decimal, zero or negative · −0.10 = 10% below the high | — |
| Session of the month | The session's place in its month on the strategy's calendar (NYSE sessions): 1 on the month's first session. It reads no prices, but the condition still needs its ticker priced. | whole sessions · 1 = the first | — |
| Session of the month from the end | The session's place from the end of its month on the strategy's calendar (NYSE sessions): 1 on the month's last session, 2 on the one before. Unreadable until the calendar holds the rest of the month. | whole sessions · 1 = the first | — |
| Month of the year | The session's calendar month, 1 for January to 12 for December. It reads no prices, but the condition still needs its ticker priced. | month number · 1 = January, 12 = December | — |
| Day of the week | The session's day of the week, 1 for Monday to 7 for Sunday (NYSE sessions run 1 to 5). It reads no prices, but the condition still needs its ticker priced. | weekday number · 1 = Monday, 7 = Sunday | — |
Strategy settings#
The control at the right of the strategy’s top row (Runs daily, Rebalances every run on a new strategy) holds two settings.
Runs: how often the strategy checks its signals and trades.
Daily, Weekly, Monthly, Quarterly, Yearly.
Daily rules react fastest but trade more; monthly rules trade less and react more slowly. Many published strategies are monthly because their authors worked from month-end data. A weekly, monthly, quarterly, or yearly strategy runs on the last trading day of each period, or on its first trading day if you choose First trading day under the runs. Composer and QuantMage run on the first, so their imports start there.
Rebalance: when the holdings go back to their target weights. Between trades, prices move the mix: a 60/40 split drifts toward 65/35 when stocks rise.
- Every run restores the target weights each time the strategy runs. It keeps the mix exact, at the price of small trades on every run.
- When the allocation changes trades only when the rules want different weights than last time, and lets the holdings drift with prices in between.
- When a holding drifts trades only when a holding has moved more than a set number of percentage points, such as 5, away from its target. It’s a middle ground: fewer trades, but the mix never wanders far.
- On a schedule restores each Weight’s split weekly, monthly, quarterly, or yearly, less often than the strategy runs. Signals still switch on every run: a strategy that runs daily and rebalances monthly leaves stocks the day its trend rule turns, but lets a 60/40 Weight drift with prices until the month ends. If you make the strategy run as seldom as its schedule, it rebalances every run instead.
Every trade pays 5 basis points (0.05%) of the amount traded, the same for every strategy.
New strategies run daily, rebalance every run, and compare against SPY. The starter patterns come with the settings each tested best with.
Benchmark is set in the backtest panel rather than the tree: a ticker, SPY by default, or a strategy, yours or a published one. The backtest compares against it over the same dates. Pick what you would otherwise hold: a strategy built on tech stocks deserves QQQ, not SPY.
Saving and revisions#
Save keeps a new revision of your strategy. Earlier revisions never change, so a result can always be traced to the exact rules behind it. To go back, open Revision history and choose Restore as new revision on the one you want. The workspace lists your strategies with their latest results.
Publishing is separate: it turns one saved revision into a numbered public version. See Publishing and sharing.
Limits#
- Lookback windows up to 2,520 trading days, or 120 months of month-end closes.
- A condition can require up to 252 days in a row.
- A Rank can choose from up to 200 candidates.
- Up to 300 tickers can be held, and up to 400 referenced, across one strategy.
- Up to 10,000 blocks, nested up to 256 levels deep.
Some things a strategy can’t express today:
- Selling short or borrowing. Leveraged funds are fine as ordinary holdings, and inverse funds such as SH express a bearish view.
- Part of a strategy running more often than the whole, such as daily rules inside a monthly strategy.
- Assets other than US-listed stocks and funds, such as futures.
- Portfolio optimization beyond equal, specified, and inverse-volatility weights.