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))
endKey concepts demonstrated:
- State persistence across callbacks (
pricestable,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
endKey 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
endKey concepts demonstrated:
- Timer-based strategy using
on_tickinstead ofon_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
endKey concepts demonstrated:
- Event-driven strategy using
on_fillas 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))
endKey concepts demonstrated:
- Subscribing to Binance spot data with
pyx.subscribe_spot - Using
on_spot_priceas the primary trading signal - Cross-market signal: external spot price driving Polymarket orders
- Moving average deviation as a trade trigger