On this page
Arbitrage Signal Stream
Artery ships a streaming arbitrage detector: every 5s the system recomputes spreads for every multi-provider event, applies per-provider fees, and pushes a signal whenever the net edge crosses a profitable threshold.
Channel layout
| Channel | What |
|---|---|
artery:stream:event:<universalId> | Full spread snapshot every time a leg's mid moves ≥ 0.5¢ |
artery:stream:arbitrage:<universalId> | Only when net edge after fees ≥ 50 bps; emitted on every tick the condition holds |
Both channels carry the standard event envelope:
json{
"type": "event",
"subscription_id": "uuid",
"data": {
"type": "event.arbitrage",
"marketId": "evt_e8b415bf-...",
"payload": { /* see below */ },
"receivedAt": 1714928400000
}
}Arbitrage payload
json{
"universalId": "evt_e8b415bf-be26-4766-9680-998549fe01b1",
"fetchedAt": "2026-05-09T05:58:05.037Z",
"buyProvider": "polymarket",
"buyMarketId": "0xa8aceb0fb04828602057be...",
"buyMid": 0.0095,
"sellProvider": "kalshi",
"sellMarketId": "KXBTCD-26MAY0917-T69999.99",
"sellMid": 0.030,
"grossEdge": 0.0205,
"netEdge": 0.0203,
"annualizedReturn": 7.41
}| Field | Meaning |
|---|---|
buyProvider / buyMid | Cheapest YES — execute the BUY leg here |
sellProvider / sellMid | Richest YES — execute the SELL leg here |
grossEdge | Raw mid-to-mid difference (sellMid − buyMid) |
netEdge | After both legs' round-trip taker fees |
annualizedReturn | netEdge × 365 — for ranking only, not actual yield |
Quickstart
- List the universal events you want to watch
bash
TOKEN="art_live_..." curl 'https://api.artery.questflow.ai/v1/events?underlying=BTC&min_providers=2' \ -H "Authorization: Bearer $TOKEN" \ | jq -r '.events[].universalId' - Subscribe via WebSocket
js
import WebSocket from 'ws'; const TOKEN = process.env.ART_TOKEN; const eventIds = [/* from step 1 */]; const ws = new WebSocket(`wss://api.artery.questflow.ai/v1/stream?token=${TOKEN}`); ws.on('open', () => { const channels = eventIds.flatMap((id) => [ `artery:stream:arbitrage:${id}`, `artery:stream:event:${id}`, ]); ws.send(JSON.stringify({ type: 'subscribe', channels })); }); ws.on('message', (raw) => { const m = JSON.parse(raw.toString()); if (m.type === 'pong') return; if (m.data?.type === 'event.arbitrage') { const p = m.data.payload; console.log( `BUY ${p.buyProvider} ${p.buyMarketId} @ ${p.buyMid} → ` + `SELL ${p.sellProvider} ${p.sellMarketId} @ ${p.sellMid} ` + `net=${(p.netEdge * 100).toFixed(2)}c` ); } }); - Tune the threshold
Default minimum net edge for emission is 50 bps. To see all positive signals (including marginal):
bash
curl "https://api.artery.questflow.ai/v1/events/<id>/arbitrage" \ -H "Authorization: Bearer $TOKEN"The
/arbitrageREST endpoint uses threshold 0 — it surfaces every positive net edge, even if too small to push to the stream.
Fee schedule
The signal engine deducts a conservative round-trip taker fee per provider:
| Provider | Default takerBps | Override env |
|---|---|---|
| Polymarket | 20 | ART_FEES_POLYMARKET_TAKER_BPS |
| Kalshi | 50 | ART_FEES_KALSHI_TAKER_BPS |
| Hyperliquid HIP-4 | 14 | ART_FEES_HYPERLIQUID_HIP4_TAKER_BPS |
| Hyperliquid Perp | 14 | ART_FEES_HYPERLIQUID_PERP_TAKER_BPS |
| Hyperliquid DEX | 30 | ART_FEES_HYPERLIQUID_DEX_TAKER_BPS |
Set in your deployment env to tighten or loosen the filter:
bashART_FEES_KALSHI_TAKER_BPS=25 pnpm --filter @artery/api devWhat the signals don't account for
The net edge is upper-bound — many real-world frictions can erode it:
- Slippage — if you're moving size, the displayed mid won't fill at that price
- Funding rate exposure for leveraged positions (HL Perp / HIP-3)
- Settlement risk — UMA disputes, Kalshi force-majeure clauses
- Latency — the snapshot was true 5s ago; market may have moved
- KYC / geo restrictions — can you actually trade on both legs?
Treat these as triggers for further analysis, not "press 'execute' to win".
Settlement consensus tracking
Each universal event also exposes its historical settlement record across providers:
bashcurl "https://api.artery.questflow.ai/v1/events/<id>/settlement" -H "Authorization: Bearer $TOKEN"json{
"universalId": "evt_...",
"records": [
{ "provider": "polymarket", "polarity": "yes", "resolvedAt": "..." },
{ "provider": "kalshi", "polarity": "yes", "resolvedAt": "..." }
],
"consensusScore": 1.0
}A consensus score < 1.0 means the linked markets disagreed on resolution — a flashing red indicator that this kind of event has structural settlement risk and any "arbitrage" should be sized accordingly.
See also
- Event correlation — how universal events are constructed
- Cross-platform spread — REST snapshot endpoint and price normalisation
- Streaming — WebSocket transport details