Welcome to API Academy

Module 15 · Production · ~25 min

Market making.

By the end of this module, you’ll be the one collecting fees on Limitless instead of paying them, your bot quoting both sides of the book and getting paid for the privilege of waiting.

To get there, you’ll quote both sides of the book, manage inventory, and earn the spread, the first real strategy in the Production tier.

Production tier · Reference card
Quick answer

How does a market-making bot work on Limitless?

It posts two passive limit orders, a bid below the mid and an ask above it, and collects the spread whenever someone crosses them; you’re renting your balance sheet to impatient traders, not betting on direction. The loop: pull a fresh mid from the orderbook, place a pair of OrderType.GTC orders with postOnly: true (rejected if they would cross, which guarantees maker fees), and when the mid drifts past a reposition threshold (0.5% in the example), cancelAll(marketSlug) and re-quote. A smart maker also skews: when inventory builds up long, it widens the bid and tightens the ask to lean the next fill back toward flat, reading live balances from PortfolioFetcher.getPositions(). The risk you’re paid for is inventory risk, being left holding a position when the mid moves against you.

Endpoints verified 2026-06-09 against the OpenAPI spec.

Section 01

The market maker’s job.

A market maker posts two passive limit orders, a bid below the mid and an ask above it, and collects the spread every time someone crosses them. You’re not betting on direction. You’re renting your balance sheet to impatient traders who want to move right now.

Bid

Willing to buy YES slightly below mid. Filled when sellers hit you.

Spread

The gap between your bid and ask. Wider = safer but slower. Tighter = faster but more inventory risk.

Ask

Willing to sell YES slightly above mid. Filled when buyers lift you.

The risk you earn the spread for is inventory risk, being left holding a position when the mid moves against you.

Section 02

Quote management.

A basic quote placer: pull the orderbook mid, place a pair of OrderType.GTC orders on the YES token with postOnly: true so you always earn maker fees, and cancelAll(marketSlug) before re-quoting when the mid drifts past a reposition threshold. Everything else is tuning. Wrap the loop in the SDK’s retry helper so transient 429s and 5xxs don’t kill your maker.

How to run this

  1. Set LIMITLESS_API_KEY and PRIVATE_KEY in your environment, this loop places real CLOB orders. Swap btc-100k-weekly at the bottom for a low-volume slug you’re comfortable quoting.
  2. Save the snippet above as quote-maker.ts, then run npx tsx quote-maker.ts.
  3. Every two seconds, your process cancels existing orders and posts a fresh maker pair at mid × (1 ± 0.02) whenever the mid has drifted more than 0.5%. Check the Limitless UI, you should see your bid and ask sitting on the book with post-only status.
// Module 15, Basic quote placer with reposition on mid drift.

import {
  HttpClient,
  MarketFetcher,
  OrderClient,
  Side,
  OrderType,
  withRetry,
} from '@limitless-exchange/sdk';
import { Wallet } from 'ethers';

const SPREAD     = 0.02;   // quote ±2% of mid
const REPOSITION = 0.005;  // re-quote if mid moves > 0.5%
const SIZE       = 100;    // shares per quote

const httpClient    = new HttpClient({ apiKey: process.env.LIMITLESS_API_KEY });
const marketFetcher = new MarketFetcher(httpClient);
const wallet        = new Wallet(process.env.PRIVATE_KEY!);
const orderClient   = new OrderClient({ httpClient, wallet, marketFetcher });

let quotedMid = 0;

async function requote(slug: string) {
  // 1. Pull orderbook for a fresh mid.
  //    GET /markets/{slug}/orderbook
  const book = await marketFetcher.getOrderBook(slug);
  const bestBid = Number(book.bids[0]?.price ?? 0);
  const bestAsk = Number(book.asks[0]?.price ?? 0);
  if (!bestBid || !bestAsk) return;
  const mid = (bestBid + bestAsk) / 2;

  // 2. Skip if drift is below the reposition threshold.
  if (quotedMid && Math.abs(mid - quotedMid) / quotedMid < REPOSITION) return;

  // 3. Cancel every live order on this market before re-quoting.
  //    DELETE /orders/all/{slug}
  await orderClient.cancelAll(slug);

  // 4. Place a fresh maker-only pair on the YES token.
  const market = await marketFetcher.getMarket(slug);
  const yesTokenId = market.positionIds[0];

  await withRetry(
    () => orderClient.createOrder({
      marketSlug: slug,
      tokenId:    yesTokenId,
      side:       Side.BUY,
      price:      mid * (1 - SPREAD),
      size:       SIZE,
      orderType:  OrderType.GTC,
      postOnly:   true,          // rejects if it would cross, guarantees maker fees
    }),
    { statusCodes: [429, 500, 502, 503, 504], maxRetries: 3, delays: [1000, 2000, 4000] },
  );

  await withRetry(
    () => orderClient.createOrder({
      marketSlug: slug,
      tokenId:    yesTokenId,
      side:       Side.SELL,
      price:      mid * (1 + SPREAD),
      size:       SIZE,
      orderType:  OrderType.GTC,
      postOnly:   true,
    }),
    { statusCodes: [429, 500, 502, 503, 504], maxRetries: 3, delays: [1000, 2000, 4000] },
  );

  quotedMid = mid;
}

setInterval(() => {
  requote('btc-100k-weekly').catch(console.error);
}, 2_000);

Section 03

Inventory skew.

A naive maker quotes symmetric spreads. A smart one skews, when inventory gets long, it tightens the ask and widens the bid to lean the next fill flat. Pull your current CLOB balances from PortfolioFetcher.getPositions() and feed them into the skew helper every loop.

// Module 15, Inventory-skewed quotes, driven by real CLOB balances.

import { HttpClient, PortfolioFetcher } from '@limitless-exchange/sdk';

interface SkewParams {
  mid:           number;
  baseSpread:    number;  // e.g. 0.02
  inventory:     number;  // signed units; >0 long YES, <0 short
  inventoryCap:  number;  // hard ceiling
  skewStrength:  number;  // 0..1
}

export function skewedQuotes(p: SkewParams): { bid: number; ask: number } {
  const ratio = Math.max(-1, Math.min(1, p.inventory / p.inventoryCap));
  const skew  = ratio * p.skewStrength * p.baseSpread;

  // long (ratio > 0): widen bid, tighten ask
  const bid = p.mid * (1 - p.baseSpread - skew);
  const ask = p.mid * (1 + p.baseSpread - skew);
  return { bid, ask };
}

const httpClient = new HttpClient({ apiKey: process.env.LIMITLESS_API_KEY });
const portfolio  = new PortfolioFetcher(httpClient);

async function quotesForMarket(slug: string, mid: number) {
  // GET /portfolio/positions
  const positions = await portfolio.getPositions();
  const clobEntry = positions.clob.find((p) => p.market.slug === slug);

  // marketValue is the canonical inventory signal on PositionDataDto
  // (5 required fields: cost, fillPrice, realisedPnl, unrealizedPnl, marketValue).
  // It's the current USDC-denominated value of each side in token decimals.
  const yesValue = Number(clobEntry?.positions?.yes?.marketValue ?? 0);
  const noValue  = Number(clobEntry?.positions?.no?.marketValue  ?? 0);
  const inventory = yesValue - noValue;

  return skewedQuotes({
    mid,
    baseSpread:   0.02,
    inventory,
    inventoryCap: 100,
    skewStrength: 0.5,
  });
}

quotesForMarket('btc-100k-weekly', 0.52).then(console.log).catch(console.error);

How to run this

  1. Set LIMITLESS_API_KEY, this helper only reads positions, it doesn’t place orders. Already have a position in btc-100k-weekly? Good. No position? Point the slug at a market you’re in so the skew isn’t zero.
  2. Save the snippet above as inventory-skew.ts, then run npx tsx inventory-skew.ts.
  3. Terminal prints a { bid, ask } pair for a mid of 0.52. If you’re long YES, the bid is pulled lower and the ask is pulled lower too, the whole fence leans against your inventory. Flip the sign of the position and both quotes shift up.

Section 04

Adverse selection & toxic flow.

When not to make markets

Market makers lose money when the people crossing them know something they don’t. That’s adverse selection, you’re getting filled exactly because the price is about to move against you. These are the three environments where a maker should stop quoting immediately.

Event windows

Don’t quote into scheduled resolutions, court decisions, election calls, earnings prints, or macro data releases. The flow that hits you seconds before an event is almost always informed.

Thin books

If total depth around the mid is less than a few multiples of your quote size, a single market order can move the mid through both your quotes. You’ll take two fills in the wrong direction before you can even cancel.

Informed counterparties

Large repeat flow from a single wallet that always seems to pick the right side isn’t luck. Track your per-counterparty hit rate; back off anyone you keep losing to.

A profitable maker widens or withdraws faster than they cancel. Your event calendar, book-depth check, and counterparty-score check should all be able to pull quotes inside a single loop iteration.

Common questions

Market making on Limitless: what people ask

Each answer also ships invisibly as schema.org FAQ data for search engines and AI assistants. Tap a question to expand.

  1. What does postOnly do on a Limitless order?
    postOnly: true makes the order strictly passive: if it would cross the book and fill immediately, it’s rejected instead of executed. For a market maker that’s the point, every fill you do get is a maker fill, so you always earn maker fees rather than paying taker. The quote placer pairs it with OrderType.GTC on both the bid and the ask of the YES token.
  2. When should a market-making bot re-quote?
    When the mid drifts past a reposition threshold, not on every tick. The example loop checks every two seconds and re-quotes only when the mid has moved more than 0.5% from the last quoted mid, which prevents thrashing. The sequence matters: cancelAll(marketSlug) (the DELETE /orders/all/{slug} path) first, then post the fresh maker pair at mid × (1 ± spread), then record the new quoted mid. Wrap the calls in the SDK retry helper so a transient 429 can’t kill the maker.
  3. What is inventory skew in market making?
    Tilting both quotes against your current position so the next fill leans you back toward flat. Compute signed inventory as the YES marketValue minus the NO marketValue from PortfolioFetcher.getPositions(); when you’re long, widen the bid and tighten the ask. Skew is a gradient, not a fence: hard-cap inventory in code on every quote emit, before any spread or skew math, because in a strong run the market can trade through your bids faster than skew keeps up.
  4. What risk is a market maker actually paid for?
    Inventory risk: being left holding a position when the mid moves against you. A maker isn’t betting on direction; the spread is rent for providing immediacy to traders who want to move right now. The tuning knob is the spread itself: wider is safer but fills slower, tighter fills faster but carries more inventory risk. The spread you collect is your compensation for bearing that risk.
  5. When should a market maker stop quoting?
    Three environments: event windows, scheduled resolutions, earnings prints, election calls, macro releases, where the flow that hits you seconds before the event is almost always informed; thin books, where one market order can move the mid through both your quotes before you can cancel; and informed counterparties, a single wallet that keeps picking the right side, so track per-counterparty hit rate and back off anyone you keep losing to. All three checks should be able to pull quotes inside a single loop iteration.

Section 05

Module checklist.

Tick each item once you’ve actually done it. The Continue button unlocks at 5/5.

Module 15 complete

Quoting live.

You’re a maker, not a taker. Other traders cross your spread to get filled, and the difference is your fee, the same business model that funds entire trading firms, running on your code instead of theirs.

Concretely, you’re posting both sides of the book and collecting the spread. Three things you walk away with:

01

A two-sided quote loop that cancels and repositions on every 0.5% mid drift, posts postOnly GTC orders so you always earn maker fees, and wraps every call in withRetry so a transient 429 can’t kill the bot.

02

A skewedQuotes() helper that reads your live PortfolioFetcher.getPositions() inventory and leans both sides of the fence against your current YES−NO imbalance.

03

A concrete list of conditions under which a maker should widen or withdraw, event windows, thin books, and informed counterparty flow, plus the mental model that a profitable maker pulls faster than they cancel.

Next up: hunting parity gaps, YES + NO ≠ $1, sibling markets out of sync, and the execution code that captures the difference before it closes.

Complete the checklist above to unlock