Corporate KPI Prediciton Market Correlation with Share Price

Do prediction markets for corporate KPIs, in this case Boeing's commercial deliveries, correlate with the underlying stock price? Uses the prediction market dataset available @ Snowflake.

import numpy as np
import pandas as pd
import plotly.graph_objects as go
from scipy.optimize import curve_fit
from scipy.special import erf

from prediction_markets.database.snowflake_writer import connector
TICKER = "BA"
ENTITY = "Boeing"
PROPERTY = "commercial deliveries"
MARKET_TITLE = "%commercial airplane deliveries in 2026?%"
PRICE_START_DATE = "2026-07-11"

FIT_SCALE_GUESS = 670  # initial guess for the log-normal median
MAX_GAP_DAYS = 5  # how long a stale market quote is carried onto a trading day
SMOOTH_WINDOW = 7  # median filter width, in trading days
CORR_WINDOW = 30  # rolling-correlation width, in trading days
def query(stmt: str, **params) -> pd.DataFrame:
    cursor = connector.cursor()
    cursor.execute(stmt, params or None)
    return cursor.fetch_pandas_all()


def fmt4(x: float) -> str:
    return f"{x: .4f}"

Quoted thresholds

QUOTES_STMT = """
SELECT
    price_time::DATE AS date,
    price AS p,
    std_property_value AS v
FROM PREDICTION_MARKET_FACTORS.PUBLIC.STANDARDIZED_MARKETS
WHERE price IS NOT NULL
  AND std_resolved_entity ILIKE %(entity)s
  AND std_property = %(property)s
  AND market_title ILIKE %(market_title)s
ORDER BY date, price_time  -- so "last" per (date, threshold) is that day's final quote
"""

quotes = query(QUOTES_STMT, entity=ENTITY, property=PROPERTY, market_title=MARKET_TITLE)
print(f"{len(quotes)} quotes across {quotes['V'].nunique()} thresholds")
quotes.head()
51 quotes across 7 thresholds
DATE P V
0 2026-07-11 0.91 580
1 2026-07-11 0.91 600
2 2026-07-11 0.18 680
3 2026-07-12 0.44 660
4 2026-07-12 0.70 640

Log-normal fit

def lognormal_cdf(x: np.ndarray, mu: float, sigma: float) -> np.ndarray:
    return 0.5 * (1 + erf((np.log(x) - mu) / (sigma * np.sqrt(2))))


def fit_lognormal_mean(thresholds: np.ndarray, survival: np.ndarray) -> float:
    """Mean of the log-normal whose CDF best fits a quoted survival curve.

    `survival[i]` is the market price for "value exceeds thresholds[i]" — i.e. P(X > x),
    so the CDF is fitted against 1 - survival.
    """
    (mu, sigma), _ = curve_fit(
        lognormal_cdf,
        thresholds,
        1.0 - survival,
        p0=[np.log(FIT_SCALE_GUESS), 1.0],
    )
    return float(np.exp(mu + sigma**2 / 2))
def daily_forecast(quotes: pd.DataFrame) -> pd.Series:
    """One market-implied point forecast per date, for the dates whose fit converges."""
    # One column per threshold, one row per date. Carry each threshold's last quote
    # forward so a date only needs *some* market activity to be fitted.
    grid = (
        quotes.astype({"V": float, "P": float})
        .assign(DATE=lambda d: pd.to_datetime(d["DATE"]))
        .pivot_table(index="DATE", columns="V", values="P", aggfunc="last")
        .sort_index()
        .ffill()
    )

    fits = {}
    for date, row in grid.iterrows():
        row = row.dropna()
        if len(row) < 2:  # curve_fit needs at least one point per parameter
            continue
        try:
            fits[date] = fit_lognormal_mean(row.index.values, row.values)
        except (RuntimeError, ValueError):  # no convergence on this day's curve
            continue

    # A near-zero sigma sends the mean to +inf, which would flatten every plot below.
    fitted = pd.Series(fits, dtype=float, name="FORECAST").rename_axis("DATE")
    return fitted[np.isfinite(fitted)]


forecast = daily_forecast(quotes)
print(
    f"{len(forecast)} fitted dates "
    f"({forecast.index.min().date()} -> {forecast.index.max().date()})"
)
forecast.describe()
25 fitted dates (2026-07-11 -> 2026-08-25)
count     25.000000
mean     653.088986
std        7.994472
min      645.447043
25%      650.426805
50%      651.633211
75%      652.488092
max      690.215739
Name: FORECAST, dtype: float64
fig = go.Figure(
    go.Scatter(
        x=forecast.index,
        y=forecast.values,
        mode="lines",
        line=dict(color="#3B7AB8", width=2),
        hovertemplate="%{x|%b %d, %Y}<br>%{y:,.0f} deliveries<extra></extra>",
    )
)

fig.update_layout(
    title=f"{TICKER} total deliveries — market-implied forecast (log-normal fit)",
    template="plotly_white",
    height=420,
    margin=dict(l=60, r=30, t=60, b=40),
    hovermode="x unified",
    showlegend=False,
)
fig.update_xaxes(showgrid=False, showline=True, linecolor="#D4D4D4", ticks="outside")
fig.update_yaxes(
    title="Expected deliveries",
    gridcolor="#EDEDED",
    zeroline=False,
    tickformat=",.0f",
)
fig.show()

Closing prices

PRICES_STMT = """
SELECT
    date AS price_date,
    value AS closing_price
FROM SNOWFLAKE_PUBLIC_DATA_PAID.PUBLIC_DATA.STOCK_PRICE_TIMESERIES
WHERE ticker = %(ticker)s
  AND variable_name = 'Post-Market Close'
  AND date > %(start_date)s
ORDER BY date
"""

prices = (
    query(PRICES_STMT, ticker=TICKER, start_date=PRICE_START_DATE)
    .assign(PRICE_DATE=lambda d: pd.to_datetime(d["PRICE_DATE"]))
    .set_index("PRICE_DATE")["CLOSING_PRICE"]
    .astype(float)
    .sort_index()
)
prices.tail()
PRICE_DATE
2026-08-19    222.44
2026-08-20    215.70
2026-08-21    214.20
2026-08-24    211.26
2026-08-25    211.20
Name: CLOSING_PRICE, dtype: float64
# The forecast is on calendar dates, the stock on trading days. Carry the forecast onto
# the union of the two calendars first, so a Saturday quote lands on the following Monday
# instead of being dropped by the join.
forecast_on_trading_days = (
    forecast.reindex(forecast.index.union(prices.index))
    .ffill(limit=MAX_GAP_DAYS)
    .reindex(prices.index)
)

combined = pd.concat([prices, forecast_on_trading_days], axis=1).dropna()

# Carried quotes leave flat runs, and occasionally a stale price inverts the survival
# curve. Keep a median-filtered copy to see how much of the result rides on those days.
combined["FORECAST_SMOOTH"] = (
    combined["FORECAST"].rolling(SMOOTH_WINDOW, center=True, min_periods=3).median()
)

print(
    f"overlap with {TICKER}: {len(combined)} trading days "
    f"({combined.index.min().date()} -> {combined.index.max().date()})"
)
combined.describe()
overlap with BA: 32 trading days (2026-07-13 -> 2026-08-25)
CLOSING_PRICE FORECAST FORECAST_SMOOTH
count 32.000000 32.000000 32.000000
mean 220.765000 651.335372 651.101519
std 9.976393 1.587714 1.094425
min 205.480000 648.965059 648.965059
25% 213.585000 650.149852 650.676755
50% 217.930000 651.286654 651.159169
75% 231.437500 652.120710 652.107567
max 239.910000 654.989962 652.210310
combined.plot()
Output of cell 13: matplotlib figure.

Correlate closing prices with the KPI forecast

# Correlate CHANGES, not levels. The stock and the fitted forecast both trend, so a
# rolling correlation of their levels largely reports whether two trends happen to point
# the same way inside each window -- it swings between +1 and -1 on almost no information.
chg = pd.DataFrame(
    {
        f"{TICKER}_RET": np.log(combined["CLOSING_PRICE"]).diff(),
        "D_FORECAST": np.log(combined["FORECAST"]).diff(),
        "D_FORECAST_SMOOTH": np.log(combined["FORECAST_SMOOTH"]).diff(),
    }
).dropna()

pairs = {
    "changes (raw forecast)": (f"{TICKER}_RET", "D_FORECAST"),
    f"changes ({SMOOTH_WINDOW}d median filtered)": (
        f"{TICKER}_RET",
        "D_FORECAST_SMOOTH",
    ),
}

full = pd.DataFrame(
    [
        {
            "basis": label,
            "pearson": chg[a].corr(chg[b]),
            "spearman": chg[a].corr(chg[b], method="spearman"),
            "n": len(chg),
        }
        for label, (a, b) in pairs.items()
    ]
    + [
        {
            "basis": "levels (spurious, for reference)",
            "pearson": combined["CLOSING_PRICE"].corr(combined["FORECAST"]),
            "spearman": combined["CLOSING_PRICE"].corr(
                combined["FORECAST"], method="spearman"
            ),
            "n": len(combined),
        }
    ]
).set_index("basis")

print(f"=== Full-sample correlation ({TICKER} close vs. fitted delivery forecast) ===")
print(full.to_string(float_format=fmt4))
# ~2 standard errors under the null, as a yardstick for the numbers above
print(f"\n+/-2 SE at n={len(chg)}: {2 / np.sqrt(len(chg)):.3f}")
=== Full-sample correlation (BA close vs. fitted delivery forecast) ===
                                  pearson  spearman   n
basis                                                  
changes (raw forecast)            -0.1566   -0.2866  31
changes (7d median filtered)       0.1463    0.1094  31
levels (spurious, for reference)   0.1924    0.2544  32

+/-2 SE at n=31: 0.359
rolling_corrs = pd.DataFrame(
    {label: chg[a].rolling(CORR_WINDOW).corr(chg[b]) for label, (a, b) in pairs.items()}
).dropna(how="all")

summary = pd.DataFrame(
    {
        "Mean Correlation": rolling_corrs.mean(),
        "Median Correlation": rolling_corrs.median(),
        "Max Abs Correlation": rolling_corrs.abs().max(),
        "Std Dev": rolling_corrs.std(),
    }
).dropna()

print(
    f"=== Rolling {CORR_WINDOW}-day correlation of changes "
    f"({len(rolling_corrs)} windows) ==="
)
print(summary.to_string(float_format=fmt4))

rolling_corrs.plot(
    figsize=(11, 4),
    title=f"Rolling {CORR_WINDOW}d correlation: {TICKER} return vs. change in fitted forecast",
    ylim=(-1, 1),
)
=== Rolling 30-day correlation of changes (2 windows) ===
                              Mean Correlation  Median Correlation  Max Abs Correlation  Std Dev
changes (raw forecast)                 -0.1481             -0.1481               0.1572   0.0129
changes (7d median filtered)            0.1445              0.1445               0.1464   0.0027
Output of cell 16: matplotlib figure.
chg.plot(figsize=(11, 4), title="Daily log changes")
Output of cell 17: matplotlib figure.