The research question
What survives after the cost of entering and exiting? In our introduction, we committed to separating a rising chart from realized profit. This lab makes that distinction concrete with four artificial, independently closed paper outcomes.
The point is accounting, not a trading signal. Every example begins with a hypothetical entry notional of 100 units and ends at an invented exit price. There is no order routing, broker account, leverage, or market feed. An executable simulation would need much more detail, including size-specific liquidity, spread, partial fills, and rejected orders.
Time: approximately 25 minutes. Prerequisite: the Python workspace from Edition 2. This script contains its own data, so the earlier CSV is not required.
Step 1 — Freeze the accounting assumptions
Before calculating anything, write down the model:
- Each example has an entry price of 100 and an entry notional of 100 currency units.
- There are four separate completed examples; they are not four marks of the same open position.
- Fees are 10 basis points per side and slippage is 20 basis points per side.
- We approximate all costs using entry notional. One basis point is one hundredth of a percent, so the round-trip allowance is 60 basis points, or 0.60 currency units per example.
This fixed allowance is intentionally simple. A real cost model can charge exit fees on exit value and model slippage as a change in fill price. Do not add the same slippage both to a fill price and again as an explicit cost: that would double count it.
Step 2 — Build the ledger
Save this script as lab_03.py and run it using your environment's Python interpreter. Decimal values are created from strings to keep this teaching arithmetic explicit.
from decimal import Decimal
D = Decimal
entry = D("100")
notional = D("100")
units = notional / entry
fee_bps_per_side = D("10")
slippage_bps_per_side = D("20")
round_trip_bps = 2 * (fee_bps_per_side + slippage_bps_per_side)
exits = [D("102"), D("99"), D("101"), D("100.20")]
ledger = []
for number, exit_price in enumerate(exits, start=1):
gross = units * (exit_price - entry)
estimated_cost = notional * round_trip_bps / D("10000")
net = gross - estimated_cost
ledger.append({"id": number, "gross": gross,
"cost": estimated_cost, "net": net})
print(f"close={number} gross={gross:.2f} cost={estimated_cost:.2f} net={net:.2f}")
assert len({row["id"] for row in ledger}) == len(ledger)
assert sum(row["net"] for row in ledger) == D("-0.20")
print(f"gross_total={sum(row['gross'] for row in ledger):.2f}")
print(f"net_total={sum(row['net'] for row in ledger):.2f}")
print(f"gross_wins={sum(row['gross'] > 0 for row in ledger)}/4")
print(f"net_wins={sum(row['net'] > 0 for row in ledger)}/4")
print("mode=synthetic_paper no_orders_sent")
Expected output:
close=1 gross=2.00 cost=0.60 net=1.40
close=2 gross=-1.00 cost=0.60 net=-1.60
close=3 gross=1.00 cost=0.60 net=0.40
close=4 gross=0.20 cost=0.60 net=-0.40
gross_total=2.20
net_total=-0.20
gross_wins=3/4
net_wins=2/4
mode=synthetic_paper no_orders_sent
Three gross winners sound encouraging. The same fixture has two net winners and a negative total after our chosen costs. A win-rate headline leaves out both the size of wins and losses and the expense of getting them.
Step 3 — Run a sensitivity experiment
Copy the file before changing it. In the copy, test slippage assumptions of 0, 20, and 40 basis points per side. Replace the fixed -0.20 assertion with an assertion appropriate to each deliberately changed scenario. Keep the original version untouched as your baseline.
For these four examples, the corresponding net totals are 1.40, -0.20, and -1.80. These are not predictions. They demonstrate how a result depends on a cost assumption. In your notes, label every number with its cost scenario so you never compare a cheap baseline with an expensive challenger by accident.
Step 4 — Identify missing evidence
Create a short list headed Not modeled. Include executable depth, actual bid/ask spread, latency, partial fills, fees charged on exit value, position overlap, and the chance of an unsuccessful exit. If sellability or cost evidence is missing in a real observation, mark it unknown rather than substituting zero.
Do not count an unsold position's favorable mark as a completed outcome. Keep open marks, simulated closes, and verified funded fills in separate categories. This journal's examples remain in the simulated category throughout.
Troubleshooting
If the assertion fails before you intentionally change assumptions, compare the four exit values and the per-side cost values with the example. If you see a Decimal type error, avoid mixing decimal objects with float literals. If the output says four wins, check whether you used >= 0 or counted gross instead of net values.
Evidence and limits
Four hand-selected outcomes teach an accounting identity. They cannot estimate a strategy's future return, win probability, or drawdown. The fixed size and simplified cost allowance are useful because we can inspect them; they are also reasons not to treat the result as market evidence.
Completion check: Explain why the fourth example changes from a gross winner to a net loser, then reproduce all three cost scenarios.
Next edition: Freeze the rule before you judge it.
Primary reference
- Python Decimal documentation describes decimal arithmetic and why constructing decimals from strings differs from constructing them from binary floats. The ledger and scenarios above are original synthetic examples.