Degenerate Gambling on Polymarket.com: Picking Up Pennies in Front of a Steamroller… for a 12.09% Yield?

Recently, I placed a bet on Polymarket.com, speculating that Kamala Harris and JD Vance will not debate before the upcoming election. With just 60 days left, the odds looked favorable, and this trade seemed like an easy win. But as any seasoned investor knows, the real question is: Is it worth picking up pennies in front of a steamroller?

My Trade Breakdown

Here’s how my position stands:

  • Outcome: No (Harris and Vance will not debate)
  • Quantity: 5,740 shares
  • Average Price: 98.2¢ per share
  • Total Cost: $5,637.09
  • Potential Payout: $5,740
  • Potential Profit: $102.91

The potential profit might seem modest, but when you consider the short time horizon, it’s important to look deeper into the numbers.

Return on Investment (ROI) and Yield

Let’s calculate the return:

  1. Total Payout: $5,740
  2. Total Cost: $5,637.09
  3. Profit: $102.91
  4. Return on Investment (ROI):

    \[ \text{ROI} = \frac{\text{Profit}}{\text{Total Cost}} \times 100 \]

    \[ \text{ROI} = \frac{102.91}{5637.09} \times 100 = 1.83\% \]

Although a 1.83% return over 60 days might not sound like much, the annualized yield adds a different perspective. Using the formula for annualized returns:

    \[ \text{Annual Yield} = \left(1 + \frac{1.83}{100}\right)^{\frac{365}{60}} - 1 \]

    \[ \text{Annual Yield} \approx 12.09\% \]

That’s a 12.09% annualized yield, which suddenly makes this trade more appealing. But there’s more to consider.

The Risk of the Steamroller

This trade feels safe—after all, the odds of Harris and Vance debating seem extremely low. Yet, there’s always the slim possibility that something unexpected happens in the political world.

In the words of Nassim Nicholas Taleb:

“I was convinced that I was totally incompetent in predicting market prices – but that others were generally incompetent also but did not know it, or did not know they were taking massive risks. Most traders were just ‘picking pennies in front of a steamroller,’ exposing themselves to the high-impact rare event yet sleeping like babies, unaware of it.”

Taleb’s quote resonates with this situation perfectly. It highlights the danger of taking what looks like an easy bet, without fully appreciating the rare but devastating risks lurking in the background. In my case, that steamroller is the unexpected possibility of a debate between Harris and Vance, which could be triggered by an unforeseen political event.

Conclusion

The 1.83% return over 60 days isn’t life-changing, but with an annualized yield of 12.09%, this trade seems tempting. However, like Taleb warns, many traders sleep soundly while unknowingly exposing themselves to rare, high-impact risks. While I remain confident in the outcome, I can’t entirely ignore the chance of an unexpected political twist that flattens this seemingly “safe” trade.

Degenerate Gambler or Investor? Analyzing Polymarket’s “Will Apple Remain the Largest Company” Bet

I’ve spent the last couple of weeks shifting my focus toward finding arbitrage opportunities in the betting markets. Today, I came across an intriguing bet on Polymarket: “Will Apple remain the largest company through September 30th?”. The question got me thinking—is Nvidia (NVDA) really about to overtake Apple (AAPL)? I knew Nvidia’s market cap had surged recently, but an 84% chance that Apple would still be the largest by the end of the month? I had to investigate further.

So, I wrote some code to analyze the odds. I used two methods:

  • Probability Based on Distribution
  • Probability Based on Exceedance Rate of Returns Over an X-Month Timeframe

The Basics of Polymarket Betting

Polymarket allows users to place bets on the likelihood of various events, ranging from election outcomes to company performance. These bets are binary—either “Yes” or “No.” The value of these bets fluctuates based on market sentiment, much like options in the stock market. The key to making informed decisions on such platforms lies in understanding the underlying data and probabilities. If you’re right, you get $1 per share, and if you’re wrong, you lose your stake.

Betting on Apple: An Investment Analysis

The current market sentiment suggests an 83% chance that Apple will retain its position as the largest company by September 30th. This probability is derived from the cost of betting “Yes” on Polymarket, which is currently priced at 84 cents on the dollar. If you’re right, you stand to make 16 cents per dollar invested.

But is the market sentiment accurate? Let’s delve into the historical data, statistical distributions, and probability models to assess whether this bet offers good value. This process involves analyzing how often other major companies like Microsoft (MSFT) or Nvidia (NVDA) have exceeded Apple’s growth rate in the past and whether such exceedances are likely within the given timeframe.

Frequency-Based Analysis vs. Distribution-Based Analysis

We examined two approaches:

  1. Frequency-Based Analysis: This method examines historical data to determine how often, on average, another company has surpassed Apple in growth rate during any given month. By calculating the “exceedance rate,” we can estimate how likely it is that Apple will be dethroned within the specified period. If this probability is high, betting “Yes” on Apple may not be favorable.
  2. Distribution-Based Analysis: This approach dives deeper into the statistical distribution of monthly returns, considering variance and volatility. If the data shows that other companies have frequently come close to Apple’s growth rates, the odds of an upset increase.

Determining Probability

The first step in determining the probability is to get the market cap of the top 10 companies.

import yfinance as yf
import pandas as pd

# List of top 10 companies by ticker
tickers = ["AAPL", "MSFT", "NVDA", "GOOG", "AMZN", "2222.SR", "META", "BRK-B", "TSM", "LLY"]

# Manual conversion rates (as of recent rates)
conversion_rates = {
    'SAR': 0.27,  # 1 SAR ≈ 0.27 USD
    'TWD': 0.032  # 1 TWD ≈ 0.032 USD
}

# Fetch the current market cap for each company
market_caps = {}
for ticker in tickers:
    stock = yf.Ticker(ticker)
    info = stock.info
    if 'marketCap' in info:
        market_cap = info['marketCap']
        currency = info.get('financialCurrency', 'USD')
        # Convert market cap to USD if it's not already in USD
        if currency != 'USD':
            if currency in conversion_rates:
                market_cap = market_cap * conversion_rates[currency]
            else:
                print(f"Conversion rate for {currency} not found, skipping conversion.")
        market_caps[ticker] = market_cap
    else:
        print(f"Market cap for {ticker} not found.")

# Create a DataFrame to store the data
df = pd.DataFrame(list(market_caps.items()), columns=['Ticker', 'Market Cap'])
df.sort_values(by='Market Cap', ascending=False, inplace=True)
df.reset_index(drop=True, inplace=True)

# Find the largest company
top_company = df.iloc[0]

# Calculate the percentage increase needed for each of the smaller companies to overtake the top one
df['Percentage Increase Needed'] = ((top_company['Market Cap'] - df['Market Cap']) / df['Market Cap']) * 100

# Output the results
print(f"Top Company: {top_company['Ticker']} with a Market Cap of ${top_company['Market Cap']:,}")
print(df[['Ticker', 'Market Cap', 'Percentage Increase Needed']])

Market Capitalization and threshold

Once we have the market cap, we can calculate the percentage increase required for each company to exceed AAPL. Below is the output from the code above.

Top Company: AAPL with a Market Cap of $3,481,738,936,320.0
    Ticker    Market Cap  Percentage Increase Needed
0     AAPL  3.481739e+12                    0.000000
1     MSFT  3.100618e+12                   12.291764
2     NVDA  2.928146e+12                   18.905915
3     GOOG  2.020401e+12                   72.329063
4     AMZN  1.873465e+12                   85.844935
5  2222.SR  1.818538e+12                   91.458086
6     META  1.318820e+12                  164.004029
7    BRK-B  1.026412e+12                  239.214585
8      LLY  8.644270e+11                  302.779992
9      TSM  2.849440e+10                12119.030326

Now that we know the percentage increases required, we can download all historical returns and see which companies have actually achieved this in a previous month.

import yfinance as yf
import pandas as pd
import plotly.graph_objects as go

# Assuming 'df' from the previous code cell contains the market cap data

# Download historical monthly data for each ticker
historical_data = {}
for ticker in df['Ticker']:
    stock_data = yf.download(ticker, start="1900-01-01", interval="1mo", auto_adjust=True)
    stock_data['Date'] = pd.to_datetime(stock_data.index)
    stock_data.set_index('Date', inplace=True)
    stock_data['Pct Change'] = stock_data['Close'].pct_change() * 100  # Convert to percentage change
    historical_data[ticker] = stock_data

# Identify the top company by ticker
top_ticker = df.iloc[0]['Ticker']

# Plotting the data with dynamic variables, but skipping the top company
for index, row in df.iterrows():
    ticker = row['Ticker']
    
    if ticker == top_ticker:
        continue  # Skip plotting for the top company
    
    threshold = row['Percentage Increase Needed']
    
    # Prepare the data for plotting
    stock_data = historical_data[ticker]
    
    # Create a bar chart with conditional coloring
    colors = ['green' if pct > threshold else 'red' for pct in stock_data['Pct Change']]
    
    fig = go.Figure(data=[go.Bar(
        x=stock_data.index,
        y=stock_data['Pct Change'],
        marker_color=colors
    )])
    
    # Add a red horizontal line at the threshold
    fig.add_hline(y=threshold, line_dash="dash", line_color="red", 
                  annotation_text=f"Threshold: {threshold:.2f}%", 
                  annotation_position="top right")
    
    # Customize layout
    fig.update_layout(
        title=f'Monthly Percentage Change for {ticker} with Threshold',
        xaxis_title='Date',
        yaxis_title='Percentage Change (%)',
        template='plotly_white'
    )
    
    # Show the plot
    fig.show()

This code outputs multiple graphs. However, only two companies—MSFT and NVDA—realistically have a chance to exceed AAPL’s market cap in the next month.

Relative Return

Instead of looking at NVDA’s return individually, I’ll examine the correlation between NVDA and AAPL by subtracting NVDA’s return from AAPL’s return for every month.

import plotly.graph_objects as go

# Assuming 'df' and 'historical_data' from the previous cells are still available

# Identify the top company by ticker
top_ticker = df.iloc[0]['Ticker']
top_stock_data = historical_data[top_ticker]

# Plotting the relative returns for each ticker compared to the top ticker, with threshold lines
for index, row in df.iterrows():
    ticker = row['Ticker']
    
    if ticker == top_ticker:
        continue  # Skip plotting for the top company
    
    threshold = row['Percentage Increase Needed']
    
    # Fetch the stock data for the current ticker
    stock_data = historical_data[ticker]
    
    # Align and truncate data to the earliest common date
    combined_data = pd.concat([top_stock_data['Pct Change'], stock_data['Pct Change']], axis=1, keys=[top_ticker, ticker]).dropna()
    
    # Subtract top stock's performance from the current stock's performance
    combined_data['Relative Return'] = combined_data[ticker] - combined_data[top_ticker]
    
    # Create a bar chart with conditional coloring
    colors = ['green' if pct > 0 else 'red' for pct in combined_data['Relative Return']]
    
    fig = go.Figure(data=[go.Bar(
        x=combined_data.index,
        y=combined_data['Relative Return'],
        marker_color=colors
    )])
    
    # Add a red horizontal line at the required threshold
    fig.add_hline(y=threshold, line_dash="dash", line_color="red", 
                  annotation_text=f"Threshold: {threshold:.2f}%", 
                  annotation_position="top right")
    
    # Customize layout
    fig.update_layout(
        title=f'Relative Monthly Returns for {ticker} vs {top_ticker} with Threshold',
        xaxis_title='Date',
        yaxis_title='Relative Return (%)',
        template='plotly_white'
    )
    
    # Show the plot
    fig.show()

Now, we can see the relative returns as well as the threshold of times they exceeded the required amount to surpass AAPL.

Distribution of Relative Returns

To view this data differently, we can now look at the distribution of this data. The code will loop through the last 10 years, but here is an example for one year:

### Analysis for the Last 1 Year(s) ###
NVDA exceeded the threshold 3 times.
NVDA Mean Relative Change: 7.4181%
NVDA Std Dev of Relative Change: 13.8239%
NVDA Z-score for threshold 18.91%: 0.8310
Probability of exceeding threshold based on distribution: 20.2984%

What is the expected value?

Now that we know the distribution and odds, we can introduce Polymarket’s implied odds—83% with a cost of 84 cents per share and a payout of 16 cents per share. Based on these inputs, we can convert this to Expected Value (EV).

Polymarket 'Yes' Probability: 83.00%
Polymarket 'Yes' Bet Cost: $0.84 per share
Polymarket 'Yes' Payout: $0.16 per share

### Summary for the Last 1 Year(s) ###
Months Analyzed: 12 months
Probability of Apple remaining the largest (based on exceedance rate): 75.00%
Probability of Apple remaining the largest (based on distribution): 79.7016%
Expected Value (EV) of betting 'Yes' on Polymarket (based on exceedance rate): $0.1200 per $1 bet
Expected Value (EV) of betting 'Yes' on Polymarket (based on distribution): $0.1275 per $1 bet
Based on the exceedance rate, for every $1 bet, you should expect to make $0.1200.
Based on the distribution, for every $1 bet, you should expect to make $0.1275.


import pandas as pd
from datetime import datetime
from scipy.stats import norm

# Given Polymarket values
polymarket_prob_yes = 0.83  # 83% chance of Apple remaining the largest
polymarket_price_yes = 0.84  # 84¢ to bet "Yes"
polymarket_payout_yes = 1 - polymarket_price_yes  # Payout for "Yes" bet

print(f"Polymarket 'Yes' Probability: {polymarket_prob_yes * 100:.2f}%")
print(f"Polymarket 'Yes' Bet Cost: ${polymarket_price_yes:.2f} per share")
print(f"Polymarket 'Yes' Payout: ${polymarket_payout_yes:.2f} per share")

# Assuming 'df', 'historical_data', and the correct top_ticker (Apple in this case) are available from previous code cells

# Loop over 1 to 10 years
for year in range(1, 11):
    print(f"\n### Summary for the Last {year} Year(s) ###")
    
    # Calculate the cutoff date for the current loop iteration
    cutoff_date = datetime.now() - pd.DateOffset(years=year)
    
    # Calculate the number of months in the specified period
    months_in_period = year * 12
    
    # Initialize a cumulative counter for exceedances and probabilities
    cumulative_exceedances = 0
    cumulative_prob_not_exceeding = 1  # Start with 1 (100% chance of no exceedance)
    
    # Iterate through the tickers in the df to calculate exceedances and probabilities
    for index, row in df.iterrows():
        ticker = row['Ticker']
        if ticker == top_ticker:
            continue  # Skip the top company itself
        
        # Fetch the stock data for the current ticker and truncate to the last N years
        stock_data = historical_data[ticker]
        stock_data_truncated = stock_data[stock_data.index >= cutoff_date]
        
        # Align and truncate data to the earliest common date
        combined_data = pd.concat([historical_data[top_ticker]['Pct Change'], stock_data_truncated['Pct Change']], axis=1, keys=[top_ticker, ticker]).dropna()
        
        # Subtract top stock's performance from the current stock's performance
        combined_data['Relative Change'] = combined_data[ticker] - combined_data[top_ticker]
        
        # Calculate how many times this stock exceeded the threshold (Frequency-Based Approach)
        exceedance_count = (combined_data['Relative Change'] >= row['Percentage Increase Needed']).sum()
        
        # If no exceedances, skip this ticker
        if exceedance_count == 0:
            continue
        
        # Add to the cumulative counter
        cumulative_exceedances += exceedance_count
        
        # Distribution and Standard Deviation Approach
        mean_relative_change = combined_data['Relative Change'].mean()
        std_relative_change = combined_data['Relative Change'].std()
        
        # Calculate the Z-score for the threshold
        z_score = (row['Percentage Increase Needed'] - mean_relative_change) / std_relative_change
        
        # Calculate the probability of exceeding the threshold using the CDF of the normal distribution
        prob_exceeding_threshold = 1 - norm.cdf(z_score)
        
        # Multiply with the cumulative probability of not exceeding the threshold (for all stocks)
        cumulative_prob_not_exceeding *= (1 - prob_exceeding_threshold)
    
    # Calculate the rate of exceedances per month (Frequency-Based Approach)
    exceedance_rate_per_month = cumulative_exceedances / months_in_period
    
    # Calculate the probability that Apple will remain the largest company based on the exceedance rate
    months_until_sept_30 = 1  # Assuming 1 month left until September 30
    prob_remain_largest_based_on_exceedance = 1 - (exceedance_rate_per_month * months_until_sept_30)
    
    # Calculate the cumulative probability that Apple will remain the largest based on the distribution
    prob_remain_largest_based_on_distribution = cumulative_prob_not_exceeding

    # EV calculation for the Polymarket bet (using exceedance rate)
    ev_polymarket_yes_exceedance_rate = prob_remain_largest_based_on_exceedance * polymarket_payout_yes
    
    # EV calculation for the Polymarket bet (using distribution-based probability)
    ev_polymarket_yes_distribution = prob_remain_largest_based_on_distribution * polymarket_payout_yes

    # Output the results for the current year
    print(f"Months Analyzed: {months_in_period} months")
    print(f"Probability of Apple remaining the largest (based on exceedance rate): {prob_remain_largest_based_on_exceedance:.2%}")
    print(f"Probability of Apple remaining the largest (based on distribution): {prob_remain_largest_based_on_distribution:.4%}")
    print(f"Expected Value (EV) of betting 'Yes' on Polymarket (based on exceedance rate): ${ev_polymarket_yes_exceedance_rate:.4f} per $1 bet")
    print(f"Expected Value (EV) of betting 'Yes' on Polymarket (based on distribution): ${ev_polymarket_yes_distribution:.4f} per $1 bet")

    # Summary of recommendation
    if ev_polymarket_yes_exceedance_rate > 0:
        print(f"Based on the exceedance rate, for every $1 bet, you should expect to make ${ev_polymarket_yes_exceedance_rate:.4f}.")
    else:
        print(f"Based on the exceedance rate, this bet may not be favorable, as you would expect to lose ${abs(ev_polymarket_yes_exceedance_rate):.4f} per $1 bet.")
    
    if ev_polymarket_yes_distribution > 0:
        print(f"Based on the distribution, for every $1 bet, you should expect to make ${ev_polymarket_yes_distribution:.4f}.")
    else:
        print(f"Based on the distribution, this bet may not be favorable, as you would expect to lose ${abs(ev_polymarket_yes_distribution):.4f} per $1 bet.")

Should You Bet “Yes”?

Essentially, we can analyze various years of data to determine how likely it is that NVDA will dethrone AAPL. Based on the output below, which analyzes data from 1 to 10 years, it turns out that betting “Yes” on AAPL retaining the largest market cap into October 2024 could be favorable. However, the EV isn’t significant, so I wouldn’t wager a large sum of money on this bet.

Polymarket 'Yes' Probability: 83.00%
Polymarket 'Yes' Bet Cost: $0.84 per share
Polymarket 'Yes' Payout: $0.16 per share

### Summary for the Last 1 Year(s) ###
Months Analyzed: 12 months
Exceedance Rate: 0.25 exceedances per month
Probability of Apple remaining the largest (based on exceedance rate): 75.00%
Probability of at least one stock exceeding the threshold (distribution-based): 20.2984%
Expected Value (EV) of betting 'Yes' on Polymarket (based on exceedance rate): $0.1200 per $1 bet
Expected Value (EV) of betting 'Yes' on Polymarket (based on distribution): $0.0325 per $1 bet
Based on the exceedance rate, for every $1 bet, you should expect to make $0.1200.
Based on the distribution, for every $1 bet, you should expect to make $0.0325.

### Summary for the Last 2 Year(s) ###
Months Analyzed: 24 months
Exceedance Rate: 0.29 exceedances per month
Probability of Apple remaining the largest (based on exceedance rate): 70.83%
Probability of at least one stock exceeding the threshold (distribution-based): 24.5599%
Expected Value (EV) of betting 'Yes' on Polymarket (based on exceedance rate): $0.1133 per $1 bet
Expected Value (EV) of betting 'Yes' on Polymarket (based on distribution): $0.0393 per $1 bet
Based on the exceedance rate, for every $1 bet, you should expect to make $0.1133.
Based on the distribution, for every $1 bet, you should expect to make $0.0393.

### Summary for the Last 3 Year(s) ###
Months Analyzed: 36 months
Exceedance Rate: 0.19 exceedances per month
Probability of Apple remaining the largest (based on exceedance rate): 80.56%
Probability of at least one stock exceeding the threshold (distribution-based): 18.1044%
Expected Value (EV) of betting 'Yes' on Polymarket (based on exceedance rate): $0.1289 per $1 bet
Expected Value (EV) of betting 'Yes' on Polymarket (based on distribution): $0.0290 per $1 bet
Based on the exceedance rate, for every $1 bet, you should expect to make $0.1289.
Based on the distribution, for every $1 bet, you should expect to make $0.0290.

### Summary for the Last 4 Year(s) ###
Months Analyzed: 48 months
Exceedance Rate: 0.15 exceedances per month
Probability of Apple remaining the largest (based on exceedance rate): 85.42%
Probability of at least one stock exceeding the threshold (distribution-based): 14.3875%
Expected Value (EV) of betting 'Yes' on Polymarket (based on exceedance rate): $0.1367 per $1 bet
Expected Value (EV) of betting 'Yes' on Polymarket (based on distribution): $0.0230 per $1 bet
Based on the exceedance rate, for every $1 bet, you should expect to make $0.1367.
Based on the distribution, for every $1 bet, you should expect to make $0.0230.

### Summary for the Last 5 Year(s) ###
Months Analyzed: 60 months
Exceedance Rate: 0.13 exceedances per month
Probability of Apple remaining the largest (based on exceedance rate): 86.67%
Probability of at least one stock exceeding the threshold (distribution-based): 12.8382%
Expected Value (EV) of betting 'Yes' on Polymarket (based on exceedance rate): $0.1387 per $1 bet
Expected Value (EV) of betting 'Yes' on Polymarket (based on distribution): $0.0205 per $1 bet
Based on the exceedance rate, for every $1 bet, you should expect to make $0.1387.
Based on the distribution, for every $1 bet, you should expect to make $0.0205.

### Summary for the Last 6 Year(s) ###
Months Analyzed: 72 months
Exceedance Rate: 0.12 exceedances per month
Probability of Apple remaining the largest (based on exceedance rate): 87.50%
Probability of at least one stock exceeding the threshold (distribution-based): 11.3315%
Expected Value (EV) of betting 'Yes' on Polymarket (based on exceedance rate): $0.1400 per $1 bet
Expected Value (EV) of betting 'Yes' on Polymarket (based on distribution): $0.0181 per $1 bet
Based on the exceedance rate, for every $1 bet, you should expect to make $0.1400.
Based on the distribution, for every $1 bet, you should expect to make $0.0181.

### Summary for the Last 7 Year(s) ###
Months Analyzed: 84 months
Exceedance Rate: 0.12 exceedances per month
Probability of Apple remaining the largest (based on exceedance rate): 88.10%
Probability of at least one stock exceeding the threshold (distribution-based): 10.4131%
Expected Value (EV) of betting 'Yes' on Polymarket (based on exceedance rate): $0.1410 per $1 bet
Expected Value (EV) of betting 'Yes' on Polymarket (based on distribution): $0.0167 per $1 bet
Based on the exceedance rate, for every $1 bet, you should expect to make $0.1410.
Based on the distribution, for every $1 bet, you should expect to make $0.0167.

### Summary for the Last 8 Year(s) ###
Months Analyzed: 96 months
Exceedance Rate: 0.12 exceedances per month
Probability of Apple remaining the largest (based on exceedance rate): 87.50%
Probability of at least one stock exceeding the threshold (distribution-based): 11.5422%
Expected Value (EV) of betting 'Yes' on Polymarket (based on exceedance rate): $0.1400 per $1 bet
Expected Value (EV) of betting 'Yes' on Polymarket (based on distribution): $0.0185 per $1 bet
Based on the exceedance rate, for every $1 bet, you should expect to make $0.1400.
Based on the distribution, for every $1 bet, you should expect to make $0.0185.

### Summary for the Last 9 Year(s) ###
Months Analyzed: 108 months
Exceedance Rate: 0.13 exceedances per month
Probability of Apple remaining the largest (based on exceedance rate): 87.04%
Probability of at least one stock exceeding the threshold (distribution-based): 12.1167%
Expected Value (EV) of betting 'Yes' on Polymarket (based on exceedance rate): $0.1393 per $1 bet
Expected Value (EV) of betting 'Yes' on Polymarket (based on distribution): $0.0194 per $1 bet
Based on the exceedance rate, for every $1 bet, you should expect to make $0.1393.
Based on the distribution, for every $1 bet, you should expect to make $0.0194.

### Summary for the Last 10 Year(s) ###
Months Analyzed: 120 months
Exceedance Rate: 0.13 exceedances per month
Probability of Apple remaining the largest (based on exceedance rate): 86.67%
Probability of at least one stock exceeding the threshold (distribution-based): 11.7986%
Expected Value (EV) of betting 'Yes' on Polymarket (based on exceedance rate): $0.1387 per $1 bet
Expected Value (EV) of betting 'Yes' on Polymarket (based on distribution): $0.0189 per $1 bet
Based on the exceedance rate, for every $1 bet, you should expect to make $0.1387.
Based on the distribution, for every $1 bet, you should expect to make $0.0189.

Source Code

The source code can be found here to run your own analysis.

Exploiting Arbitrage – Betting Against Nate Silver for a 54% Yield

I’m always on the hunt for an edge in the markets. After finishing Nate Silver’s book, “On the Edge: The Art of Risking Everything.” this week I thought it would be fitting to write a blog post about an arbitrage opportunity

Ironically I’m placing a bet against him on Polymarket.com…. Ok, maybe not technically against him. But earlier today I bought 11,927 ‘No’ shares that he would not call the Presidential election. What’s funny about this position is I think he will accurately call the election. Or at least there is a better than 50/50 chance he will call the election. Making this a bad bet.

You might be wondering why I would do this if I didn’t think it would profit. I will explain how to profit by betting against Nate Silver’s election predictions—a strategy that, if executed correctly, can offer a guaranteed win or at least a break-even outcome, all while potentially yielding a 54% yield on your investment. In this post, I’ll walk you through the mechanics, the math behind it, and how to manage your bets leading up to election day.

The Opportunity: Betting Against Nate Silver


Nate Silver is a well-known figure in election forecasting, and platforms like Polymarket.com allow you to bet on whether he will correctly predict an election outcome. Here’s the edge: you can bet that Nate Silver will be wrong while simultaneously betting on the candidate he predicts to win. If you play it right, this creates a no-risk scenario where you can either break even or secure a small profit with an annualized return of up to 54% (the trade only lasts for 67 days so you won’t actually make 54% this is however the annual yield)

“To succeed in a world full of uncertainty, one must adopt the mindset of the fox, always ready to pivot and re-evaluate when new data presents itself.”

While I might be exploiting a flaw in prediction markets here, Silver’s insights are invaluable for understanding how to think critically about risk and predictions. It’s a reminder that the best strategies are those that remain agile and open to new information.

The Math That Makes It Work

Here’s how it works:

Bet on “NO” for Nate Silver being wrong: Cost C_N = 40 cents.

Bet on the candidate Nate Silver predicts to win: This cost C_C will fluctuate based on the odds, but here’s where it gets interesting.

Conditional Formula

To ensure you either break even or make a profit, here’s the formula to follow:

    \[P =\begin{cases}1 - (C_N + C_C), & \text{if } (C_N + C_C) \leq 1 \\\text{Do not place the bet}, & \text{if } (C_N + C_C) > 1\end{cases}\]

This means:

If C_N + C_C \leq 1: You place the bet, knowing you’ll either break even or profit.

If C_N + C_C > 1: You skip the candidate bet altogether to avoid a loss.

Setting and Managing Your Limit Orders

Nate Silver’s predictions are updated multiple times per week as new data comes in. To optimize this strategy you have to do two things:

  • You have to set a limit order at the inverse of your price that you bought ‘No’ shares for Nate Silver. This will ensure that you break even. C_N + C_C \leq 1
  • As election day moves closer and you are confident that Nate’s predictions are not going to flip you should lock in your profits by buying the candidate that he predicts will win. You must place this trade or you will not be hedged! And if the cost to buy ‘No’ on Nate Silver and the cost of the candidate exceeds 1 you will lose money. C_N + C_C

My Take: A Tight Race Expected

In my opinion, this election is likely to be a close one—a virtual coin toss with odds hovering around 50/50. Given this, the potential profit from this strategy might be around 10%, assuming the market prices stay within a reasonable range. With 67 days left until the election, this profit can be annualized to provide a significant APR.

To calculate the APR, you can use the formula:


    \[\text{APR} = \left( \frac{\text{Profit}}{\text{Investment}} \right) \times \left( \frac{365}{\text{Days until Election}} \right)\]


So, if you’re making about a 10% return over 67 days:

    \[\text{APR} = 0.10 \times \left( \frac{365}{67} \right) \approx 0.54 \text{ or } 54\%\]


Unfortunately, Liquidity is an Issue

As promising as this strategy sounds, there’s one significant drawback: liquidity. I was only able to purchase 11,927 shares of Nate Silver being wrong and 4,266 shares of 538 calling the election for less than 39 cents. Essentially, I bought both trades as they represent the same underlying outcome. This lack of liquidity means that your ability to place large bets or move in and out of positions may be limited, potentially affecting the profitability of this strategy.

Definitions, The Fine Print

One crucial aspect of this strategy that needs to be understood is the timing of Nate Silver’s predictions. Typically, Nate Silver and his team at FiveThirtyEight may continue to update their forecasts until the very last moment—sometimes even until the morning of election day. This means that the candidate Nate predicts to win could change at the last minute, potentially complicating the execution of your bet on the winning candidate.

If Silver’s prediction is “frozen” too late, it may reduce the amount of time you have to place your bet, making it harder to lock in the favorable odds that ensure a profit. Additionally, a last-minute change in prediction could cause significant fluctuations in the betting market, making it difficult to execute your planned hedge. This “fine print” detail is something you should be acutely aware of when executing this strategy, as it could impact your ability to successfully carry out the arbitrage.

In short, while the strategy seems straightforward, the timing of when the prediction is finalized adds a layer of complexity that must be considered to avoid potential pitfalls. It is critical to monitor the forecast updates closely and be prepared to act quickly when the prediction is frozen to maximize the chances of executing the arbitrage successfully.

Is This Actually Arbitrage?

While this strategy appears to be a form of arbitrage, it’s important to recognize that certain unforeseen events could complicate the situation. For example, if one of the candidates were to die before the election, the market dynamics could change dramatically. The sudden withdrawal of a candidate would likely cause a market upheaval, potentially voiding some bets or causing severe losses. This is a reminder that even in what appears to be a “sure thing,” there are always risks that need to be considered. Thus, while this strategy mimics the characteristics of arbitrage, it’s not entirely free of risk.

Conclusion

This arbitrage opportunity isn’t about taking big risks for big rewards. Instead, it’s about leveraging market inefficiencies to guarantee yourself a win, however small. By betting that Nate Silver will be wrong and strategically placing a bet on the candidate he predicts to win, you’re setting yourself up for a situation where the numbers work in your favor—as long as you stick to the math. Just remember, the total cost must not exceed $1. If it does, walk away and wait for the next opportunity.

And if you’re interested in diving deeper into understanding risk and decision-making, I highly recommend Nate Silver’s book “On the Edge: The Art of Risking Everything.” It’s packed with insights on how to think like a fox—adaptable, curious, and always ready to revise your beliefs when new evidence comes to light.

Generating API keys for Polymarket.com

Here is a quick tutorial on how to generate API Key, Secret, and Passphrase to Polymarket.com. This way you can start trading using Python and skip the web interface.

keys.env

The first step is to create an environment file. You only want to populate the PK value. You do not know the other 3 variables yet so just comment them out. You can find this private key by logging in to your Polymarket.com account. Clicking on ‘Cash’, 3 dots, and finally Export Private Key.

#remove the '0x'
PK = "PRIVATE KEY EXPORTED FROM POLYMARKET.COM"

#these are generated through get_api_key.py do not specify these until you run the script with your private key
#API Key: xxxxxxxx-xxxx-xxxxx-xxxx-xxxxxxxxxxxx
#Secret: Lxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
#Passphrase: dxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxe9

generate_api_key.py

import os
from py_clob_client.client import ClobClient
from dotenv import load_dotenv

# Load the environment variables from the specified .env file
load_dotenv('keys.env')

def main():
    host = "https://clob.polymarket.com"
    key = os.getenv("PK")
    chain_id = 137  # Polygon Mainnet chain ID

    # Ensure the private key is loaded correctly
    if not key:
        raise ValueError("Private key not found. Please set PK in the environment variables.")

    # Initialize the client with your private key
    client = ClobClient(host, key=key, chain_id=chain_id)

    # Create or derive API credentials (this is where the API key, secret, and passphrase are generated)
    try:
        api_creds = client.create_or_derive_api_creds()
        print("API Key:", api_creds.api_key)
        print("Secret:", api_creds.api_secret)
        print("Passphrase:", api_creds.api_passphrase)

        # You should now save these securely (e.g., store them in your .env file)
    except Exception as e:
        print("Error creating or deriving API credentials:", e)

if __name__ == "__main__":
    main()

Accessing Polymarket.com Data in Python

Recently I started exploring arbitrage opportunities in the Polymarket.com world. I quickly realized I would need faster access to the data. So I wrote some Python programs to access the platform for data analytics. The documents for doing this are located in the Polymarket docs. Here’s how it works.

Install py-clob-client

pip install py-clob-client<br><br>

Coinbase Wallet

To perform this first block of code you need a Coinbase wallet. Once you have it setup you need to export your private key.

from py_clob_client.client import ClobClient

host = "https://clob.polymarket.com"
private_key = "YOUR_PRIVATE_KEY_GOES_HERE"
chain_id = 137  # Polygon Mainnet

# Initialize the client with private key
client = ClobClient(host, key=private_key, chain_id=chain_id)

api_key_data = client.create_api_key()

print(api_key_data)

Creating keys.py

We can take this outputted data and create our keys.py which will hold our keys.For some reason, the api_passphrase and rename it api_key while commenting out the original api_key. I left the api_secret and original api_passphrase. Now that I have my keys.py I can reference it in my main code


Extracting all of the data

import csv
import json
from py_clob_client.client import ClobClient
from keys import api_key  # Import only the API key
from py_clob_client.clob_types import OpenOrderParams

# Replace with your actual host and chain ID
host = "https://clob.polymarket.com"
chain_id = 137  # Polygon Mainnet

# Initialize the client with only the host, key, and chain_id
client = ClobClient(
    host,
    key=api_key,
    chain_id=chain_id
)

# Initialize variables for pagination
markets_list = []
next_cursor = None

# Fetch all available markets using pagination
while True:
    try:
        # Print the cursor value for debugging
        print(f"Fetching markets with next_cursor: {next_cursor}")

        # Make the API call based on the cursor value
        if next_cursor is None:
            response = client.get_markets()
        else:
            response = client.get_markets(next_cursor=next_cursor)

        # Print the raw response for debugging
        print(f"API Response: {json.dumps(response, indent=2)}")

        # Check if the response is successful and contains data
        if 'data' not in response:
            print("No data found in response.")
            break

        markets_list.extend(response['data'])
        next_cursor = response.get("next_cursor")

        # Exit loop if there's no next_cursor indicating no more data to fetch
        if not next_cursor:
            break

    except Exception as e:
        # Print the exception details for debugging
        print(f"Exception occurred: {e}")
        print(f"Exception details: {e.__class__.__name__}")
        print(f"Error message: {e.args}")
        break

# Debugging step: Print out the raw data to understand its structure
print("Raw Market Data:")
print(json.dumps(markets_list, indent=2))

# Dynamically extract all keys from the markets to create the CSV columns
csv_columns = set()
for market in markets_list:
    csv_columns.update(market.keys())
    # Also include nested keys like tokens
    if 'tokens' in market:
        csv_columns.update({f"token_{key}" for token in market['tokens'] for key in token.keys()})

csv_columns = sorted(csv_columns)  # Sort columns alphabetically for consistency

# Writing to CSV
csv_file = "markets_data.csv"
try:
    with open(csv_file, 'w', newline='') as csvfile:
        writer = csv.DictWriter(csvfile, fieldnames=csv_columns)
        writer.writeheader()
        for market in markets_list:
            row = {}
            for key in csv_columns:
                # Handling nested 'tokens' structure
                if key.startswith("token_"):
                    token_key = key[len("token_"):]
                    row[key] = ', '.join([str(token.get(token_key, 'N/A')) for token in market.get('tokens', [])])
                else:
                    row[key] = market.get(key, 'N/A')
            writer.writerow(row)
    print(f"Data has been written to {csv_file} successfully.")
except IOError as e:
    print(f"Error writing to CSV: {e}")

Output CSV

If all goes as planned you will now have a giant CSV file named markets_data.csv with all the different markets and their values. You can now build on this code to manipulate it to your needs.