You are here

Code Update - Buying/Selling a Portfolio, Transaction support, Initial Refactoring

Submitted by greggles on Mon, 12/24/2007 - 13:38

In today's update there are a few additions:

Complete Buying a Portfolio

You can buy/sell an entire portfolio. I keep doing it just for fun. This will record a purchase of 1 of every contract in the portfolio. This has removed a lot of my previous TODO entries from the hook_nodeapi which feels great. I even added in some more comments.

Transaction Support

One concept that I knew I would have to include was transactions. When you buy or sell something 2-4 things have to happen

  1. Credit/Debit your bank account
  2. Credit/Debit someone else's bank account
  3. Credit/Debit your stock account
  4. Credit/Debit someone else's stock account

So, let's say that you place an order at one point to buy 10 contracts at a price of 90 points each. This will cost you 900 points. At the time you place the order you have 900 points, but the order is not fulfilled instantly because nobody wants to sell at that price. Later you buy some other contracts which depletes your points, but your order is still in the queue. Then someone offers to sell their contracts for 90 points - a match for your old order - and the system tries to make the transaction. However, since you no longer have enough points to spend the transaction no longer can work.

We could just check that you have enough points before we do the transaction, but in a highly dynamic system with many orders being placed every second it is possible that this scenario can happen that between checking your account to see if it has enough points and actually reducing your points that another trade occurs which takes points from you and the order still fails!

Enter transactions. As a developer you just add two new steps to the list above -

  1. Begin transaction
  2. ...
  3. Commit/Rollback transaction

If there is a problem along the way, you simply rollback in step 5 and everything goes back to how it was at step 0. If it all worked fine - "commit" the transaction and it is recorded. The PressFlow Transaction module has a very simple syntax which enables module developers to easily add transactions.

Now, we will want certain pieces of our operation to succeed even if passing points and contracts gets rolled back. So, we have to be careful which pieces are wrapped in the transaction. Generally, though, that takes care of itself.

There were two tricks for me with this module. First, I needed to change all my tables to a storage engine that supports transactions, like InnoDB. This little script did it for me:

mysql -u uname dbname -e "show tables" | grep -v Tables_in | grep -v "+" | gawk '{print "alter table " $1 " ENGINE = InnoDB;"}' | sort -r | mysql -u uname dbname

Second, while the bank transaction was being properly rolled back, the message to the user was set via drupal_set_message which stores the data in the $_SESSION variable and therefore still shows it to the user even if the database interaction has been rolled back. Fortunately, you can disable Userpoints messages so I wrapped the transaction in a little bit of code that will disable messages while my transaction runs and then set the variable back to whatever the user had configured when the transaction is done. It takes an extra three lines that are well worth it to prevent confusion for the trader. This caused a problem with the transactions so I've commented out this fancy bit for now. Luckily David Strauss alerted me to the problem and a solution is simple: wrap the transaction in it's own function so that destructor is called before the variable_set and the rollback can happen.

Refactoring for Agents

One feature that I want to be sure to enable is allowing agents to get involved in trading. Agents are automated traders, or perhaps are bots that simluate some pattern of activity so that researchers can investigate what happens to real humans when in the middle of a certain sequence of trades. So, I want to make sure that my code is properly designed to allow an easy to use API. Refactoring is just the process of moving chunks of code from one area to another. It requires you to test your code to make sure it all still makes sense, but is generally not hard to do. The hard part is designing the functions to include the right amount of functionality. Too much functionality and they are not easily re-usable. Too little activity in a function leaves you with thousands of functions making it hard to read and understand your code.

Question to You - Capturing Trade "status"

Trades can be in multiple states. At least initially we'll need "open", "canceled", "executed", and "invalid". I'm trying to figure out a good system for this. Trade status values must be exclusive - an order can't be open and canceled at the same time. I could use the taxonomy system. I could use nodequeue, workflow, or workflow "next generation". While I like nodequeue a lot, it has the drawback of not enforcing exclusivity, so that might remove it from the running. Workflow and WorkflowNG are both big scary modules to me. Taxonomy is nice and well tested, but of course lacks some features that Workflow/WorkflowNG have.

Thoughts?

Comments

Unless all orders will be treated as "all or nothing", you'll also need a status of "incomplete" or "partial". If the book orders can only be accepted as a whole, your trading engine will get complicated, since it will have to reject orders at the market price that don't satisfy the entire book offer.

It's much simpler to allow a market trade to match part of an existing book offer. But that does mean that you need to be able to notify the trader who left an offer on the books that it was partially satisfied, and ensure that the remaining order clearly reflects its partially complete status.

Great point.

Right now, "IEM equivalent" has been my mantra and they use the system of canceling: "if the order exceeds the number of contracts available at that price, the remainder of the order is canceled without being placed in the respective queue."

I think that it's probably best eventually to make this a user option, though, and I don't think supporting both of them will be all that difficult.