Recover a simulated stock account without duplicate trades
Recovering a simulated stock account requires more than re-running its last buy instruction. The account may have committed the instruction before acknowledgement was lost, and a repeated input could then spend the money twice. Recover from a trusted asset checkpoint, replay identified events and verify the contents of every duplicate before advancing cash or shares.
This guide uses a single-writer, single-stock USD fixture with actual temporary checkpoint files. It exercises save, reload, repeated events, missing quotes, version errors and corrupted content. It does not restart a live service, call a model, contact a broker or prove that CoTrading has durable scheduling and recovery. The event-ledger recovery method remains an external extension.
Define the recovery boundary before replay
A checkpoint contains rule version v1, the last committed sequence, cash, shares and a map from processed event IDs to payload digests. Opening values are sequence zero, cash 1,000, zero shares and an empty receipt map. The event stream is already a set of synthetic confirmed execution inputs; the example does not regenerate strategy signals from missing historical market data.
Freeze the rule and input contracts with the checkpoint. A new rule version cannot be substituted just because it is the newest file available after a restart. The resulting actions might differ from those that originally produced the saved inventory. Unknown rule versions stop recovery; a separate, documented migration would be required to change the account’s semantics.
The checkpoint is a recovery aid, not independent proof that all past source events were correct. Keep the event archive and opening assumptions available for a full reconciliation. If there is no trusted starting state, do not infer cash from stock value or fabricate missing transactions to connect an attractive ending balance to the available records.
Commit assets and duplicate receipts together
The saved state includes both assets and the processed-event map. Saving only cash and shares while keeping deduplication in memory loses the information needed to recognize an acknowledged-or-not event after restart. Saving a receipt before assets is equally dangerous: replay could then skip an event whose asset movement never committed. Treat them as one state representation.
The sample serializes an envelope containing that state and its SHA-256 digest. It writes a temporary file in the same directory, flushes and synchronizes the file, then replaces the checkpoint. The Python os.replace documentation describes replacement semantics and filesystem restrictions. This local test is not a guarantee of power-loss durability or cross-platform filesystem behavior.
The procedure assumes one writer. It does not coordinate two concurrent processes, lock an account, synchronize a distributed log or implement a production database transaction. A real implementation needs a tested storage protocol for its environment. Do not call a successful local replacement an exactly-once broker guarantee; the only assets here are independent virtual values.
Compare duplicate identity and payload
Before applying a new event, compute the digest of its canonical JSON payload. If the event identity already exists and the digest matches, return duplicate without moving cash or shares. If the identity exists with different price, quantity, fee or version, raise a conflicting-duplicate error. Blindly ignoring every repeated ID could hide a source correction or inconsistent delivery.
The Python JSON documentation explains sorted-key serialization and nonfinite-value handling. The fixture disables NaN and infinity serialization and writes its own controlled checkpoint JSON. Its loader is not a general hardened importer for arbitrary untrusted JSON; schema, size and repeated-key policies for outside files require additional validation rather than assuming a checksum provides them.
After duplicate checks, require the event’s rule version to match and its sequence to be exactly one greater than the checkpoint. Do not sort away a missing sequence or skip ahead because the later trade looks affordable. Ordering is part of the asset history. A duplicate with an existing valid receipt is safe to ignore even though its sequence is earlier than the current checkpoint.
Apply a complete event with a conservation check
For each new event, validate side, positive whole-share quantity, finite positive price and finite nonnegative fee. Proposed cash subtracts signed quantity times price and the fee; inventory adds signed quantity. Negative cash or shares reject the replay. At that event’s price, post-trade assets must equal pre-trade assets minus fee before the new sequence and receipt can be saved.
E1 buys two shares at 100 and pays one, leaving cash 799 and two shares. E2 sells one at 110 with fee one, leaving 908 cash and one share. E3 buys one at 105 with fee one, leaving 802 cash and two shares. At a separate terminal mark of 110, NAV is 1,022. These are invented amounts, not historical account performance.
The program first computes this uninterrupted reference state. It then saves and reloads the state after E1, representing a committed event whose acknowledgement may not have reached the sender. The recovery input is E1, E2, E2, E3. The statuses are duplicate, applied, duplicate, applied, and the final loaded checkpoint must exactly match the uninterrupted state including receipts.
Stop at gaps instead of manufacturing signals
Starting from E1’s checkpoint, an E2 with price=None returns quote_unknown_stop and the unchanged state. E3 cannot bridge that gap: its sequence is not the required next value and raises an error. Retain the last known cash 799 and two shares. A valuation report may be unknown without inventing a quote or pretending the missing event had no effect.
The missing-price test concerns replay inputs, not an instruction to create a strategy signal from a later quote. If signal-generation history is missing, recover the required observations and frozen rule separately or leave the interval unresolved. A future price, a current holding snapshot and a plausible action sequence do not establish what the rule actually observed during the interruption.
The other counterexamples provide E2 under v2, a sequence gap and a conflicting repeat of E2 with price 111. All stop rather than altering the known state. The saved file is also deliberately modified to set cash 9,999 while retaining its original checksum; loading rejects the corruption. A checksum detects this mismatch, not an adversary who can rewrite both content and checksum.
The digest compares the recorded representation, not economic equivalence. Price strings 100 and 100.0 can therefore produce different receipts. If a real importer normalizes representations, freeze that transformation before assigning event identities and retain the raw source. Do not add convenient normalization during recovery merely to make a conflicting duplicate disappear.
Run and inspect the recovery evidence
Download the complete example, save R645.py and run python R645.py using Python 3.12 or later. It needs only the standard library and synthetic inputs. Temporary files are created and removed within the example’s own temporary directory. No user account, persistent service directory, market download or existing application state is accessed.
Assertions compare the full recovered state with the uninterrupted reference, verify unchanged state at the missing quote, reject version/gap/conflicting duplicates and detect checkpoint corruption. The test does not terminate a process midway through every possible write or certify storage under hardware failure. It verifies the stated save/reload and idempotent replay behavior under the supplied single-writer scenario.
Hand off a recoverable observation branch
Retain the last trusted checkpoint identity, rule bytes, event archive, recovery start and stop reasons, repeated-event receipts and unresolved gaps. Record whether a checkpoint was loaded, rebuilt from a trusted earlier state or rejected. Corrected events need a new explicit revision policy; changing a payload under an old ID must not silently rewrite an existing receipt.
Before resuming new observations, reconcile cash and shares and confirm that all required prior events are represented. A resumed loop is not evidence that missing opportunities were evaluated. If the required market data or rule version remains unavailable, keep the account paused with known assets and unknown coverage. Honest stopping is preferable to a continuous-looking virtual history made from inferred actions.
FAQ
Is a repeated ID always ignored?
Only when its canonical payload matches the stored receipt. A conflicting duplicate stops replay.
Does this guarantee exactly-once live trading?
No. It tests single-writer virtual save/reload and replay. No broker order or production scheduling is involved.