PYX

Synth Data Integration

Access ML-powered price predictions and Polymarket probability estimates from Synthdata.

Overview

Pyx integrates with Synthdata's prediction network to provide ML-derived probability estimates and volatility forecasts directly in your scripts. Synthdata aggregates predictions from 200+ competing AI models to produce probabilistic price forecasts for BTC, ETH, SOL, and other assets.

Data is refreshed approximately every 60 seconds server-side. No configuration is needed - the data is available to all scripts automatically.

pyx.get_synth_data()

Returns the latest Synthdata snapshot, or nil if data is not yet available.

local synth = pyx.get_synth_data()
if synth and synth.hourly then
    pyx.log(string.format("Synth fair: %.4f | Poly: %.4f | Edge: %.4f",
        synth.hourly.synth_prob_up,
        synth.hourly.poly_prob_up,
        synth.hourly.synth_prob_up - synth.hourly.poly_prob_up
    ))
end

Returns a table or nil:

FieldTypeDescription
assetstringAsset symbol (e.g. "BTC")
updated_atnumberLast update time (ms since epoch)
hourlytable?Hourly Up/Down probability data
percentilestable?Price forecast percentiles at multiple horizons
volatilitytable?Volatility forecast data

Hourly

The hourly field contains probability estimates for Polymarket's BTC Up/Down markets. The synth_prob_up value represents Synthdata's ML-derived estimate of the probability that BTC will be higher at the end of the period - this is your fair price for the Up token.

FieldTypeDescription
synth_prob_upnumberSynth's probability of "Up" outcome (0–1)
poly_prob_upnumberCurrent Polymarket implied probability of "Up"
current_pricenumberCurrent BTC price
start_pricenumberBTC price at period start
synth_outcomestringSynth's predicted outcome ("Up" or "Down")
event_end_timestringMarket expiry time (ISO 8601)
local synth = pyx.get_synth_data()
if synth and synth.hourly then
    local edge = synth.hourly.synth_prob_up - synth.hourly.poly_prob_up
    if edge > 0.05 then
        pyx.log(string.format("BUY Up: synth=%.2f poly=%.2f edge=%.2f",
            synth.hourly.synth_prob_up,
            synth.hourly.poly_prob_up,
            edge
        ))
    elseif edge < -0.05 then
        pyx.log(string.format("BUY Down: synth_down=%.2f poly_down=%.2f edge=%.2f",
            1 - synth.hourly.synth_prob_up,
            1 - synth.hourly.poly_prob_up,
            -edge
        ))
    end
end

Percentiles

Price forecast percentiles at fixed horizons derived from Synthdata's prediction network. Each horizon contains price levels at various probability percentiles.

FieldTypeDescription
current_pricenumberCurrent asset price
5mtable?5-minute forecast
15mtable?15-minute forecast
30mtable?30-minute forecast
1htable?1-hour forecast
4htable?4-hour forecast

Each horizon table contains percentile keys mapping to price levels:

KeyDescription
"0.005"0.5th percentile (extreme low)
"0.05"5th percentile
"0.2"20th percentile
"0.35"35th percentile
"0.5"50th percentile (median)
"0.65"65th percentile
"0.8"80th percentile
"0.95"95th percentile
"0.995"99.5th percentile (extreme high)
local synth = pyx.get_synth_data()
if synth and synth.percentiles then
    local h1 = synth.percentiles["1h"]
    if h1 then
        local median = h1["0.5"]
        local p95 = h1["0.95"]
        local p05 = h1["0.05"]
        pyx.log(string.format("1h forecast: p5=%.0f median=%.0f p95=%.0f", p05, median, p95))
    end

    -- Use percentiles to estimate probability of BTC above a strike
    -- If current price is between p35 and p50, roughly 35-50% chance of being lower
    local m5 = synth.percentiles["5m"]
    if m5 then
        local current = synth.percentiles.current_price
        local range = m5["0.95"] - m5["0.05"]
        pyx.log(string.format("5m 90%% range: $%.0f (±$%.0f)", range, range / 2))
    end
end

Volatility

Forecasted and realized volatility metrics for the asset.

FieldTypeDescription
forecast_avgnumberAverage forecasted volatility (annualized)
realized_avgnumberAverage realized volatility (annualized)
current_pricenumberCurrent asset price
local synth = pyx.get_synth_data()
if synth and synth.volatility then
    local vol_ratio = synth.volatility.forecast_avg / synth.volatility.realized_avg
    if vol_ratio > 1.5 then
        pyx.warn("High forecast vol - widening spreads recommended")
    end
end

Use volatility data to dynamically adjust your strategy:

  • Widen spreads when forecast_avg is elevated relative to realized_avg (regime change)
  • Reduce position sizes during high volatility periods
  • Tighten spreads when volatility is low and stable for more competitive quoting

Example: Fair Price Market Maker

Instead of quoting around the market midpoint, use Synth's probability estimate as the fair price:

local CONFIG = {
    spread     = 0.02,
    order_size = 10.0,
    min_edge   = 0.01,   -- minimum edge before quoting
}

local token_id = nil
local condition_id = nil
local last_bid = nil
local last_ask = nil

local function update_quotes(fair)
    if not token_id then return end

    local pos = pyx.get_position(token_id)
    local qty = pos and pos.qty or 0

    local bid_price = math.floor((fair - CONFIG.spread) * 100 + 0.5) / 100
    local ask_price = math.ceil((fair + CONFIG.spread) * 100 - 0.5) / 100

    if bid_price <= 0 or bid_price >= 1 then bid_price = nil end
    if ask_price <= 0 or ask_price >= 1 then ask_price = nil end
    if qty <= 0 then ask_price = nil end

    if bid_price == last_bid and ask_price == last_ask then return end
    last_bid = bid_price
    last_ask = ask_price

    pyx.cancel_all()

    if bid_price then
        pyx.buy(token_id, condition_id, bid_price, CONFIG.order_size)
    end
    if ask_price then
        pyx.sell(token_id, condition_id, ask_price, math.min(qty, CONFIG.order_size))
    end
end

function on_price(ctx)
    if not token_id then
        token_id = ctx.token_id
        condition_id = ctx.condition_id
        pyx.log("Locked onto " .. (ctx.outcome or "unknown"))
    end
    if ctx.token_id ~= token_id then return end

    local synth = pyx.get_synth_data()
    if not synth or not synth.hourly then return end

    local fair = synth.hourly.synth_prob_up
    update_quotes(fair)
end

function on_fill(fill)
    last_bid = nil
    last_ask = nil
    pyx.log(string.format("FILL: %s %.4f @ %.4f | pnl=%.4f",
        fill.side, fill.qty, fill.price, fill.realized_pnl))
end

Example: Edge-Based Directional Trading

Buy when Synth detects a significant mispricing relative to Polymarket:

local CONFIG = {
    edge_threshold = 0.05,  -- 5% minimum edge
    order_size     = 20.0,
    max_position   = 100.0,
}

local token_id = nil
local condition_id = nil

function on_price(ctx)
    if not token_id then
        token_id = ctx.token_id
        condition_id = ctx.condition_id
        pyx.log("Tracking " .. (ctx.outcome or "unknown"))
    end
    if ctx.token_id ~= token_id then return end

    local synth = pyx.get_synth_data()
    if not synth or not synth.hourly then return end

    local pos = pyx.get_position(token_id)
    local qty = pos and pos.qty or 0

    local edge = synth.hourly.synth_prob_up - ctx.ask
    if edge > CONFIG.edge_threshold and qty < CONFIG.max_position then
        pyx.buy(token_id, condition_id, ctx.ask, CONFIG.order_size)
        pyx.log(string.format("BUY edge=%.3f synth=%.3f ask=%.3f",
            edge, synth.hourly.synth_prob_up, ctx.ask))
    end
end

function on_fill(fill)
    pyx.log(string.format("FILL: %s %.4f @ %.4f (%s)",
        fill.side, fill.qty, fill.price, fill.liquidity))
end

Notes

  • Data availability depends on Synthdata's API uptime. Always check for nil before accessing fields.
  • The synth_prob_up value is a probability (0–1), which maps directly to the fair price of the Up token on Polymarket.
  • For the Down token, the fair price is 1 - synth_prob_up.
  • Synthdata currently supports BTC markets. Additional assets may be added in the future.
  • Synth data updates roughly every 60 seconds - avoid making trading decisions that depend on sub-second freshness from this data.

On this page