2021-07-30 / exchange,server,java / Tom
Reveling in Sam's guilt at having started what was supposed to be a joint project without me, I set about making an obviously superior version of the exchange in Java. Despite all of our chats about design patterns and code architecture I envisaged creating a simple
Java app primarily focussing on the domain model to get my eye in as it were. At the time I hadn't got a clue what an exchange does (I still actually dont, but I have more questions...) and thought that it would be a quick afternoon project at most.
Version 1 was actually exactly what I envisaged. Orders of a given stock could be made and would be fulfilled by a fairly arbitrary fulfillment algorithm. Coupling between components was fairly tight but given the restricted domain and the low functionality this didn't seem too bad. The one design pattern I bothered with was the strategy pattern. The Market class, responsible for accepting orders and fulfilling them somehow used a fullment strategy. I provided one, admittedly bad, strategy by default but the pattern seemed appropriate. When instantiating a Market you provided some concrete strategy that the market would use to match orders. I'm still not sure whether this is better or worse than something like the template method pattern. Strategy would let you change strategy at runtime which is not something that an exchange is likely to want to do. A template method approach would lead to creating Market subclasses that dealt with accepting orders in a different way. I tend to prefer delegation over inheritance, so maybe this will come clear in the future.
Pleased with how version 1 had gone I set about separating concerns and inserting interfaces where they seemed appropriate. The hashmap used to stored the orders/stocks moved to an InMemMarketRepository class that implemented a MarketRepository interface. This way the Market (soon to be MarketService) accesses the actual market data via an interface. This lets us swap between different data storage backends relatively easily. The only sticking point has been fully defining that MArketRepository interface. Pleased with how this was going I set to implementing the FIFO matching algorithm (Price Time order). I actually thought that this would be a much easier task than it has turned out to be. Most of the difficulty has been due to me overthinking it and then misunderstanding the algorithm. Essentially:
- The highest price and
oldest
buy order is matched against all available sell orders that are lower in price. - A buy order can be fulfilled by multiple sell orders.
- A buy order cannot be partially fulfilled (this was my misunderstanding). If there isn't sufficient market volume to satisfy the buy. The buy order remains unfulfilled.
One of my main sticking points was deciding what was responsible for actually removing/amending sell orders. Should the matching algorithm do that or should the market do that. The market has a reference to the market repository which suggests that it is a concern of the market class. To realise this design the mathcing algorithm must return its matches. At the time I had no domain entity to model these matches. Looking at Sam's code I realised that these were probably trades
and promptly stole the idea. The current matching algorithm builds a list of trades, each containing the buy order, the sell order and the volume of the buy being fulfilled by the sell. This list is returned to the market that then processes the trades and removes/amends sell orders as appropriate. If the market recieves a null value from the matching algorithm (no matches found) it simply adds the buy to the market repo.
At this point I have only implemented half of the matching algorith, it deals with new buy orders but not new sell orders. A lot of my issues are to do with pre-optimising the algorithm rather than just getting something working. Some obvious (but possibly wrong) optimisations that come to mind are:
- If the new buy is lower in value than the top existing buy, fail fast.
- If the new sell is greater in value the cheapest existing sell, fail fast. Now I may be wrong but it feels like in both of those circumstances its not worth actually running any matching at all.
Im currently sat here trying to figure out how to implement the matching for a new sell order. Part of me thinks the solution is to just add the sell order to the market repo and then run the matching algorithm (as it is) repeatedly until a buy order is unable to be filled or the sell is exhausted. I don't quite know why I don't like this approach but it feels wrong. The alternative is to pass the sell to matching algorithm, do all of the matching (as above) and pass back the sell (or amended sell) as an incomplete trade. This feels like a hack.
Essentially I think I've worked myself into a corner without an obvious way out... Hopefully all will become clear soon.