# Strategy builder guide

> Every block, condition, indicator, and setting in the Tactfolio strategy builder.

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](https://tactfolio.com/docs/patterns.md).

## 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](https://tactfolio.com/docs/simulated-history.md) 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 or Inverse vol
Weight has a block inside that can hold nothing on some days, such as an If with
an empty branch, **Share empty** in its head gives that block's share to the
others on those days instead of holding it as cash.

An Equal Weight of exactly two blocks can **Blend** them by a reading instead of
splitting evenly. Set the reading (any indicator a condition can read) and two
values: at the **First at** value or beyond it the first block holds
everything, at the **Second at** value or beyond it the second block does, and
in between the money moves in proportion. An RSI of 10 days on TQQQ blended from
90 to 70 holds the first block above 90, the second below 70, and half of each
at 80. On a day the reading can't be read, the Weight holds cash.

How closely a Weight keeps its split between trades is a strategy setting:
see **Rebalance** under [Strategy settings](#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:

Strategy "S&P 500 200-Day Timing", which runs daily and rebalances every run:

- Weight: Equal
  - If: current price of SPY is above 200d moving average of SPY
    - Then:
      - Weight: Equal
        - **SPY**
    - Otherwise:
      - Weight: Equal
        - **BIL**

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, and
  shifted with **Plus**, for example price is above the price 15 days ago
  plus 0.50.
- **Left from** and **Right from** read a side that many days back, so the
  200-day average above itself 20 days ago is a rising average.
- **Percentile over** reads the left side as its percentile among its own last
  days, from 0 to 100: a 10-day average of TLT/BIL above 50 over 100 days is
  above the middle of its last 100 readings.
- **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.

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:

Strategy "RSI swing", which runs daily and rebalances every run:

- Weight: Equal
  - Enter / Exit: Enter: 10d RSI of QQQ is below 30 · Exit: 10d RSI of QQQ is above 70
    - Active:
      - Weight: Equal
        - **QQQ**
    - Inactive:
      - Weight: Equal
        - **BIL**

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.

### 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:

Strategy "Maewal-Bock Paired Switching", which runs quarterly and rebalances every run:

- Weight: Equal
  - Rank: Top 1 · 3-month return (month-end)
    - **SPY**
    - **TLT**

- **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.
- **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](https://tactfolio.com/strategies), 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); a
Hyperliquid strategy counts UTC days. 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*.

**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.

| 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 | — |
| 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 |
| 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, or UTC days on Hyperliquid): 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, or UTC days on Hyperliquid): 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 (UTC on Hyperliquid). It reads no prices, but the condition still needs its ticker priced. | month number · 1 = January, 12 = December | — |

## 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.

Hourly, Every 4 hours, 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 draft** on the one
you want. The [workspace](https://tactfolio.com/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](https://tactfolio.com/docs/sharing.md).

## 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.

## Learn the ideas behind it

- [Moving averages](https://tactfolio.com/learn/moving-average.md): What a moving average is, SMA vs EMA with a 5-day example, the 200-day average, golden and death crosses, and dated backtests of classic rules.
- [Momentum strategy](https://tactfolio.com/learn/momentum-strategy.md): A momentum strategy buys recent winners by a written rule.
- [Trading indicators](https://tactfolio.com/learn/best-indicators-for-trading.md): Moving averages, RSI, MACD, Bollinger Bands, volume and momentum explained in plain words, with what the research says and classic rules that use them.
