Forge Fate
Quant & Data Research

Start a Backtest with Positions, Not Returns: One-Period Accounting for Buy and Hold vs. Momentum

6 min read

Evidence and scope — Accounting example with synthetic inputs

Hand calculations and code illustrate one holding period for fictional SYN_A/B/C assets. Real ETF prices, market calendars, and final-test data have not been acquired. The figures are not evidence of actual returns or strategy superiority.

Keep the time contract established in Part 3 unchanged. Signals are finalized using information available through the signal-date close; orders are executed at the next trading day’s close; and only price movements after execution contribute to returns. This chapter verifies, within that sequence, what to buy, how much to hold, and how to record cash and portfolio value.

The project’s U.S. ETF research candidates are SPY, IEF, and GLD, but actual CSV files, a market calendar, and price data for 2006–2025 have not yet been obtained. Final test data for 2021–2025 has also not been obtained or viewed. The SYN_A, SYN_B, and SYN_C assets below are therefore synthetic assets, unrelated to actual ETFs, used for a hand-checkable single holding period. This is not a market backtest or a demonstration of strategy superiority. It is an implementation guide for locking down shared accounting rules before connecting real data.

The core idea is simple: apply the same starting capital, execution prices, and ending valuation prices to both strategies. The only differences should be their allocation rules and the resulting position quantities. That lets us confirm that any difference in ending value comes from the stated holding rules—not from a timing bug in the code or untracked cash.

Shared Inputs for One Holding Period

For this example, the reference prices 12 months earlier are [80, 120, 100], and all signal-date closes are 100. Each asset is compared with its own prior price and current signal-date price. This is consistent with the basic idea of time-series momentum, which focuses on an asset’s own past return rather than ranking assets against one another. [S1]

ItemSYN_ASYN_BSYN_C
Reference price 12 months earlier80120100
Signal-date close100100100
12-month signal1/4-1/60
Next-trading-day execution close100100100
Valuation price immediately before next rebalance12090120

The signals are calculated as follows:

  • SYN_A: 100 / 80 - 1 = 1/4
  • SYN_B: 100 / 120 - 1 = -1/6
  • SYN_C: 100 / 100 - 1 = 0

The signal-date close of 100 is not the order-fill price. After the signal is finalized, the initial orders are assumed to execute at the next trading day’s closing prices, [100, 100, 100]. The positions are then valued at [120, 90, 120], immediately before the next rebalance.

Starting capital is 1000, transaction costs are 0, and cash yield is 0. Fractional shares are allowed. These assumptions do not attempt to reproduce real ETF order sizes or execution constraints; they are the minimum inputs needed to examine the relationship between quantities and portfolio value precisely.

Using Fraction represents rational values such as 1/4, -1/6, 10/3, and 4/11 without floating-point rounding. [S2] That makes the calculations easier to verify by hand, but it does not guarantee executable code or real-world market feasibility.

Momentum Rule for This Example: Select Only Positive Signals, Otherwise Hold Cash

This rule is not a direct replication of the original paper. Time-series momentum research considers each security’s own historical return, but the empirical universe in that research differs from this synthetic example’s assets, long-only rule, and cash-holding rule. [S1]

The educational rule used in this project is:

  1. Select only assets with a 12-month signal that is strictly positive.
  2. If any assets are selected, allocate equally across them.
  3. If no assets are selected, hold the entire portfolio in cash.
  4. Do not trade on the signal date; calculate quantities using the next trading day’s execution price.
  5. After initial entry, do not reset the buy-and-hold strategy to equal weights each month.

With signals of [1/4, -1/6, 0], only SYN_A is strictly positive. The momentum weights are therefore [1, 0, 0]. SYN_C is not selected because its signal is 0. The boundary must remain consistent in both code and documentation: this is “greater than zero,” not “greater than or equal to zero.”

Buy and Hold Keeps Share Quantities Fixed

Buy and hold divides the initial 1000 equally among the three assets and enters once. Each asset receives 1000 / 3, and because all execution prices are 100, the initial quantities are:

text
(1000 / 3) / 100 = 10 / 3

The initial holdings are therefore 10/3 shares each of SYN_A, SYN_B, and SYN_C. Those quantities do not change when prices later diverge. At the end of the holding period, the position values are:

text
SYN_A: (10 / 3) × 120 = 400SYN_B: (10 / 3) ×  90 = 300SYN_C: (10 / 3) × 120 = 400

The ending value is 400 + 300 + 400 = 1100. That is a 10% increase from starting capital, but it is only the arithmetic result of applying deliberately chosen synthetic prices.

The important point is that ending weights are no longer one-third each. They become 4/11, 3/11, and 4/11. A and C, whose prices rose, become larger shares of the portfolio; B, whose price fell, becomes smaller. That is not an error—it follows from fixed quantities. Resetting the portfolio to one-third per asset every month would no longer be buy and hold; it would be a separate periodic rebalancing strategy.

Momentum Uses the Same Ledger

Momentum selects only SYN_A because it has a positive signal. It invests the entire initial 1000 at the execution price of 100, producing a quantity of 10. Valued at the ending price of 120, the portfolio is worth 1200.

StrategyInitial quantitiesEnding valueEnding weights
Buy and holdA=10/3, B=10/3, C=10/31100A=4/11, B=3/11, C=4/11
Positive-signal momentumA=10, B=0, C=01200A=1, B=0, C=0
All-cash checkA=0, B=0, C=0, cash=10001000cash=1

On this same price path, buy and hold gains 10% and momentum gains 20%. Those figures must not be interpreted as evidence that momentum is superior, as actual ETF returns, or as proof that the strategy is investable. The ending prices were chosen by hand for accounting verification, and an attractive result from one period does not reduce the risk of overfitting in strategy development and backtesting. [S3]

The point is not that “buying only A worked better.” The point is to verify that both strategies share the same execution and valuation dates while correctly following their respective quantity rules.

Core Code to Check Against Hand Calculations

The code below calculates quantities, cash, and portfolio value in one function. signals are finalized on the signal date, execution_close is used only to calculate quantities at the next trading day’s price, and end_close values holdings after that point.

python
from fractions import Fraction as F# Keep all inputs exact for hand-checkable accounting.reference = [F(80), F(120), F(100)]signal_close = [F(100), F(100), F(100)]execution_close = [F(100), F(100), F(100)]end_close = [F(120), F(90), F(120)]initial_cash = F(1000)# Confirm the signal before the next-session execution.signals = [now / past - 1 for past, now in zip(reference, signal_close)]# Select strictly positive signals and equal-weight selections.selected = [signal > 0 for signal in signals]selected_count = sum(selected)momentum_weights = [    F(1, selected_count) if chosen else F(0)    for chosen in selected] if selected_count else [F(0), F(0), F(0)]# Buy and hold enters equally once, then keeps quantities fixed.buyhold_qty = [initial_cash / 3 / price for price in execution_close]# Momentum calculates quantities at the next-session execution price.momentum_qty = [    initial_cash * weight / price    for weight, price in zip(momentum_weights, execution_close)]def value(quantities, prices, cash=F(0)):    return cash + sum(        qty * price for qty, price in zip(quantities, prices)    )# Verify signals and initial accounting.assert signals == [F(1, 4), F(-1, 6), F(0)]assert momentum_weights == [F(1), F(0), F(0)]assert value(buyhold_qty, execution_close) == initial_cashassert buyhold_qty == [F(10, 3), F(10, 3), F(10, 3)]# Verify fixed quantities and ending valuation.assert buyhold_qty == [F(10, 3), F(10, 3), F(10, 3)]assert value(buyhold_qty, end_close) == F(1100)assert value(momentum_qty, end_close) == F(1200)# Verify cash-only valuation when cash return is zero.cash_only_qty = [F(0), F(0), F(0)]assert value(cash_only_qty, end_close, cash=initial_cash) == initial_cash

The checks answer four questions: Is buy and hold valued at exactly 1000 immediately after the initial purchases? Are its quantities fixed through the ending date? Are the ending values exactly 1100 and 1200? If the portfolio is entirely cash, does its value remain 1000 regardless of price movement?

According to the provided execution record, all of these assertions passed using the standard-library fractions.Fraction in local python3 on 2026-09-16. This result is based on the provided record; it is not an independently rerun result in this article. Also, checking that an all-cash portfolio remains unchanged does not mean that every branch producing non-positive signals, or the full monthly execution flow, has been automatically verified.

What Has Not Yet Been Verified

This one-period example does not construct an intervening 12-month daily time series. Trading-day handling, month-end detection, date sorting, and missing-value handling have not been implemented. Because actual CSV data is unavailable, this also does not test real SPY, IEF, or GLD returns; corporate actions such as dividends and splits; costs and taxes; spreads; market impact; or order and execution feasibility.

This synthetic one-period example is not an input for performance evaluation. It is an accounting check for quantities, cash, and portfolio value. For that reason, it does not report CAGR, Sharpe ratio, or maximum drawdown.

The next step is not a larger performance table. First, calculate the SYN_A/B/C inputs by hand and confirm that the quantities, cash, and ending values match the code. Once real data is available, connect trading-day alignment, signal-date-to-execution-date mapping, monthly scheduling, missing-data treatment, and costs to the same accounting ledger in sequence. Part 5 will address costs and risk, but no real-world performance or tradability claims can be made until actual data, trading days, and execution assumptions are in place.

This article is an educational and research-oriented accounting validation, not a recommendation to buy or sell any ETF.

Sources

Report an error or share feedback

Open a draft with this article’s title and URL. Review the message and recipient before sending.

To: [email protected]

Open email draft

If no email app opens, copy these details into your usual email service.

Contact information