imc-prosperity-2 icon indicating copy to clipboard operation
imc-prosperity-2 copied to clipboard

My IMC Prosperity 2 code (9th place)

IMC Prosperity 2

This repository contains the IMC Prosperity 2 code of my solo team named "camel_case".

Final position: 9th overall, 3rd in Europe, 1st in the Netherlands.

This is a list of all my open-source Prosperity 2 tools:

Round results

Profit / loss Leaderboard position Visualizer links
Overall Manual Algo Round Overall Manual Algo Country Submission Final
1 133,761 102,028 31,733 133,761 210 349 265 10 Link Link
2 842,398 113,938 594,698 708,636 6 319 6 1 Link Link
3 985,116 78,577 64,141 142,718 9 452 11 1 Link Link
4 1,331,214 102,592 234,505 346,097 9 349 9 1 Link Link
5 1,700,672 115,500 253,956 369,457 9 160 8 1 Link Link

Round summaries

Tutorial round

I spent most of the tutorial round building developer tooling for later rounds. I made all of them open-source (visualizer, backtester, and submitter), and although I didn't embed analytics in them, based on Discord messages I believe a significant number of participants used at least one of them :).

After building developer tools I spent about a day on building a market-making algorithm for amethysts and starfruit. I took some inspiration from the code of the team that finished 2nd in IMC Prosperity 1, especially their use of the popular buy/sell price, i.e. the buy/sell price with maximum volume. For amethysts I assumed a true value of 10k, and for starfruit I assumed a true value in the middle between the popular buy and sell prices. I also found some improvement in adding soft/hard liquidation procedures, which would force a buy or a sell at less-than-usual prices to get out of prolonged positions at the boundary of the limit (position = limit or position = -limit), because in those positions we can no longer market-make in both directions.

Based on Discord messages from other participants I also tried to do some sort of linear regression on starfruit, but couldn't get that to work profitably.

Round 1

The algo side of round 1 appeared to be a continuation of the tutorial round on new data. I tried to improve my market making strategy, but couldn't improve on my tutorial code, so ended up submitting that without any changes.

For the manual side I took 10M random reserve prices according to the given distribution and ran a grid search over all possible low/high bid combinations. This resulted in a maximum profit of 204M with a price-per-fish at 33.6 (considering only the fishes that we took up on their offer) or 20.4 (considering all 10M fishes in the simulation) using a low bid of 952 and a high bid of 978. After the round ended Edgar Maddocks published a write-up of a closed-form solution that resulted in the same bids, which served as a pleasant confirmation that my simulation was accurate.

Round 2

The algo side of round 2 had me struggling. Even though many people on Discord were claiming 60k+ and eventually even 100k+ profits, I couldn't get past a few thousands in profit. I posted about my struggles on Discord about 6 hours before the round ended, and got two surprisingly useful and specific hints seconds later. I guess making my developer tooling for the competition open-source got people in a helpful mood :). The trick was to continuously short sell to the limit at a price at which you could immediately convert profitably back to 0 in the next iteration, hoping for other bots in the simulated market to fill your orders. This got me to 60k profit at first, and to 109k minutes later after I realized you can convert your position to 0 and then immediately go short again in the same iteration.

For the manual side of round 2 I calculated the profit of each possible answer, and ended up submitting seashells -> pizza slices -> wasabi roots -> seashells -> pizza slices -> seashells, which gave the maximum profit of 113,938.8.

Also, according to round 2 Discord lore I should thank Marlon for their help. Thank you Marlon.

Round 3

After the round opened, it was clear that the algo side of round 3 was basically a copy-paste of last year's round 4. As such, I started off with building a strategy based on the difference between the value of gift baskets and the combined value of the contents of a gift basket. When this difference crosses certain thresholds, the strategy would go 100% long or 100% short on gift baskets. I later extended it to mirror the position on the individual products of the gift basket, which did reasonably well (although overfit).

After hearing other participants reaching 600k in backtests and failing to figure that out myself, I decided to start trading the gift baskets and its components at different thresholds, and performed several grid searches to find good thresholds for the 30k iterations of example data. This resulted in a profit on everything but roses, on which I lost over 36k. Following these results I disabled my trading on components for future rounds, as I deemed it too risky against too little potential profits.

For the manual challenge of round 3 I combined my gut feeling, some some calculations I ran on guesses of the number of participants choosing each tile, and the spreadsheet containing user-reported expeditions created by noah1921 on Discord. Based on these sources, I figured it was unlikely to earn >75k from an expedition, while >25k seemed very doable, making two expeditions the best amount. I decided on expeditions 83 and 87, mostly based on noah1921's spreadsheet, and ended up with a profit of 78,577. The maximum profit of this manual round was 106,291, which was a bit unfortunate.

Round 4

Round 4 had unfortunate scheduling issues. Because both round 1 and round 3 were extended by 24 hours following major production incidents that made submissions impossible, round 4 fell entirely within a pre-planned weekend holiday of mine to Luxembourg to hike part of the Mullerthal trail with my father. I didn't plan on bringing my laptop, meaning all my round 4 work had to be done on a phone.

The evening before leaving I built a custom Streamlit app to convert email/password to ID token, to use my submitter (converted the CLI to work inside Streamlit), to download leaderboard data (for my own leaderboard website), and to list submissions. On the way to Luxembourg I decided to try some Python editors in the Play Store, and found Pydroid 3 to be the most capable one, because it allows installing and running arbitrary PyPI packages like my backtester. I also found an app called Desktop FullScreen Web Browser, which let me bypass Prosperity's anti-mobile devices "feature" (that happens to be resilient against the "Show desktop version" features in Chrome and Firefox).

For the algo side of round 4 my inability to effectively analyze the provided data was pretty annoying. While it was clear that coupons were meant as an option-like instrument with coconuts being the underlying assets, I had some trouble turning that knowledge into a profitable strategy. Based on hints from other participants I eventually tried to apply the Black-Scholes model to calculate the expected price of the coupons, and then traded around that. This worked, and following discussions with other participants I was able to tune my Black-Scholes parameters to getting 420k in backtests. The profit I got on coupons in backtests was pretty close to the profit I got at the end of the round, which was nice to see.

I also created a simple directional trading strategy for coconuts themselves, expecting them to mostly go upwards or downwards during a trading day. This assumption failed catastrophically at the end of the round, leaving me with a 77k loss on coconuts against a 184k profit on coupons. Following these results I stopped using this strategy in round 5.

For the manual challenge I figured most people would probably go for round 2's optimal bids of 952/978. Because I thought some people would increase their high bid to be at or above the average, I decided to go for 952/980 instead. The average bid ended up being 980, which left me with a nice profit of 102,592, which appeared to be the maximum profit when comparing to results others posted on Discord.

All things considered I'm very pleased with my round 4 results. I definitely didn't expect to maintain my 9th place overall using only my phone.

Round 5

Round 5's algo challenge introduces de-anonymized trades data. I initially stared at generated charts for a while and found four credible signals, and then ran a grid-search consisting of 3,600 backtests to find signals I might've missed by eye, finding nothing new. These are the signals I found:

  • If Vladimir sells CHOCOLATE to Remy, then go long on CHOCOLATE.
  • If Remy sells CHOCOLATE to Vladimir, then go short on CHOCOLATE.
  • If Rihanna/Rhianna sells ROSES to Vinnie, then go long on ROSES.
  • If Vinnie sells ROSES to Rihanna/Rhianna, then go short on ROSES.

My final strategy for each product is as follows, and all of these ended up being profitable in round 5:

  • AMETHYSTS: market-making around 10,000.
  • STARFRUIT: market-making around the popular mid price.
  • ORCHIDS: arbitrage between the north and south archipelagos.
  • CHOCOLATE: directional trading based on trades between Vladimir and Remy.
  • STRAWBERRIES: directional trading based on parameters overfit on round 3 data.
  • ROSES: directional trading based on trades between Rihanna/Rhianna and Vinnie.
  • GIFT_BASKET: directional trading based on parameters overfit on round 3 data.
  • COCONUT: nothing, I couldn't find a consistently profitable strategy for it.
  • COCONUT_COUPON: trading around the expected value of the product calculated using the Black-Scholes model.

For the manual side of this round I read the Iceberg news and decided that for electric blankets (sell), sleds (sell), ice sculptures (buy), PS6 (buy), and moustache serum (sell) I felt confident about which direction I thought they would move, and that for icicle earrings (buy) I was a little less confident but was still willing to trade. For refrigerators, lava lamps, and hot chocolate I didn't feel certain enough about their direction, so I decided not to trade them. I allocated 10% of my capital to icicle earrings, and then divided the remaining 90% equally between the other products. This resulted in a manual profit of 115k, with the best manual result for this round being 149k, and the optimal being 157k.

In closing

All in all I'm very pleased with my results, getting into the top 10 and staying there for round 2-5 was a big improvement after finishing 322nd in Prosperity 1. Although I graduated a few months prior to the start of the competition, I received an email from the organizers saying there were granting me an exception to the only-students-can-win-prizes rule a few days before the final leaderboard went out as a token of appreciation for my activity in the competition's Discord server, which I am very grateful for.

It appears the trick to getting multi-million algo profits in rounds 4 and 5 was to exploit the coconut data in the Prosperity 2 being a (near-)exact match with the coconut data in Prosperity 1. Congratulations to Puerto Vallerta for figuring that out first during round 4 (presumably), and to the other three teams in the final top 4 that seemingly figured it out too during round 5.

Prosperity 2 was yet again an interesting competition, with some challenges being very similar to last year's with others being brand-new. It was a bit disappointing to see two rounds being extended due to the website going down for extended periods of time, especially after Prosperity 1's issues with AWS Lambda errors. Hopefully Prosperity 3 is plagued with fewer issues. Nonetheless, I thank Prosperity's organizers for keeping the competition going, and all other participants for creating a lively online community together for the duration of the competition.