Course contents
Trusting code with money
Code that places orders deserves more testing than any other code you write, because its bugs cost money directly. This chapter applies plain software-testing habits to a trading system, unit tests for the risk gate and order manager, a dry-run mode, and reproducibility, and shows a test suite catching a risk-gate bug before it could reach the market.
- Write unit tests for the critical safety code (the risk gate, the order manager)
- Use a dry-run or simulation mode to test the full system without the broker
- Apply reproducibility habits so a result can be trusted and repeated
Most code, if it has a bug, shows you a wrong number or a broken page. Code that trades, if it has a bug, spends your money. That difference is why a trading system deserves more testing than anything else you are likely to write, and why the most important code to test is not the strategy but the safety machinery: the risk gate and the order manager, the parts whose job is to prevent disasters. This chapter is about earning the right to trust code with money.
Test the safety code hardest
A unit test is a small piece of code that checks one behaviour of another piece of code, automatically, so you can run it any time and know instantly whether that behaviour still holds. For a trading system, the behaviours most worth testing are the safety ones: does the risk gate block an order that is too large, does it block one that breaches the position limit, does it reject a bad price, does the order manager refuse to send a duplicate. These are exactly the checks that stand between you and a large loss, so they are the ones you must know are correct. Here is a small test suite for the risk gate.
# Code that places orders deserves tests more than any other code you write,
# because its bugs cost money directly. Here are unit tests for the risk gate, the
# most safety-critical part of the system. Each test sets up a fresh gate, feeds
# it one case, and asserts the gate did the right thing. Then we show the same
# tests catching a deliberately broken gate.
from paper_broker import PaperBroker
from risk_gate import RiskGate
def make_gate(gate_class=RiskGate):
broker = PaperBroker(cash=1_000_000, prices={"RELIANCE": 1400})
return broker, gate_class(broker, max_order_qty=100, max_position=200,
daily_loss_limit=20_000)
def test_allows_a_valid_order(gate_class):
_, gate = make_gate(gate_class)
ok, _ = gate.check("RELIANCE", "BUY", 50, 1400)
assert ok is True, "a valid order should be allowed"
def test_blocks_an_oversized_order(gate_class):
_, gate = make_gate(gate_class)
ok, _ = gate.check("RELIANCE", "BUY", 500, 1400)
assert ok is False, "an order bigger than the max should be blocked"
def test_blocks_a_position_breach(gate_class):
_, gate = make_gate(gate_class)
gate.send("RELIANCE", "BUY", 100) # position now 100
ok, _ = gate.check("RELIANCE", "BUY", 150, 1400) # would reach 250 > 200
assert ok is False, "an order breaching the position limit should be blocked"
def test_rejects_a_bad_tick(gate_class):
_, gate = make_gate(gate_class)
ok, _ = gate.check("RELIANCE", "BUY", 1, 2000) # 2000 is far from 1400
assert ok is False, "a price far from the last should be rejected"
TESTS = [test_allows_a_valid_order, test_blocks_an_oversized_order,
test_blocks_a_position_breach, test_rejects_a_bad_tick]
def run(gate_class, label):
print(f"Running the suite against {label}:")
passed = 0
for test in TESTS:
try:
test(gate_class)
print(f" PASS {test.__name__}")
passed += 1
except AssertionError as error:
print(f" FAIL {test.__name__}: {error}")
print(f" {passed}/{len(TESTS)} passed\n")
# A broken gate that forgot to enforce the order-size limit.
class BuggyRiskGate(RiskGate):
def check(self, symbol, side, quantity, price):
return True, "ok" # BUG: always allows, checks nothing
run(RiskGate, "the real risk gate")
run(BuggyRiskGate, "a buggy gate (size check removed)")
print("The tests pass on the correct gate and catch the bug in the broken one.")
print("That is why safety code is tested before it ever trades real money.")Running the suite against the real risk gate: PASS test_allows_a_valid_order PASS test_blocks_an_oversized_order ALLOWED BUY 100 RELIANCE: FILLED PASS test_blocks_a_position_breach PASS test_rejects_a_bad_tick 4/4 passed Running the suite against a buggy gate (size check removed): PASS test_allows_a_valid_order FAIL test_blocks_an_oversized_order: an order bigger than the max should be blocked ALLOWED BUY 100 RELIANCE: FILLED FAIL test_blocks_a_position_breach: an order breaching the position limit should be blocked FAIL test_rejects_a_bad_tick: a price far from the last should be rejected 1/4 passed The tests pass on the correct gate and catch the bug in the broken one. That is why safety code is tested before it ever trades real money.
Read the two runs. Against the real risk gate, all four tests pass: it allows a valid order and blocks the oversized order, the position breach, and the bad tick. Then the same tests are run against a deliberately broken gate, one whose check was gutted to always allow, and three of the four fail at once. That is the whole value of tests in one picture. The tests did not just confirm the good gate; they caught the broken one immediately, before it could ever reach a real order. A bug in the risk gate found by a test costs nothing. The same bug found in live trading costs money.
Test the whole system without the broker
Beyond unit tests on the parts, you want to exercise the whole system without risking anything, and you already have the tool: the simulated broker. Running the complete strategy, order manager, and risk gate together against the simulator is a full-system test, a dry run that behaves like live trading but touches no money. This is the forward test from earlier, seen as a testing habit. Before any change goes live, run the whole system against the simulator and confirm it still behaves. A dry-run mode, where the system does everything except send real orders, is one of the most useful safety features you can build.
Make results reproducible
One more habit separates trustworthy work from luck: reproducibility. A result you cannot reproduce is a result you cannot trust. Seed your random numbers, as the overfitting chapter did, so a test gives the same answer every time. Pin the versions of the libraries you depend on, so an update does not silently change your results. Record the data and the settings behind any backtest, so you or someone else can run it again and get the same numbers. Code that touches money should be code whose behaviour you can repeat on demand, not code that worked once and you are now afraid to touch.
What to carry forward
A bug in ordinary code shows a wrong number; a bug in trading code spends money, which is why the safety machinery, the risk gate and order manager, deserves the hardest testing. Unit tests check each safety behaviour automatically, and you saw them pass on a correct risk gate and catch a gutted one before it could trade. Dry-run the entire system against the simulated broker before any change goes live, and keep results reproducible with seeds, pinned versions, and recorded settings, so the behaviour you trust is behaviour you can repeat. With the system tested, the last two chapters step back to the honest questions: what algo trading can realistically do for you, and how to put it all into practice.