Back to Strategies

Polymarket BTC Late-Window Sniper

Code Strategy by 0x7s0lt1·2026-09-04

// Polymarket BTC "Late-Window Sniper"
//
// This strategy looks for strong BTC price moves near the end of a
// 15-minute Up/Down market.
//
// The idea:
// 1. Track BTC's price when the market Window starts.
// 2. Wait until the market is Close to expiration.
// 3. Measure how large the BTC move is relative to the volatility
//    expected during the remaining time.
// 4. If the move is statistically significant, determine whether
//    "Up" or "Down" is strongly favored.
// 5. Check Polymarket's current price for that outcome.
// 6. Only trade if the outcome still appears underpriced.
//
// This is a directional strategy, NOT a guaranteed arbitrage.
// The strategy assumes Polymarket's price can temporarily lag
// a fast move in the underlying BTC market.
//
// IMPORTANT:
// - The default parameters are not optimized.
// - Test the strategy and monitor zScore/buyPrice before using
//   meaningful capital.
// - Fees, slippage, liquidity, and execution latency can materially
//   affect the result.

let state = (await $.Storage.get()) || {};
const now = Math.floor(Date.now() / 1000);

const windowSeconds = Number($.Env.WINDOW_SECONDS || 900);
const slugPrefix = $.Env.SLUG_PREFIX || "btc-updown-15m-";

// Window metadata is static for the life of the Window — only
// refresh when nothing is cached or the cached Window has expired.
const needsRefresh = !state.activeWindow || state.activeWindow.endTime <= now;

if (needsRefresh) {
  const boundary = Math.floor(now / windowSeconds) * windowSeconds;
  const candidateSlug = slugPrefix + boundary;

  const res = await $.Http.get("https://gamma-api.polymarket.com/events/slug/" + candidateSlug);
  if (res.status !== 200) {
    return;
  }

  const event = JSON.parse(res.text);
  const market = event.markets && event.markets[0];
  if (!market) {
    return;
  }

  const outcomes = JSON.parse(market.outcomes);
  const tokenIds = JSON.parse(market.clobTokenIds);
  const endTime = Math.floor(new Date(market.endDate).getTime() / 1000);

  if (endTime <= now) {
    return;
  }

  const openPrice = await $.getExchangeRate($.Exchange.BINANCE, "btcusdt");
  if (!openPrice) {
    return;
  }

  await $.Storage.set({
    activeWindow: {
      slug: candidateSlug,
      outcomes: outcomes,
      tokenIds: tokenIds,
      endTime: endTime,
      openPrice: openPrice,
      acted: false
    }
  });
  return;
}

const w = state.activeWindow;
if (!w || w.acted || now >= w.endTime) {
  return;
}

const timeRemaining = w.endTime - now;
const snipeWindow = Number($.Env.SNIPE_WINDOW_SECONDS || 60);

// Not Close enough to expiry yet — nothing to do this run.
if (timeRemaining > snipeWindow) {
  return;
}

const price = await $.getExchangeRate($.Exchange.BINANCE, "btcusdt");
if (!price) {
  return;
}

const priceMove = (price - w.openPrice) / w.openPrice;

// Tunable, not fitted — calibrate against realized data before
// trusting it.
const annualVol = Number($.Env.BTC_ANNUAL_VOL || 0.55);
const secondsPerYear = 365 * 24 * 3600;
const sigmaRemaining = annualVol * Math.sqrt(timeRemaining / secondsPerYear);

if (sigmaRemaining <= 0) {
  return;
}

const zScore = priceMove / sigmaRemaining;
const zThreshold = Number($.Env.Z_THRESHOLD || 1.5);

let outcomeName = null;
if (zScore > zThreshold) {
  outcomeName = "Up";
} else if (zScore < -zThreshold) {
  outcomeName = "Down";
} else {
  return;
}

const idx = w.outcomes.indexOf(outcomeName);
if (idx === -1) {
  return;
}

const priceRes = await $.Http.get("https://clob.polymarket.com/price", {
  token_id: w.tokenIds[idx],
  side: "BUY"
});
if (priceRes.status !== 200) {
  return;
}

const buyPrice = Number(JSON.parse(priceRes.text).price);
if (!(buyPrice > 0 && buyPrice < 1)) {
  return;
}

// Confirm the real fee for this market's category before trusting
// this default.
const takerFee = Number($.Env.TAKER_FEE || 0.018);
const margin = Number($.Env.EDGE_MARGIN || 0.02);
const maxAllowedPrice = 1 - takerFee - margin;

// Market hasn't caught up to the "sure thing" yet — that gap is the
// entire edge this strategy is trying to capture.
if (buyPrice >= maxAllowedPrice) {
  return;
}

const size = Number($.Env.TRADE_SIZE_USDC || 50);

const postRes = await $.Http.post($.Env.PREDICTION_BOT_WEBHOOK_URL, {
  key: $.Env.PREDICTION_BOT_KEY,
  slug: w.slug,
  outcome: outcomeName,
  side: "BUY",
  size: size,
  debug: "late-sniper"
});

if (postRes.status >= 200 && postRes.status < 300) {
  w.acted = true;
  w.actedOutcome = outcomeName;
  w.actedPrice = buyPrice;
  w.actedZScore = zScore;
  await $.Storage.set(state);
}