Skip to content
Course contents
Live risk, operations, and the honest close

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.

9 min readChapter 26 of 28
What you will learn
  • 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

Test code that trades from the bottom up: many small unit tests, then integration, then paper and forward tests, and test the safety code hardest.
Test code that trades from the bottom up: many small unit tests, then integration, then paper and forward tests, and 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.

ExampleUnit tests for the risk gate, and the same tests catching a bugch26/test_risk_gate.py
# 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.")
Output
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.