Last Updated: 2026-10-26
This is a runnable paper-only control fixture, not a ready-to-trade bot. It sends no orders, reads no account and needs no API key. Its purpose is to demonstrate three failures that a restart loop can otherwise hide: unfinished data, duplicate events and a loss halt that disappears when the process restarts. The Python example uses only the standard library.
Separate the jobs before choosing a provider
Scroll sideways to see every column. Keyboard: focus the table region and use the arrow keys.
| Layer | Input and output | Question the test must answer |
|---|---|---|
| Data | Timestamped observations | Is this bar closed, fresh and from the intended market? |
| Signal | Versioned rule and available data | Was the signal knowable before the assumed fill? |
| Risk | Equity, exposures and pending orders | What stops new actions, and does that state survive restart? |
| Execution | Intent, request ID and venue response | Was an order rejected, filled, partially filled or left unknown? |
| Reconciliation | Orders, fills and balances | Does recorded state agree with the venue after interruption? |
The fixture below covers only part of the risk/data boundary. It does not implement a signal or execution layer. A moving average crossing is not evidence of a profitable strategy; custom code does not create an advantage merely by being custom.
Run a reproducible local example
Save the code as paper_control.py and run python paper_control.py. The demonstration creates its own temporary synthetic state file. Expected statuses are recorded_only, halted, halted; orders_sent is zero in every result. It records a fictional $1,000 peak, observes $890 and retains the halt when a later sample says $1,100.
"""Offline, single-process teaching fixture. No exchange or order calls."""
import json
from decimal import Decimal, InvalidOperation
from pathlib import Path
def positive(value):
if isinstance(value, bool):
raise ValueError("boolean is not a numeric input")
try:
number = Decimal(str(value))
except (InvalidOperation, ValueError):
raise ValueError("invalid numeric input") from None
if not number.is_finite() or number <= 0:
raise ValueError("finite positive number required")
return number
def inspect(path, sample, now):
path = Path(path)
state = json.loads(path.read_text()) if path.exists() else {
"halted": False, "peak": "1000", "seen": []
}
# Corrupt/unknown saved state raises instead of silently resetting the halt.
if (type(state.get("halted")) is not bool
or not isinstance(state.get("seen"), list)
or not all(isinstance(x, str) for x in state["seen"])):
raise ValueError("invalid saved state")
peak = positive(state["peak"])
if state["halted"]:
return {"status": "halted", "orders_sent": 0}
stamp = sample.get("closed_at")
if (type(now) is not int or type(stamp) is not int
or not 0 <= now - stamp <= 10):
return {"status": "stale_or_future", "orders_sent": 0}
if sample.get("closed") is not True:
return {"status": "bar_not_closed", "orders_sent": 0}
event = sample.get("event_id")
if not isinstance(event, str) or not event:
return {"status": "missing_event_id", "orders_sent": 0}
if event in state["seen"]:
return {"status": "duplicate", "orders_sent": 0}
equity = positive(sample.get("equity"))
peak = max(peak, equity)
state["peak"] = str(peak)
# 10% is a toy boundary, not a recommended account-risk limit.
state["halted"] = equity <= peak * Decimal("0.9")
state["seen"].append(event)
temporary = path.with_name(path.name + ".pending")
temporary.write_text(json.dumps(state))
temporary.replace(path)
# A failed write raises before any accepted result is returned.
return {"status": "halted" if state["halted"] else "recorded_only",
"orders_sent": 0}
if __name__ == "__main__":
import tempfile
with tempfile.TemporaryDirectory() as folder:
saved = Path(folder) / "synthetic-state.json"
base = {"closed": True, "closed_at": 100}
print(inspect(saved, dict(base, event_id="a", equity="1000"), 101))
print(inspect(saved, dict(base, event_id="b", equity="890"), 102))
# A new call reloads the persisted halt even after equity recovers.
print(inspect(saved, dict(base, event_id="c", equity="1100"), 103))
The ten-second freshness allowance and 10% boundary are arbitrary fixture values. They are not a trading schedule or suitable loss limit. A later process call loads the halt from disk; restarting the program's decision function does not clear it. The small demonstration uses a new temporary folder each run so it cannot touch an existing account record.
Test refusals, not only the happy path
Try an open bar, a timestamp in the future, an old timestamp and the same event ID twice. Each should produce a named refusal without a new action. Nonfinite or nonpositive equity must fail validation. A malformed saved state must raise, rather than becoming a fresh account. If the file cannot be written, the function raises before reporting a recorded decision.
The example assumes a single process and a single writer. It is not a transactional event ledger, does not bound state growth and does not establish power-loss durability or concurrent-writer safety. It has no reset API. A real operating procedure would require a deliberate, recorded reconciliation before clearing a halt; a service's Restart=always setting is not that procedure.
What remains before any execution integration
- Finished data: use the provider's actual candle-close semantics. The last returned OHLCV row can still be open. Backtests must not use a final close to assume an earlier same-price fill.
- Order identity: persist an intent and client identifier before a request. After a timeout, query and reconcile its status before retrying. A timeout is not proof that no order exists.
- Quantities and costs: validate the market's precision, minimum quantity and minimum notional. Six decimal places is not a universal exchange rule. A $20 fill at 0.1% costs $0.02 before other expenses.
- Open risk: stopping the process does not cancel outstanding orders or close positions. Cancellation can also have an uncertain or partial result. Track those states separately.
- Authority: a trade-enabled key without withdrawals can still lose funds through orders. Separate read-only access from execution and expose only the capability a component needs. Never put a secret in an article, repository or test fixture.
- Evaluation: retain held-out periods, all tested variants, fees, spread, funding and missing-data treatment. A testnet verifies selected API behavior; it does not reproduce live liquidity, fills or profitability.
Do not convert a fixed number of paper days into automatic permission to increase capital. Record the actual integration, operational and strategy evidence still missing. Hosting, data, monitoring and maintenance can cost money and time even when the code is free. This guide provides no expected date of profitability.
For the reader-level order and loss arithmetic, see the beginner reference. For a separate prediction-market example, the paper payoff checker validates stated inputs without forecasting an outcome.
Educational engineering example. No live trading system, account protection guarantee or investment recommendation is supplied. AITR may earn commissions from other commercial links on the site; that does not validate a bot.