Test stock strategy holding states separately from entry conditions
A stock strategy’s entry trigger is not automatically its holding condition. An event may authorize one entry and then disappear while an existing position remains valid. Write the lifecycle as explicit states, define exit precedence and count cooldown sessions, then test the expected target sequence. A Boolean entry column alone cannot express that contract.
This guide constructs an eleven-session synthetic path with flat, long and cooling states. CoTrading state-path tooling remains planned or unverified. The example verifies desired holding states and reasons; it does not claim that any market order filled or that local sale restrictions were satisfied.
Define the lifecycle before choosing a condition
Start with three separate questions: what creates a new opportunity, what keeps an existing position open and what terminates it? A breakout event can be true only briefly, while the intended position lasts until an independent failure or time limit. Reusing the event as a daily holding requirement would cause an unintended sale as soon as it becomes false.
The fixture accepts two visible Boolean inputs per completed session: enter and exit. It has no price model. The entry event is supplied by another already evaluated rule, and the exit event is supplied independently. This separation isolates lifecycle semantics from indicator arithmetic and makes the path hand-checkable.
Use symbolic states rather than a collection of loosely related flags. Python’s enum reference describes named enumeration members, which the script uses for flat, long and cooling. Choosing those names does not provide strategy logic by itself; the transition order below supplies the actual behavior.
Freeze the transition priority
While long, an exit event moves to cooling and sets two remaining cooldown sessions. An absent exit leaves the target long even if enter is false. Another enter event while already long does not create another entry or increase target size. The long-state branch is therefore independent of repeated entry qualification.
While cooling, each complete subsequent session consumes one remaining session and keeps the target zero. The session that reduces the counter to zero still belongs to cooldown; entry becomes possible only on the following session. This convention prevents an ambiguous off-by-one interpretation of “wait two days.”
While flat, exit blocks entry if both are true. Otherwise an enter event moves to long; no event leaves the account flat. These priorities are declared before observing any outcome. A different choice can be researched, but it requires a different expected path rather than a silent reordering of branches.
Interpret the target separately from an order
Target one means the desired long state, not a new instruction to buy a full position each day. Target zero means the desired nonlong state. Actual quantities, order placement, fills, cash and market eligibility are outside this example. A target transition may later require an order, but it is not proof that the order executed.
For a real adapter, preserve desired state and actual position separately. If an exit is blocked, the desired state can be cooling while the account still owns shares. That branch needs an execution-aware contract before reuse in a live or full simulated account; the signal-only fixture does not silently resolve it.
Similarly, decide whether cooldown starts on the exit decision or on confirmed exit execution. This exercise starts it on the supplied decision transition. The distinction is material if fills can be delayed. Record the chosen convention instead of borrowing the phrase cooldown from another strategy and assuming its timing is identical.
Reconstruct the ordinary entry and hold path
At index zero, enter is true and exit false. Flat becomes long and the first entry reason is recorded. At index one, enter becomes false but exit remains false. The target stays one because the holding state is persistent. This row directly tests the mistake of treating entry qualification as an ongoing position requirement.
At index two, enter is true again while already long. The target still stays one, and the reason remains hold without reentering. There is no second entry event. Comparing only target values would miss a duplicate-entry implementation that creates another order internally, so the script also counts explicit entry reasons.
At index three, both enter and exit are true. Exit wins, the target becomes zero and the counter becomes two. The strategy cannot exit and immediately create a new long state in the same decision. The saved before-state, visible inputs, after-state and reason make this conflict independently inspectable.
Count the cooldown boundary explicitly
At index four, an entry event is present but the first cooldown session is consumed. Remaining cooldown becomes one and target stays zero. Index five consumes the second session, remaining becomes zero and state becomes flat, but target still stays zero for that row.
At index six, a fresh entry event is allowed and the target returns to one. An exit at index seven starts another two-session cooldown. Indices eight and nine remain zero despite entry events; index ten is the next allowed entry. The complete target sequence is [1,1,1,0,0,0,1,0,0,0,1].
There are three genuine entry transitions: indices zero, six and ten. The path exercises repeated entry, a vanished trigger, an exit/entry conflict and two complete cooldown boundaries. It does not infer these behaviors from a favorable profit curve, because no price or return is involved.
Run the state-path acceptance file
Download the complete example, save it as R575.py and run python R575.py with Python 3.12 or later. The standard-library source is docs/growth/research/round-5/examples/R575.py; every input is synthetic and no external service is called.
Assertions check the full target sequence, the hold reason when enter disappears, the lack of reentry while long, exit priority, both blocked cooldown rows and the three-entry count. The output is a row ledger containing previous state, current inputs, remaining cooldown, next state, target and reason.
The script also runs every prefix of the event list and requires the same earlier output. Appending later events must not rewrite a previous transition. This verifies causality for the state logic under this fixture; it does not establish that upstream market features or external event labels were historically available.
Use an explicitly wrong control
The control simply converts each enter flag into a target. Its sequence differs from the approved path. It abandons the position at index one and attempts exposure during cooldown. This is a different algorithm, even if its code is shorter and its output contains only valid zero/one numbers.
A second failure can hide behind an apparently correct target: counting every true enter as a new trade. The explicit entry-reason count catches that lifecycle error. In a complete account test, additionally assert actual quantity changes and executed orders so a persistent target cannot multiply the position silently.
Maintain the state contract across revisions
Archive event definitions, priority, counter semantics, initial state and expected paths with the code version. When changing a hold or exit rule, identify the first changed transition before comparing any backtest. A cooldown adjustment should not unexpectedly alter the meaning of entry or reset a still-valid position.
Before attaching market execution, add fixtures for blocked exits, partial fills and restarting from a saved state if those behaviors are supported. Keep unsupported branches visible. The completion condition here is that another implementer reproduces every stated transition and reason, including conflicts and boundaries, without turning a desired signal into an unsupported claim about actual trades.
An additional initial-flat conflict test supplies enter and exit together before any position exists. It must remain flat with an explicit exit-blocks-entry reason. This checks priority in a different starting state from the ordinary long-to-cooling conflict. During handoff, compare both the previous state and the reason, rather than accept a coincidentally equal target count from a different lifecycle.
FAQ
Must a vanished entry trigger immediately close a position?
Only if the approved holding rule says so. This event-entry contract holds until an independent exit.
Is the second cooldown session also an entry session?
No. Under this convention entry becomes eligible on the following session.