A long-only stock trend-following backtest rule pack
A long-only trend rule needs a persistent holding state, a complete warmup window and an execution time after the close that formed the signal. This example uses a fixed 120-session simple moving average: target long when the completed close is strictly above the average, and target cash otherwise. Orders can first use the next supplied session open.
The package is a reproducible rule demonstration using one invented US stock and a USD cash account. Its small price path includes a loss and a missing-open counterexample. It is not a historical strategy result, a claim of stable profits or evidence that a CoTrading strategy template has been implemented. Product capabilities remain planned or unverified.
Freeze the rule and its inputs
Specify completed daily raw closes for the indicator, unadjusted supplied opens for this fixture’s transactions and no corporate actions within the synthetic period. A real stock study must choose a consistent corporate-action treatment instead of mixing split-adjusted indicators with unexplained raw holdings. Preserve a historical universe and its eligibility dates; one current constituent list does not establish past membership.
Count 120 observations including the current close. With only 119 observations, the target is zero. At exactly 120, calculate the average. Strictly greater means long; equality and lower values mean zero. This keeps a flat price series out of the market. The rule is a daily state, so consecutive qualifying closes maintain the position rather than create repeated full-size purchases.
Use a single position with integer shares, a fictional commission of one per executed order and zero additional slippage in the baseline fixture. Enter only when currently flat and the previous completed close targeted long. Exit when holding shares and that previous close targeted zero. Do not resize an existing long position every day; that would be a different execution convention with different turnover.
Walk through the 120th close
The first 119 closes are all 100. The next four closes are 102, 104, 90 and 89. Using zero-based indices, row 119 is the first with a full window. Its average is 100 plus two divided by 120, so 102 is above it. Row 120 also qualifies. Row 121 falls below its own trailing average and requests exit.
The signal tail from row 119 is [1, 1, 0, 0]. Every earlier signal is zero. The first qualifying close occurs after that session’s open has already happened. It therefore cannot buy at an earlier price of 100 or at the same close of 102 in this next-open model. Save the decision row beside every fill to make the relationship inspectable.
The supplied opening prices are 100 through row 119, then 103 at row 120, 105 at row 121 and 88 at row 122. The gap from the exit signal’s close of 90 to the executable open of 88 belongs in the account result. A stop or moving-average condition evaluated at a close does not lock in that close as a sale price.
Rebuild the baseline account
Start with 1,000 cash and no shares. At row 120, the prior close qualifies. Reserve one for commission and buy the largest integer quantity affordable at 103: nine shares. Notional is 927, commission one, and remaining cash seventy-two. At row 121 the prior signal remains one, so the account holds nine shares without another order.
At row 122 the prior close signal is zero. Sell nine shares at 88, receive 792 less one commission and finish with 863 cash. Total commissions are two and there is no position left. The loss is 137, comprising 135 from the price difference and two in fees. These invented numbers test the complete entry, hold and exit path rather than advertise a favorable curve.
Investor.gov’s order explanation distinguishes an observed trading price from an assured execution price. The fixture assumes fills at specified opens; a historical production model must explain liquidity, slippage and restrictions. The supplied prices are enough for this arithmetic demonstration, not enough to prove real-world executable capacity.
Use missing execution data as the counterexample
Remove the usable opening bar at row 120 while leaving the completed-close signal history unchanged. The strategy cannot buy at that missing open and must not substitute the previous close. Under this example’s persistent-target retry policy, row 121 still sees a qualifying prior signal, so it buys nine shares at 105 and pays one commission.
The exit request still arrives for row 122. Nine shares sell at 88 with one commission, leaving 845. This counterexample changes execution availability, not the trend calculation. A system that emits only a one-time crossing event might never retry, producing a different path. That alternative needs a separate specification; it cannot be silently substituted for the sustained holding rule.
The difference of eighteen between 863 and 845 comes from paying two extra units for each of nine shares at entry. It is not an indicator error. Preserve a no-fill event at row 120, the retry reason and the actual later order. Otherwise a reviewer might attribute the difference to a duplicated shift or a changed moving-average window.
Run the standard-library fixture
The file docs/growth/research/round-5/examples/R583.py calculates the full 120-session window rather than shortening it for convenience. Its signal definition is:
signals = [
int(i >= 119 and close > sum(closes[i - 119:i + 1]) / 120)
for i, close in enumerate(closes)
]
Run python docs/growth/research/round-5/examples/R583.py. Assertions check the 119 warmup zeros, the exact signal tail, baseline fill indices 120 and 122, nine-share quantity, final baseline cash 863, delayed-entry orders at 121 and 122 and delayed-entry cash 845. The separate account replay proves the missing-open case without manufacturing a sale of shares that were never purchased.
Prepare a historical research package
Before replacing the fixture with data, freeze symbol list, calendar, starting capital, indicator price treatment, open-price source, window, comparison, execution lag, quantity rule, costs and missing-bar policy. Store a configuration snapshot and an order ledger, then reconcile daily equity as cash plus marked shares. Explain whether the final run liquidates positions or leaves a marked holding.
For an A-share adaptation, separately model applicable sale eligibility and price constraints; do not inherit the US fixture’s unrestricted synthetic fills. Use market-specific rule sources and historical versions. Report rejected orders, turnover, exposure and cost assumptions alongside any genuine historical results. Compare against an appropriate declared baseline using the same dates and valuation convention. Update the study when data, universe or execution rules change, and rerun the hand-checkable boundary path before interpreting a new performance table.
Download the complete example. Save it as R583.py and run python R583.py with Python 3.12 or later. It uses only the standard library and synthetic data.
Audit the account state between signals
For each row, preserve prior target, opening availability, quantity before action, order reason, fee, cash after action and closing valuation. This makes the continuous hold at row 121 visible in the baseline: a target of one and existing shares produce no second purchase. It also makes the alternative path visible: the same target with no existing shares permits the delayed entry. A signal table without account state cannot distinguish those situations.
Reconcile quantity after every action before calculating a return. If a data gap prevented the purchase, a later exit signal cannot sell nonexistent shares. If an exit is blocked, keep the shares in the account and show their subsequent mark rather than declaring the strategy flat. Those branches are additional work beyond the supplied missing-entry example and should remain explicitly unverified until implemented. When extending the study, add one fixture per new state transition and confirm both the signal and the cash consequences before interpreting aggregate performance or comparing the rule with another strategy.
FAQ
Why use a persistent signal instead of only a crossing event?
It defines the desired holding state every day and makes the missing-open retry policy explicit.
Does this show that 120 sessions is the best window?
No. The window is fixed by the example’s specification, and no parameter search or real-market comparison was performed.