A 12-minus-1 stock momentum strategy with monthly ranking
A monthly cross-sectional momentum strategy compares stocks with each other, rather than asking whether one stock is above its own moving average. Freeze a historical US-stock universe, calculate a prior-return score that skips the most recent month, rank eligible names on the same decision date and trade the selected twenty at the next permitted open. Keep formation prices and execution prices separate.
This guide supplies a 23-stock synthetic panel, two monthly selections and a cost ledger. The prices are invented to expose window and replacement mechanics. There is no real historical performance, parameter optimization or claim of excess returns. CoTrading strategy-library capabilities remain planned or unverified, and no data provider or broker was run.
Define the twelve-minus-one endpoints
Let P[m] denote the completed price at monthly decision index m. This example’s score is P[m−1] divided by P[m−12], minus one. The return from P[m−1] to P[m] is the skipped latest month. The retained formation interval therefore contains eleven monthly returns. State the endpoints instead of relying on a label that different systems may interpret differently.
The Kenneth French momentum documentation uses prior two-through-twelve returns in its monthly momentum construction. That source is a methodological reference for the skip-month convention. Its published factor also uses size partitions and long-short combinations; this teaching strategy selects twenty long stocks and does not reproduce that factor.
Use a declared split-and-distribution treatment for ranking prices and a compatible accounting treatment for shares and cash. A provider’s adjusted close is not automatically an executable open. Preserve membership, identifier history and available endpoints. If only today’s S&P 500 list is available, label the study as a current-membership retrospective with survivor bias, not a historical S&P 500 strategy.
Freeze eligibility and selection capacity
At each decision, require both formation endpoints to be present and the denominator to be positive. This fixture checks the endpoints only; a production total-return calculation should also define missing intermediate observations and corporate-action validity. Missing data are an exclusion state, not a zero momentum score. Keep a reason beside each excluded name.
Rank all eligible stocks by descending score, breaking exact ties with the stable identifier. Select twenty. If fewer than twenty qualify, this guide’s production contract would leave the missing slots in cash rather than silently increase remaining weights; record the shortfall. The small fixture has enough valid stocks at both dates, so it tests a complete twenty-name selection.
Freeze ranking time at the monthly completed close and schedule trades later. Simultaneously reconcile exits and entries rather than letting input ordering prioritize the first stock. Quantity rounding, available cash, order fees and unfilled targets belong in execution records. Neither a rank nor a selected flag proves that a trade occurred.
Rebuild the synthetic ranking
There are twenty-three names, S01 through S23. For security number i, the first endpoint is ten and the subsequent monthly closes follow ten plus i times m divided by one thousand, where m runs from one through thirteen. At decision m=12, the selected score uses endpoint eleven and endpoint zero. S23 therefore scores 10.253 divided by ten minus one, or 2.53%.
The first selected list runs from S23 down to S04. S03, S02 and S01 are below the twenty-slot boundary. The rule is relative ordering, so a negative score could still qualify in a different dataset if it were among the twenty highest. If positive absolute momentum is also required, add it as a separate predeclared filter rather than assume it is part of ranking.
Set S23’s endpoint twelve to missing. That does not change decision twelve, which intentionally stops at endpoint eleven. At decision thirteen, however, endpoint twelve becomes the required ending point and S23 is excluded. The second selected list runs from S22 down to S03. Nineteen names persist, S23 leaves and S03 enters.
Check that the skipped month stays skipped
As an independent boundary test, replace S01’s endpoint twelve with 99,999 and rerun decision twelve. Its selected members must remain unchanged because endpoint twelve lies in the skipped return interval. The standalone script performs this mutation only after saving both ordinary selections, so it does not accidentally contaminate the second decision’s intended missing-data case.
An implementation using P[m] instead of P[m−1] would read the giant replacement and could elevate S01. That is a window-definition failure, distinct from S23 becoming ineligible at the next decision. Together the cases show both why unused future endpoints must be irrelevant and why previously unused endpoints can legitimately become required one month later.
Reconcile two baskets and their costs
The execution fixture separately supplies a next-open price of ten for every transaction and a later mark of ten. These are explicit synthetic opens, not inferred from formation closes. Start with 10,000 and buy forty shares of each of the twenty selected names. Each order pays a fictional fee of one. Total notional is 8,000 and fees twenty, leaving 1,980 cash.
At the second rebalance, retain the nineteen continuing holdings without orders. Sell forty S23 shares at ten, paying one fee, then buy forty S03 shares at ten, paying one fee. Cash becomes 1,978. Holdings remain eight hundred shares across twenty names, marked at 8,000, so final equity is 9,978. Total fees are twenty-two.
This flat execution-price ledger deliberately produces only fee drag. It cannot be used to claim that the ranking predicted a 2.53% account return. Formation returns describe past ranking information; subsequent fills and marks determine the account path. A full historical strategy would need all intervening valuations and actual supplied opening prices, plus applicable costs and rejected orders.
Execute and extend the research pack
The standard-library example is docs/growth/research/round-5/examples/R584.py. Its essential endpoint rule is:
start = prices[symbol][month - 12]
end = prices[symbol][month - 1]
score = end / start - 1
Run python docs/growth/research/round-5/examples/R584.py. Assertions verify both twenty-name lists, S23’s first score, invariance to the skipped-month mutation, replacement members, twenty-two fees, cash 1,978 and marked equity 9,978. Inspect exact members rather than accepting an average score or a count of twenty as sufficient validation.
For a real study, save endpoint dates, eligible historical members, exclusions, rank, target quantity, actual order, fill and valuation. Review score changes caused by revisions separately from those caused by a new month. Report turnover and cash exposure alongside any real return series. Freeze the rule before comparing variants, and update the package when universe history, corporate-action data or timing conventions change. The stopping condition is reproducibility of each monthly list and its cash bridge, not a visually appealing cumulative chart.
Download the complete example. Save it as R584.py and run python R584.py with Python 3.12 or later. It uses only the standard library and synthetic data.
Separate rank turnover from forced eligibility exits
Give every replacement a reason. In this fixture S23 leaves because its required endpoint is unavailable, while S03 enters because an available slot now reaches it in the descending list. That differs from S23 losing relative momentum to S03 on a complete panel. Both paths can create the same sell-and-buy count, so turnover alone cannot explain what happened.
Keep a compact monthly review record with both endpoint dates, available-name count, the twentieth and twenty-first candidates and all exclusions affecting the selection boundary. The boundary candidates are useful because a small data revision can swap one holding while leaving average portfolio momentum almost unchanged. Do not use that sensitivity to tune the rule after seeing subsequent returns. Instead report the revision, regenerate the frozen construction and compare its orders. If membership history or a delisting return is missing, retain that limitation beside the result. A polished monthly chart cannot repair an incomplete investable universe or establish that excluded securities would have been available historically.
Compare endpoints, eligibility, ranking and quantities in that order when checking another implementation. Equal terminal equity can conceal offsetting errors, so inspect the selection boundary and one actual exit.
FAQ
Is this the published French momentum factor?
No. It borrows a skip-month convention but uses a different long-only twenty-stock construction.
Does the latest month never matter?
It is excluded from this month’s formation score but can become part of the next month’s required interval.