Engineering note · Paired-order lifecycle
A Paired Buy and Sell EA Needs One Explicit Cycle State
Define exactly what happens when one leg stops, the survivor closes and the EA restarts so one paired-order cycle cannot create duplicate replacements.
Buyer question
What should the EA do after one side of a paired buy and sell cycle closes?
Failure pattern
A tick sees no complete pair and creates a replacement while the survivor still belongs to the old cycle.
Acceptance rule
The survivor remains untouched, then its closure opens the next pair exactly once.
The entry rule is only the first transition
A paired-order EA sounds simple: open one buy and one sell, let one leg stop, keep the other leg alive and start a new pair after the survivor closes. The difficult part is not creating two orders. The difficult part is proving which cycle owns every order when ticks, trade events and restarts arrive in an inconvenient order.
A common implementation checks the account on every tick. If it cannot find both legs, it assumes the pair is missing and opens another one. That shortcut breaks the stated lifecycle. After the first stop, the account should contain one survivor. The missing leg is expected, not an error. Replacing it changes exposure and creates a different strategy.
The safer design gives each pair a stable cycle identity and stores the ticket or order identity of both legs. The EA then advances through explicit states. It never infers a new cycle from position count alone. Position count is evidence, but the recorded cycle state decides what the evidence means.
Source and public evidence
Use a cycle state, not a pair-present boolean
The state must answer three questions at all times: which tickets belong to this cycle, whether a survivor is expected and whether the one-time next-cycle transition has already started. That information must survive a terminal restart.
| State | What is allowed |
|---|---|
| IDLE | No cycle is owned. A new pair may be created once. |
| PAIR_SUBMITTED | Both order identities are recorded before another entry decision is allowed. |
| PAIR_OPEN | The buy and sell legs belong to the same cycle and both are active. |
| SURVIVOR_OPEN | One leg closed. The remaining leg stays open and no replacement is permitted. |
| CYCLE_CLOSING | The survivor has closed, but the next pair has not yet been created. |
| NEXT_PAIR_SUBMITTED | The one-time transition owns the next pair until both returned identities are stored. |
The smallest complete implementation
- 1
Create one cycle ID.
Generate it before submitting either leg and attach it through magic number, comment and persisted local state where the platform permits.
- 2
Record both returned identities.
Do not report PAIR_OPEN until the buy and sell submissions have known outcomes. A rejected second leg needs an explicit recovery state.
- 3
Process closures once.
Trade-history evidence advances one known ticket from open to closed. Re-reading the same closure must be harmless.
- 4
Protect the survivor state.
When one ticket closes, store SURVIVOR_OPEN with the remaining ticket. Entry logic stays locked.
- 5
Own the next-cycle transition.
After the survivor closes, move to CYCLE_CLOSING before any new submission. Only that state may create the next pair.
- 6
Rebuild before trading.
On initialization, reconcile persisted tickets with open trades and history before enabling any new order path.
Three acceptance tests to freeze before coding
These tests define completion in observable terms. They also close the most expensive revision loop: arguing after delivery about whether a stopped leg should be replaced.
Test 1
One leg stops
Input: The buy leg hits its stop while the sell leg remains open.
Pass: The buy leg is marked closed, the sell leg remains the only survivor and no replacement buy order is submitted.
Test 2
The survivor closes
Input: The remaining sell leg reaches its own exit after the first leg is already closed.
Pass: The old cycle closes and exactly one new buy and sell pair is created. Repeated ticks cannot create a second pair.
Test 3
Restart between closures
Input: MT4 or the EA restarts while one leg is closed and the survivor is still open.
Pass: The EA reconstructs the same cycle from persistent identities, keeps the survivor and does not duplicate either leg.
Scope boundaries that should be written down
The lifecycle does not decide the trading signal, lot size, stop distance or profit target. Those are separate rules. It also does not decide whether both initial submissions must succeed atomically. MT4 cannot make two independent broker requests atomic, so the specification must say what happens when one initial order is accepted and the other is rejected.
Source ownership, symbol selection, hedging permission, broker stop levels, magic-number isolation and manual order handling also need explicit boundaries. A paired-order state machine can be correct while the broker rejects hedged exposure or another EA uses the same ownership key. Keep those failures visible instead of turning them into silent retries.
Finally, passing these tests proves lifecycle behavior. It does not prove profit, drawdown, fill quality or future trading results. Those claims require separate evidence and should never be hidden inside software acceptance.
Free next step
Write the three transitions before describing the indicators
Use the free scope builder to record the stopped-leg rule, the survivor rule and the restart rule. A developer can then quote one deterministic lifecycle instead of guessing what a complete cycle means.