It would be a good start if we could get anything easy and safe running at the beginning. Thus we imployed penny pinching strategy.
There exists one BOND
with stable fair value of 1000. Thus we place orders of Dir: "BUY", Price: 999, ...
and Dir: "SELL", Price: 1001, ...
. After an arbitrary order is filled, we place the order again. Because the bots would buy/sell orders that earliest get to exchange, 1)faster language has some advantage. 2)size doesn't matter that much.
100~200 per round
Jane Street provides written TCP clients for Python
C/C++
Java
Ocaml
We chose Go
as our programming language for following reasons:
1) faster than Python
2) easier than C/C++
and Ocaml
3) less cumbersome than Java
- uppercase and lowercase
- When we write to exchange intending to place an order, we are required to enter our
Dir
(direction) , which according to the handbook, can be either"BUY"
or"SELL"
. - Instead we entered
"buy"
and"sell"
at the beginning and it took us (and TA) 15 mins around to figure it out
- When we write to exchange intending to place an order, we are required to enter our
- type sensitive
- We need to indicate info from exchange as
float64
and then convert it toint
a lot
- We need to indicate info from exchange as
- restart program
- Each round lasts for 5 minutes. The program will very likly be doing nothing once an exchange restarts.
- In the
connect
function, right after the socket is created (s = socket.socket(...)
), add this line:s.settimeout(10)
. This will cause your app to automatically close if the exchange doesn't send any message for 10 seconds.
At this stage, we move to address ADR to make more profit. ADR has 1 underlying(denoted as U) security(basically it's an ETF with one underlying component). Since U is more liquid, it might have slower volatility, and because ADR has close connection with U, we could use U to predict ADR. We could just observe U and buy/sell ADR to slightly avoid the pain of conversion fee.
-
fair value
- the mean of the highest buying price of U and the lowest selling price of U as our fair value of U. (If the intuition is right, it should also be quite close to the fair value of ADR)
-
trades
- if the highest buying price of U > fair value, sell ADR at (fair value + margin)
- if the lowest selling price of U < fair value, buy ADR (fair value - margin)
The limit of ADR is 10 so you might hit the limit pretty fast. Don't panic. Since we only buy ADR when the fair value is higher, i.e. the price will rise in the future, we'll make profit.
-
margin
- At first, our margin was the midpoint between the higest/lowest price and fair value
- we adjusted it to 75th percentile and it improved our performance a bit
- we did not have time to implement but might be useful: analyze the highest/lowest price of executed orders to decide margin and maybe take 95 percentile so that you maximize your room to profit in a safe way. (The previous price information was all taken from the book)
300~600 per round
We circled a bit to figure out an appropriate way to implement it. The followings are some ideas we had at the first place. They might be helpful if Jane Street change some rules or your situation is more appropriate. If you want to see the solution we ended up, view solution.
We at the beginning were stuck at passing information read from exchange to different file as we cannot have multiple TCP connections at the same time(i.e. we cannot run multiple ReadFromExchange
at the same time). Then we thought about implementing an array of channels []chan
and send information to those channels so that each strategy could just read information from channels instead of reading from exchange again. We discarded this thought because it was a bit troublesome to implement due to deadlock
of Go.
We were concerned that there would be only a few price information in each book as the book did not have accumulated information. We thought about two solutions:
1) wait until we have a certain amount of 1)books 2)price information to calculate fair value & make a decision
We take neither after the consideration:
If we wait a while to make a decision, the information might be outdated.
-
Two books (no more)
so that the information is the latest possible and we have just enough to make a buy/sell ADR decision
- one for ADR
- one for U
-
Strategy struct
- We pass a strategy pointer from the main function to the strategy function and two books are stored in strategy. Basically, all information needed are within the struct so that we don't actually need to store any information in other files.
ETF is composed by 4 different underlyings at the total amount of 10 (i.e. each underlying has different size). We still did not want to deal with conversion fee neither did we have any time to do so. As a result we decided to employ the same strategy as in 2nd Stage, plus considering the weights of each underlying since they have different size.
It is basically the same as ADR but with weights. For instance, the weight of U is 1 since there is only one such underlying. Assuming each underlying in ETF has size 2, 2, 2, 4, then the weights could just be exactly the same as the size. If you have time, maybe you could adjust the weight a bit, but based on our experiement, using size as a only factor is sufficient.
4000 per round
We intended to merge the implementations of ADR and ETF as they were quite similiar thus we decided to build a relatively abstract structure. We could consider ETF/ADR as the composite with weighted underlying(s).
We pass in an array of underlying(s), an array of weight(s), string of composite to the function to create an abstract strategy. As we have an array of books in our struct, we record the book we needed and count the books already obtained, based on the length of underlying(s) and the count of obtained books, we decide whether we would make a trade deicison. (The code would be clearer.)
It might be helpful if you run the strategy in test first and print the fair value and prices. The basket of ETF contains more securities and each might have relatively extrem prices, so the fair value might be no where near the prices of ETF. You could choose to manually set some magic number to make the fair value closer to ETF prices.
- clear operation
- If you write fair values or books_obtained in the struct like we did, remember to clear them (i.e. reset them as 0) after each trade execution. Otherwise, it would carry to the next deicison making.
Things about collaboration that we could have paid more attention about:
- structure
- we spent a fair amount of time figuring out the structure and we could have either left it dirty or had an general idea about it at the beginning.
- coding proficiency
- we were not that familiar with Go but chose it out of the consideration of speed
- collaboration method
- git add commit push pull are a lot
- We used live share in VS code but we were not familiar with VS code thus it didn't provide us with syntax highlight
- We tended to discuss issues all togethe without anyone simultaneously doing anything and we would all stuck for a relatively long time.