Track cash and positions in a simulated stock account
A simulated stock account needs an asset ledger, not merely a sequence of buy and sell labels. A rule can request an unaffordable purchase, a sale can exceed available shares, and a price update can change account value without moving any cash. Keep these events distinct so every accepted virtual transaction can be reconstructed from the preceding state.
This guide applies synthetic events to one USD stock with whole shares, independent opening cash and explicit fees. It uses no real account access, market download or broker connection. The persistent virtual-ledger method is an external research extension; successful execution of this example does not establish that CoTrading or a reference batch shadow backtest maintains a continuously updated account.
Define the opening assets and event contract
The account opens with 1,000 cash and zero shares. That capital is a separate experiment assumption, not inferred from winning journal trades or the market value of real holdings. Freeze the rule identity, basket, currency, start boundary and execution convention alongside it. This sample receives already prepared event requests and does not generate a strategy or decide when a model should run.
Each event has a unique identity, side, positive integer quantity, positive finite price, nonnegative finite fee and a confirmation field. The fixture accepts only buy or sell and the declared filled or unfilled inputs. Unknown directions and confirmation states raise errors rather than becoming zero trades. The function copies the prior state, making unsuccessful application inspectable without partially mutating the caller’s cash or shares.
The input word filled is a proposed virtual confirmation within this fixture, still subject to cash and inventory feasibility before commitment. It is not a broker execution statement. A real confirmed broker fill cannot simply be rejected because a local cash model disagrees; such a discrepancy requires reconciliation outside this simulation. Keep that distinction explicit when adapting a research ledger.
Separate cash, inventory and valuation
For a buy of q shares at p with fee f, candidate cash becomes cash minus q*p minus f, and shares increase by q. A sale uses the opposite signed share quantity, subtracting the fee in either direction. Commit only when both resulting cash and shares are nonnegative. The example does not borrow, short, split fills or maintain unsettled balances.
Net asset value at a mark is cash plus shares multiplied by that mark. It is not available buying power. Six shares marked at 110 can increase NAV even though the same 399 cash remains in the account. Conversely, paying a fee reduces NAV without necessarily changing the quoted price. Keep the mark timestamp and the transaction timestamp separately in a fuller implementation.
The Python Decimal documentation supports decimal-string arithmetic and finite-value checks. Decimal itself can represent NaN and infinity, so the sample explicitly rejects those inputs before sign comparisons. Using a monetary type alone does not make an invalid quote safe, and a missing cost must not silently become a real-world zero fee.
Reconcile each accepted transition at one price
Compute pre-event NAV using the event’s supplied price, then compute post-event NAV using exactly the same price. For a committed trade, the difference must be minus the fee. For an unfilled or rejected event, it must be zero. This isolates the bookkeeping identity from intervening market movement and detects a sign error in cash even if later price changes conceal it.
Price differences between rows remain valuation changes. Do not compare the previous row’s NAV at 105 directly with a new row’s NAV at 90 and assign the entire difference to an order that was never filled. A trace should show the new mark, the before-trade asset value, the accepted execution and the resulting assets. These are related records with different meanings.
The SEC fees bulletin explains that costs reduce portfolio value and recommends reviewing statements and confirmations. Our one-unit-per-trade fee is invented for arithmetic clarity. It is not a quoted commission schedule, a complete tax model or an assertion that every market charges the same amount.
Follow the four-event main ledger
E1 proposes buying six shares at 100 with fee one. It is affordable, so cash becomes 399 and inventory six. At that transaction price NAV is 999, exactly the opening 1,000 less the fee. The program asserts both assets and status; printing the ending amount without an assertion would not verify the money movement.
E2 requests another purchase but is unfilled at mark 105. Cash remains 399 and inventory six, with NAV 1,029 from valuation alone. E3 proposes buying five at 90 with fee one. It needs 451 cash but has only 399, so the status is insufficient_cash. Shares and cash stay unchanged, although the new mark gives NAV 939.
E4 sells two of the six shares at 110 and pays fee one. Cash increases by 219 to 618, shares fall to four, and same-price NAV is 1,058. Before this trade those assets were worth 1,059 at 110; the one-unit difference is the fee. Finally mark the remaining four shares at 105: terminal NAV is 1,038, with no extra trade or cash transfer.
These events never spend the later sale proceeds to finance E3. A model allowing sell-before-buy processing must still advance assets in the actual confirmation order and check when proceeds become available. Reordering this frozen sequence after seeing the result would create a different experiment, rather than repair the original ledger.
Retain nonexecution reasons without changing assets
An unfilled request and an unaffordable virtual execution proposal are different outcomes. Neither changes shares. Record their identities and reasons rather than collapsing them into a successful zero-quantity transaction. This allows later review to determine whether the rule stayed inactive, lacked funds or failed to receive a fill. A single trade count cannot answer those questions.
The independent counterexample attempts to sell five when only four remain. It returns insufficient_shares and leaves cash 618 and inventory four. This is an inventory constraint, different from E3’s funding constraint. The program also rejects a duplicate E1 identity and a new request with a NaN price, so a repeated or invalid record cannot be counted as another valid movement.
Duplicate detection here raises an error; it is not the complete idempotent recovery protocol. A recovery system must also compare the repeated payload and persist receipts with the account state. Keep that extension separate from this ledger’s arithmetic. Suppressing every repeated identity without checking its contents could hide a corrected quantity or a conflicting execution price.
An acceptance handoff should separate the asset ledger from its presentation. Compare recorded quantities and cash first, then reproduce each displayed valuation from its mark. A changed chart scale cannot repair incorrect shares, and an unchanged ending NAV cannot prove all transitions were correct. Preserve rejected requests next to accepted ones so a reviewer can reconcile the total event count without inferring missing rows.
Run the account example and preserve its limits
Download the complete example, save R641.py and run python R641.py with Python 3.12 or later. It uses only the standard library and synthetic events. Assertions cover all four main states, every same-price bridge, terminal value 1,038, overselling, duplicate identity and nonfinite money. The output includes both cash and shares on each row.
The sample’s whole-share, single-currency, immediate-settlement convention must travel with the result. Real stock lots, settlement rules, partial fills, dividends and corporate actions require additional contracts and independently checked inputs. Do not silently extend this toy ledger to them, or present its terminal value as a historical strategy return. It demonstrates accounting behavior under declared assumptions.
For an ongoing observation handoff, retain the last trusted state, all event requests, confirmation outcomes, fees and marks. If an input is corrected, replay from a known earlier state under a new recorded revision rather than editing only the final balance. Missing valuation prevents a complete NAV report; it does not authorize filling a price from the future. Cash and inventory can remain known while valuation is explicitly unknown.
FAQ
Does a buy signal immediately reduce cash?
Only a feasible committed virtual fill changes this ledger. An unfilled request leaves cash and shares unchanged.
Can a higher NAV finance a purchase automatically?
No. Existing stock value is separate from cash. The example neither borrows against holdings nor assumes an unconfirmed sale.