Prediction Market Primer (part 2)

Published: 2022-03-29


This is part 2 of prediction markets primer. In part 1 I discussed the internal mechanics of the contract. It went over what every field means and how to calculate payout. Please read that if you're unfamiliar with the contract or the rest of this post probably won't make much sense.



In this post I'll discuss the shift that happened over the last few months that made the games a lot harder.

So lets get into it.

Setting up your analysis

The simplest kind of analysis focuses on the round data. You can download the round data here. This is a file I maintain that is updated every minute or so. It contains the round data from the underlying PancakeSwap contract. It includes everything from the amount people bet on BULL and BEAR, payoffs, timestamps and everything else you need.



You can do this in Excel but honestly it's much easier in python. You don't need to know a lot of python, you just need to know how to install a package and setup jupyter notebook (optional). Google it if you need help. There are much better resources out there than I can provide.

The package we'll work with are pandas. Loading the data is simple enough.

import pandas as pd

DATE = "2022-03-28"

df = pd.read_csv(f"~/Downloads/{DATE}-rounds.csv")

You can see the columns available with df.columns.



Index(['epoch', 'startTimestamp', 'lockTimestamp', 'closeTimestamp', 'lockPrice', 'closePrice', 'lockOracleId', 'closeOracleId', 'totalAmount', 'bullAmount', 'bearAmount', 'rewardBaseCalAmount', 'rewardAmount', 'oracleCalled', 'Unnamed: 14'], dtype='object')



It's a good start but you need some helper columns to make the analysis easier, so let's create those.



def to_eth(val: str):

return int(val) / 1000000000000000000

// convert reward amount to ether

df['reward_amount_eth'] = df.rewardAmount.apply(to_eth)

df['total_amount_eth'] = df.totalAmount.apply(to_eth)

df['bull_amount_eth'] = df.bullAmount.apply(to_eth)

df['bear_amount_eth'] = df.bearAmount.apply(to_eth)

// calculate who won and the winner payoff

df['winner'] = "draw"

df['winner_payoff'] = (df.rewardAmount.apply(to_eth) / df.rewardBaseCalAmount.apply(to_eth))

df.loc[df.closePrice > df.lockPrice, "winner"] = 'bull'

df.loc[df.closePrice < df.lockPrice, "winner"] = 'bear'

df.loc[df.winner_payoff == np.inf, "winner_payoff"] = 0



Note that the data includes rounds where no one bet (mostly in the beginning of the life of the contract). That'll throw off our analysis, so whenever we look at anything, we'll want to filter those out. We can look at the close price with the command df[df.closePrice > 0].closePrice.plot()



Naive Strategies

Let's take a look at the simple strategies of betting always BULL or always BEAR.



df['always_bull_payoff'] = -1

df.loc[df.winner == "bull", 'always_bull_payoff'] = df.winner_payoff - 1

df['always_bear_payoff'] = -1

df.loc[df.winner == "bear", 'always_bear_payoff'] = df.winner_payoff - 1



We defined our payoff as -1 if you bet BULL and the round was BEAR, otherwise it returns the winners payoff. Note the winners payoff is net of the treasury fee since we calculated it as reward amount / reward base cal amount which is net of 3% treasury fee. Let's see how our strategy did



df.always_bear_payoff.cumsum().plot(color="red")

df.always_bull_payoff.cumsum().plot(color="green")



Not great! The treasury fee and draws kill us so both strategies lose money. Let's do something more complicated. Let's see what happens if we bet on the HIGHER or LOWER of the two payouts. So for instance, "always higher" bets on the underdog. So if there are 20 BNB of bets on BULL and only 10 on BEAR, it'll bet BEAR. The lower payout will do the reverse.



df['always_lower'] = "bull"

df.loc[df.bear_amount_eth > df.bull_amount_eth, "always_lower"] = 'bear'

df['always_higher'] = "bull"

df.loc[df.bear_amount_eth < df.bull_amount_eth, "always_higher"] = 'bear'

df['always_lower_payoff'] = -1

df.loc[df.always_lower == df.winner, 'always_lower_payoff'] = df.winner_payoff - 1

df['always_higher_payoff'] = -1

df.loc[df.always_higher == df.winner, 'always_higher_payoff'] = df.winner_payoff - 1



Here we defined always_higher to return BULL if the BULL amount is LESS THAN the BEAR amount, and vise versa. Opposite for always_lower. Now our payoff is -1 if our bet is not equal to the winner, otherwise the winner payoff - 1 (our original bet). Let's see how betting on the higher payout payoff does.



df[df.reward_amount_eth > 0].always_higher_payoff.cumsum().plot()





This explains a lot. Prior to round 30,000 (around mid December 2021), a successful strategy would be one that always bets on the higher payoff. A lot of people piled into the lower payoff making the less likely result have a nice payoff. But starting in 2022, this strategy started losing! This corresponds with what I've seen in the games, fewer people were consistently making money. The best bots used to earn an average of 0.05 - 0.1 BNB for every 1 BNB bet. So if they played 100 games at 0.1 BNB each, they came out up around 0.5 - 1 BNB (100 games * 0.1 BNB per game * 0.05 = 0.5 BNB winning). Now the best bots are lucky to come out ahead 0.01 average, meaning their gains were cut to one fifth to one tenth of their original winnings.

The question is why did this happen? I can't be sure, but I think you have more smart money out there and the odds have balanced out. Now betting on the higher or lower payout naively is about even winnings. And when you factor in treasury fee and house taking draws, both strategies are losers!

Also note that you don't know the payouts until the very end. Most of the volume comes in the last block, so unless you're reading transactions from the mempool, you won't know the payouts until its too late to bet. So even this strategy is difficult if not impossible to implement.

I hope this gave you an understanding of how to setup your analysis. The next step would be to include third party data. For instance, you may want to include oracle or exchange prices. Or other indicators. But it can all be incorporated into this framework.

I'll leave it here. Fee free to reach out to me if you have any questions or comments. I hope to publish more of these deep dives and analysis. But in the meantime, remember to bet responsibly!

query contract data. Until then, bet responsibly!