63 lines
3.1 KiB
Markdown
63 lines
3.1 KiB
Markdown
# Trade Ledger: Debugging Exercise
|
|
|
|
**Time:** 60 minutes **Language:** Python 3.10+ (standard library only) **AI:** Allowed (Codex in VS Code)
|
|
|
|
## Files
|
|
|
|
| File | Purpose |
|
|
|---|---|
|
|
| `logger.py` | A small logger that buffers records and writes them to a stream |
|
|
| `ledger.py` | Trades, positions, and the ledger that applies trades, rebuilds from history, and reconciles |
|
|
| `main.py` | Replays a sample trade list (a "blotter") through a live ledger, then does an end-of-day (EOD) rebuild and reconcile |
|
|
| `test_ledger.py` | The existing test suite |
|
|
|
|
```bash
|
|
python3 main.py
|
|
python3 -m unittest -v
|
|
```
|
|
|
|
## The ticket
|
|
|
|
> **From:** Trading Ops
|
|
> **Priority:** High
|
|
>
|
|
> The end-of-day reconcile is reporting breaks between the live ledger and the EOD rebuild,
|
|
> and some desks say their average prices look wrong after big sells. We checked the logs
|
|
> and found no errors, and the log lines don't seem to match the trades they're about.
|
|
> CI is green.
|
|
|
|
## Your tasks
|
|
|
|
1. **Clarify first.** Before you change any code, tell the interviewer which assumptions you are making, and ask about any that are unclear.
|
|
2. **Reproduce** the reported behaviour.
|
|
3. **Find and fix the 4 bugs** in the codebase. For each one, explain the root cause, not only the symptom.
|
|
4. **Review the test suite.** CI is green even though the code is broken. Explain why.
|
|
5. **Write your own tests.** Each bug needs at least one test that fails before your fix and passes after it.
|
|
6. **Talk through your trade-offs** as you go.
|
|
|
|
## Given assumptions
|
|
|
|
1. A `Trade` has `trade_id`, `symbol`, `side` (`"BUY"` or `"SELL"`), `qty`, and `price`.
|
|
2. `qty` on a trade is always entered as a positive whole number. The side gives the direction.
|
|
3. A trade with a qty or price of zero or less, or an unknown side, is invalid. It must be rejected and logged at `ERROR`.
|
|
4. Positions use **weighted-average cost**.
|
|
5. Realized P&L is booked only when a position is reduced.
|
|
6. `Ledger.history` is the record of truth for every accepted trade. The EOD job rebuilds a fresh ledger from it.
|
|
7. The two ledgers reconcile when every symbol has the same `qty` and `avg_price`.
|
|
8. Every log line must name the trade it is about.
|
|
9. A logger only writes messages at or above its configured level.
|
|
10. Single-threaded. You don't need to handle persistence, partial fills, or fees.
|
|
|
|
## Assumptions you should clarify with the interviewer
|
|
|
|
These are left open on purpose. Asking about them is part of the exercise.
|
|
|
|
- Is short selling allowed? Can one trade move a position from long, through zero, to short?
|
|
- When a position flips from long to short, or short to long, what should `avg_price` be on the new side?
|
|
- Is a `Trade` object safe to modify once it has been submitted, or should it be treated as immutable?
|
|
- Should a rejected trade appear in `history`?
|
|
- Should the EOD rebuild stop on the first bad trade, or skip it and carry on? Either way, how should it report the problem?
|
|
- Can records be lost if the process exits before `flush()` is called? Does that matter here?
|
|
- Can level names be given in lowercase? Should custom levels be supported?
|
|
- Is a tolerance of `1e-9` right for comparing prices?
|