A real-time system has no time to be surprised by its own code. Before you write a line of it, you name the objects, name the messages between them, and draw the picture — because at 3 p.m. on a live session, the diagram is the only thing that will tell you where to look.
@abstractmethod.arena.Trader and draw its UML
HW1: UML of all three abstract bases + a Level-1 bot
The diagram is a deliverable, not a doodle
Object-oriented design is not about classes. It is about deciding what each part of the system is allowed to know. In a real-time system that decision has teeth: the boundaries you draw are the places you can later put a queue, a timeout, a retry, a metric, or a test — and the places you did not draw are the places you will later be unable to instrument.
1. What are the nouns? Exchange, order book, order, trade, broker, trader, portfolio, tick. Each becomes a type with a single job and a name you would say out loud in a design review.
2. What crosses between them? Every arrow in your diagram is a message with a schema, and every message is a place where the system can be late, malformed, duplicated or lost. Objects are cheap; the arrows are where the engineering is.
Not for ceremony. For three concrete reasons: a class diagram fits on one page and a codebase does not; the arrow types force you to say whether a relationship is ownership, use, or kinship, which is exactly the decision people fudge in code; and a diagram is the artefact you hand a teammate at 3 p.m. when their bot has stopped filling.
You will draw three diagrams this quarter and each one has to survive contact with a live system. That is the test.
The static shape: types, their members, and how they relate. Answers “what exists, and who owns whom”. This is the week’s deliverable.
The dynamic shape: who sends what to whom, in what order, over time. Answers “what happened to my order”. Part 3 below animates one.
A class diagram with no sequence diagram hides the latency; a sequence diagram with no class diagram hides the ownership. Real-time work needs the pair.
Add types, then join them with the five UML relationships that matter. The arrowhead is the argument: a hollow triangle claims kinship, a filled diamond claims ownership and a lifetime, a dashed arrow claims nothing but use. Press Load the arena SDK to see the diagram you will be asked to draw in the lab, then take it apart.
The four pillars are usually taught as vocabulary. Here each one is a load-bearing piece of a live system: pick a pillar, read what the arena does with it, and toggle between the version that has it and the version that does not.
This is the Strategy pattern, and it is the reason the arena
engine never contains an if strategy == "momentum". Four bots subclass the same base and
implement the same single method. The loop below calls that method and nothing else — yet the equity
curves fan out. Change the market regime and watch which behaviour was actually being rewarded.
| Strategy | P&L | Trades | Max DD | Sharpe | What it believes |
|---|
# The engine. It has never heard of momentum. for tick in feed: for bot in self.bots: # list[Trader] sig = bot.on_tick(market, portfolio) if sig is not None: self.exchange.submit(sig) # Adding a fifth strategy touches zero lines above.
Here is the payoff, and it is a real-time payoff. One order crosses six objects and five
message types on its way from on_tick to a changed number in your portfolio. Every hop is a
place the design either lets you measure and test, or does not — and the total is the tick-to-ack
latency you will be graded on in week 3.
Nothing has happened yet. The bot is inside on_tick,
holding a Signal it has not sent.
Every one of these has shown up in a real submission. Open them.
The tell is a base class with eleven methods, of which your subclass overrides two and inherits
nine it does not want. class MyBot(Trader, RiskManager, Logger, ReconnectMixin) is not a
design, it is a pile — and now the method resolution order decides your behaviour.
Inheritance says is a kind of, and it is a promise to callers that your object is substitutable for the base everywhere. If that promise is false, every polymorphic call site is a latent bug.
Fix: composition. self.risk = RiskManager(...) and call it. Inherit only
to be substitutable; hold an object to reuse code. Ask: “would the engine be right to pass my
object anywhere it passes the base?”
class Trader: with a def on_tick(self): pass is not a contract, it is a
suggestion. Forget to implement it and you get silence: no exception, no signal, and a bot that
politely does nothing for a whole session while you read the wrong logs.
ABC plus @abstractmethod turns that into a TypeError at
construction — before the session opens, in the terminal, with your name on it.
Fix: class Trader(ABC) and exactly one
@abstractmethod on_tick. Everything else gets a real default so an empty subclass is
already a legal, running bot. Fail at import, never at 3 p.m.
One 900-line TradingSystem class. It works, briefly. Then you cannot unit-test the
matching logic without opening a socket, cannot measure decision latency separately from network
latency, and cannot replay a session because the parsing and the deciding are the same method.
This is the pitfall with the highest cost in this course, because weeks 3, 6 and 9 all ask you to instrument or replay a stage in isolation. If the stages are not objects, they are not measurable.
Fix: one responsibility per type, and put pure logic (matching, signals, parsing)
behind an interface with no I/O in it. shared/orderbook.py is the model: no sockets, no
clock, 100% testable.
def __init__(self, symbols=[]): evaluates that list once, at
function definition. Every bot you construct then shares it. The same trap with class-level
attributes: class Bot: positions = {} gives every instance the same dict, so two bots in
one process silently trade each other’s book.
It survives testing because tests usually build one object.
Fix: symbols=None then
self.symbols = list(symbols or []), and every piece of per-instance state assigned inside
__init__. If two of your bots run in one process, this bug is waiting for you.
A UML diagram is a claim about the code, and a stale claim is worse than none: someone will trust
it during an incident. Six weeks in, the diagram says Broker → Exchange and the
code has grown a router in between.
Fix: keep the diagram in the repo next to the code it describes, regenerate or redraw it whenever a boundary moves, and make the design review read the diagram against the imports. In this course the diagram is a graded artefact for exactly that reason.
Three questions, instant feedback, nothing recorded.
Q1Your bot needs rate limiting. You already have a tested RateLimiter class. What is the right relationship?
Q2You are designing the trader base class. Nine hooks look useful. How many should be @abstractmethod?
Q3The exchange creates one OrderBook per symbol, holds them for its whole life, and they are destroyed with it. Which UML arrow?
arena.Trader from the code itself — inspect.getmembers,
__abstractmethods__, __mro__ — and write down what the contract
actually is.Trader, Broker,
Exchange.Trader declares exactly one
@abstractmethod.AI-USE note on the deliverable.make install.