Analysis through the lens of A Philosophy of Software Design (Ousterhout). Each issue is tagged with the relevant chapter. Issues are ordered by priority.
File: crobat/orderbook.py
Method: history.last_canceled_order
Chapter: Ch. 2 — Complexity (unknown unknown)
The method searches for "insertion" instead of "cancelation". It is a
copy-paste of last_inserted_order with the name changed but not the logic.
Any caller using this method gets insertion events, not cancellations.
The token flag has a hidden cross-module contract
Files: crobat/orderbook.py, crobat/recorder.py
Chapter: Ch. 2 — Unknown unknowns
self.hist.token is a boolean that gates whether an order book event is
recorded. Its lifecycle spans two modules:
recorder.pyresets it toFalsebefore each update (self.hist.token = False)- Multiple methods inside
historyset it toTrueorFalsedepending on what they find trim_coordinatormakes the final decision
A reader of history cannot understand token without reading recorder.py.
A reader of recorder.py cannot understand why it resets token without
reading history. This is a hidden contract through a mutable flag.
Fix direction: Document the contract explicitly in both files. Long-term, the flag should be local to the update sequence, not a persistent attribute on the history object.
File: crobat/filesave.py
Functions: pd_csv_save, pd_pkl_save, pd_excel_save
Chapter: Ch. 18 — Obviousness
Every save function does hist_obj_df = hist_obj_df[1:] after creating the
DataFrame. No comment explains why. The first row is silently dropped.
The likely reason: the first history entry is the snapshot initialisation state, not an event, so it shouldn't appear in the output. But this is not stated anywhere. A developer reading this will stop and wonder if it's a bug.
Fix direction: Add a one-line comment. If the reason is the snapshot row, consider filtering it at the history level instead of the export level.
Files: crobat/orderbook.py, crobat/orderbook_helpers.py, crobat/filesave.py
Chapter: Ch. 5 — Information Hiding
The Cont, Kukanov and Stoikov (2011) signed order book convention — negative positions for bids, positive for asks, bid volumes negated — appears in:
orderbook.pyinitialize_snap_events: constructssnapshot_signedby negating and reversing bidsorderbook_helpers.pyset_sign/set_signed_position: implements the sign rulesfilesave.pyconvert_array_to_list_dict_sob: re-implements the position mapping withbisect
This is one piece of domain knowledge living in three files. Changing the convention requires touching all three.
Fix direction: The convention belongs in orderbook_helpers.py. The other
two files should call helpers rather than re-implementing the logic.
File: crobat/recorder.py
Methods: _handle_l2, _handle_market_trades, _handle_ticker_quotes
Chapter: Ch. 5 — Information Hiding
Coinbase field names ("price_level", "new_quantity", "event_time",
"BUY", "SELL") are hardcoded across three methods. If Coinbase renames a
field, three methods need updating.
Fix direction: Concentrate field name knowledge in one place — either
constants at the top of recorder.py or a small private parsing function per
channel that extracts the values before the handler logic runs.
File: crobat/orderbook.py
Methods: append_snapshot_bid, append_snapshot_ask, append_signed_book
Chapter: Ch. 9 — Splitting and Joining
All three methods compute:
mid_price = 0.5 * (self.bid_range[0] + self.ask_range[0])
spread = self.ask_range[0] - self.bid_range[0]These are derived values. Changing the spread formula requires updating three
methods. They should be computed once, either as properties on history or
extracted into a helper.
Fix direction: Add @property methods mid_price and spread to
history that compute from bid_range[0] and ask_range[0]. The three
append methods call the properties.
File: crobat/filesave.py
Function: filesaver
Chapter: Ch. 4 — Deep Modules
filesaver takes sides, filetype, and output_dir as **kwargs. This
means:
- No type checking at the call site
- Misspelling
'filetype'as'file_type'silently writes nothing - The function prints
"no sides specified"instead of raising — callers cannot detect the failure programmatically
Fix direction: Make sides and filetype explicit keyword arguments with
no defaults (forcing the caller to be explicit), and output_dir an explicit
kwarg with a default of '.'.
Files: crobat/orderbook.py, crobat/recorder.py
Functions: UpdateSnapshot_bid_Seq, UpdateSnapshot_ask_Seq,
update_level_depth, update_price_index_buy, update_price_index_sell
Chapter: Ch. 9 — Splitting and Joining
pre_level_depth is initialised to 0 in recorder.py, passed into
UpdateSnapshot_*_Seq, threaded through update_level_depth and
update_price_index_*, and returned — but the returned value is never read by
any caller. It adds a parameter to five function signatures for no benefit.
Fix direction: Remove the parameter from all five signatures. The value it
was meant to capture (the volume before an update) is already available as
snap_array[update_index][1] inside update_level_depth if it's ever needed.
File: crobat/orderbook_helpers.py
Function: set_sign
Chapter: Ch. 9 — Splitting and Joining
The function signature is set_sign(event_size, side, order_type) but
event_size is never referenced in the body. It was kept "for interface
consistency" but adds noise to every call site.
Fix direction: Remove the parameter. Update all call sites (two: in
append_signed_book in orderbook.py).
| Issue | Type | Impact | Effort |
|---|---|---|---|
last_canceled_order wrong search term |
Bug | High | Trivial |
filesaver kwargs → explicit params |
Shallow interface | Medium | Small |
[1:] slice — document or fix |
Unknown unknown | Low | Trivial |
token flag — document the contract |
Unknown unknown | High | Small |
pre_level_depth — remove dead param |
Dead code | Medium | Small |
event_size in set_sign — remove |
Dead code | Low | Trivial |
| Spread/mid-price duplication | Change amplification | Medium | Small |
| Signed convention consolidation | Information leakage | Medium | Medium |
| Coinbase field names — concentrate | Information leakage | Medium | Small |