PYX

Example Strategies

Complete, runnable strategy examples to learn from and adapt.

Momentum pair trader

Tracks price momentum using an exponential moving average and trades the direction. Buys the favored token and sells the opposing one when momentum is clear and spread is tight.

-- Config
local UP_TOKEN    = "abc123..."
local DOWN_TOKEN  = "def456..."
local CONDITION   = "0x..."
local BUY_SIZE    = 0.15
local SELL_SIZE   = 0.10
local THRESHOLD   = 0.04
local EMA_PERIOD  = 12
local MAX_ACTIVE  = 4

-- State
local prices: {number} = {}
local tick_count = 0

-- EMA helper
local function ema(history: {number}, period: number): number?
    if #history < period then
		return nil
    end
    local k = 2 / (period + 1)
    local val = history[#history - period + 1]
    for i = #history - period + 2, #history do
		val = history[i] * k + val * (1 - k)
    end
    return val
end

local function get_qty(token_id: string): number
    local pos = pyx.get_position(token_id)
    return if pos then pos.qty else 0
end

function on_tick(ctx)
    tick_count += 1
    pyx.log(string.format("tick %d | bal=%.2f | active=%d",
		tick_count, pyx.get_balance(), pyx.active_orders()))
end

function on_price(ctx)
    table.insert(prices, ctx.mid)
    if #prices > 100 then
		table.remove(prices, 1)
    end

    local momentum = ema(prices, EMA_PERIOD)
    if momentum == nil then return end
    if pyx.active_orders() >= MAX_ACTIVE then return end
    if pyx.pending_orders() > 0 then return end
    if ctx.spread >= THRESHOLD then return end

    if ctx.mid > momentum then
		pyx.buy(UP_TOKEN, CONDITION, ctx.ask, BUY_SIZE)
		if get_qty(DOWN_TOKEN) > SELL_SIZE then
		    pyx.sell(DOWN_TOKEN, CONDITION, ctx.bid, SELL_SIZE)
		end
    elseif ctx.mid < momentum then
		pyx.buy(DOWN_TOKEN, CONDITION, ctx.ask, BUY_SIZE)
		if get_qty(UP_TOKEN) > SELL_SIZE then
		    pyx.sell(UP_TOKEN, CONDITION, ctx.bid, SELL_SIZE)
		end
    end
end

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

Key concepts demonstrated:

  • State persistence across callbacks (prices table, tick_count)
  • EMA calculation with typed helper function
  • Guard clauses to prevent over-trading (max active, pending check, spread filter)
  • Pair trading logic (buy one side, sell the other)

Simple spread capture

Places a buy below mid and a sell above mid, capturing the spread when both fill. Resets after each fill cycle.

local TOKEN     = "abc123..."
local CONDITION = "0x..."
local SIZE      = 0.10
local EDGE      = 0.02  -- how far from mid to place orders

local buy_placed = false
local sell_placed = false

function on_price(ctx)
    -- Only place if we have no active orders
    if pyx.active_orders() > 0 then return end
    if pyx.pending_orders() > 0 then return end

    -- Need tight spread to make this work
    if ctx.spread > 0.04 then return end

    local buy_price = ctx.mid - EDGE
    local sell_price = ctx.mid + EDGE

    -- Only buy if we don't hold too much
    local pos = pyx.get_position(TOKEN)
    local qty = if pos then pos.qty else 0

    if qty < 1.0 then
		pyx.buy(TOKEN, CONDITION, buy_price, SIZE)
    end

    if qty > 0 then
		pyx.sell(TOKEN, CONDITION, sell_price, SIZE)
    end
end

function on_fill(fill)
    pyx.log(string.format("%s filled @ %.4f", fill.side, fill.price))

    -- Log realized PnL after sells
    if fill.side == "sell" then
		local pos = pyx.get_position(fill.token_id)
		if pos then
		    pyx.log(string.format("Realized PnL: %.4f", pos.realized_pnl))
		end
    end
end

Key concepts demonstrated:

  • Spread capture strategy (buy low, sell high around mid)
  • Position-aware order placement (buy only if under limit, sell only if holding)
  • Logging realized P&L from position data

Inventory rebalancer

Runs on a tick timer. Checks if the portfolio is imbalanced between two tokens and rebalances toward a target ratio.

local UP_TOKEN    = "abc123..."
local DOWN_TOKEN  = "def456..."
local CONDITION   = "0x..."
local TARGET_RATIO = 0.5  -- 50/50 split
local TOLERANCE    = 0.1  -- rebalance when >10% off target
local ORDER_SIZE   = 0.05

local function get_qty(token_id: string): number
    local pos = pyx.get_position(token_id)
    return if pos then math.max(pos.qty, 0) else 0
end

function on_tick(ctx)
    -- Don't rebalance while orders are pending
    if pyx.active_orders() > 0 or pyx.pending_orders() > 0 then
		return
    end

    local up_qty = get_qty(UP_TOKEN)
    local down_qty = get_qty(DOWN_TOKEN)
    local total = up_qty + down_qty

    if total == 0 then
		pyx.log("No positions yet")
		return
    end

    local up_ratio = up_qty / total
    local deviation = up_ratio - TARGET_RATIO

    pyx.log(string.format("UP=%.2f DOWN=%.2f ratio=%.2f dev=%.2f",
		up_qty, down_qty, up_ratio, deviation))

    if math.abs(deviation) < TOLERANCE then
		return  -- within tolerance
    end

    -- Need price data for order placement
    -- In a real strategy, you'd track latest prices from on_price
    -- For simplicity, we use a fixed price here
    if deviation > 0 then
		-- Too much UP, sell UP and buy DOWN
		pyx.sell(UP_TOKEN, CONDITION, 0.50, ORDER_SIZE)
		pyx.buy(DOWN_TOKEN, CONDITION, 0.50, ORDER_SIZE)
		pyx.log("Rebalancing: sell UP, buy DOWN")
    else
		-- Too much DOWN, sell DOWN and buy UP
		pyx.sell(DOWN_TOKEN, CONDITION, 0.50, ORDER_SIZE)
		pyx.buy(UP_TOKEN, CONDITION, 0.50, ORDER_SIZE)
		pyx.log("Rebalancing: sell DOWN, buy UP")
    end
end

Key concepts demonstrated:

  • Timer-based strategy using on_tick instead of on_price
  • Portfolio ratio calculation and deviation tracking
  • Tolerance-based rebalancing (don't trade on every small deviation)

Reactive hedger

Listens to fills and immediately hedges by placing an opposing order on the other token.

local UP_TOKEN    = "abc123..."
local DOWN_TOKEN  = "def456..."
local CONDITION   = "0x..."
local HEDGE_RATIO = 0.8  -- hedge 80% of each fill

function on_price(ctx)
    -- Main strategy logic would go here
    -- For this example, assume orders are placed elsewhere
end

function on_fill(fill)
    local hedge_size = fill.qty * HEDGE_RATIO

    if fill.token_id == UP_TOKEN and fill.side == "buy" then
		-- Bought UP, hedge by buying DOWN
		pyx.buy(DOWN_TOKEN, CONDITION, fill.price, hedge_size)
		pyx.log(string.format("Hedging UP buy: buying %.4f DOWN", hedge_size))

    elseif fill.token_id == DOWN_TOKEN and fill.side == "buy" then
		-- Bought DOWN, hedge by buying UP
		pyx.buy(UP_TOKEN, CONDITION, fill.price, hedge_size)
		pyx.log(string.format("Hedging DOWN buy: buying %.4f UP", hedge_size))
    end
end

function on_cancelled(order)
	    pyx.log("Hedge order cancelled: " .. order.order_id)
	    -- Could retry here
end

Key concepts demonstrated:

  • Event-driven strategy using on_fill as the primary trigger
  • Placing orders from within event callbacks (they're drained after the callback returns)
  • Hedging logic with configurable ratio

Spot-correlated trader

Uses Binance spot BTC price as a signal to trade a Polymarket binary market. When BTC pumps above a threshold, buys the "up" token; when it dumps, buys "down."

local UP_TOKEN    = "abc123..."
local DOWN_TOKEN  = "def456..."
local CONDITION   = "0x..."
local ORDER_SIZE  = 0.10
local MAX_ACTIVE  = 2

-- Track BTC moving average
local btc_prices: {number} = {}
local BTC_MA_LEN = 20

pyx.subscribe_spot({"BTC"})

local function avg(t: {number}): number
    local sum = 0
    for _, v in ipairs(t) do sum += v end
    return sum / #t
end

function on_spot_price(ctx)
    if ctx.symbol ~= "BTC" then return end

    table.insert(btc_prices, ctx.mid)
    if #btc_prices > BTC_MA_LEN then
        table.remove(btc_prices, 1)
    end
    if #btc_prices < BTC_MA_LEN then return end

    -- Skip if we already have orders working
    if pyx.active_orders() >= MAX_ACTIVE then return end
    if pyx.pending_orders() > 0 then return end

    local ma = avg(btc_prices)
    local deviation = (ctx.mid - ma) / ma

    if deviation > 0.001 then
        -- BTC trending up, buy the up token
        pyx.buy(UP_TOKEN, CONDITION, 0.50, ORDER_SIZE)
        pyx.log(string.format("BTC +%.3f%% -> buy UP", deviation * 100))
    elseif deviation < -0.001 then
        -- BTC trending down, buy the down token
        pyx.buy(DOWN_TOKEN, CONDITION, 0.50, ORDER_SIZE)
        pyx.log(string.format("BTC %.3f%% -> buy DOWN", deviation * 100))
    end
end

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

Key concepts demonstrated:

  • Subscribing to Binance spot data with pyx.subscribe_spot
  • Using on_spot_price as the primary trading signal
  • Cross-market signal: external spot price driving Polymarket orders
  • Moving average deviation as a trade trigger

On this page