Welcome to API Academy
Module 06 · API basics · ~25 min
Positions.
By the end of this module, your bot will know its own score, how much it’s holding right now, how much it has won or lost, and whether what the API says it owns matches what the chain says it owns.
To get there, you’ll pull your live portfolio, replay every fill, and compute realised + unrealised PnL from first principles. The same data powers your dashboards, your risk checks, and the on-chain reconciliation every production trading bot needs.
API basics tier · Reference cardHow do you read your positions from the Limitless API?
One call: GET /portfolio/positions, wrapped by the SDK’s PortfolioFetcher.getPositions(), returns everything you hold, CLOB, AMM, and group positions plus accumulated points and current rewards, using just your API key, no wallet signing. Fills come from two endpoints: GET /portfolio/trades returns a flat array of AMM trades in a single call, while GET /portfolio/history is the cursor-paginated feed (cursor + limit) that also covers CLOB fills, splits, merges, and claims. From those you compute both PnL numbers: realised is (exit price minus entry price) times closed size, minus fees, replayable from fills; unrealised is open size times (current mark minus average entry), moving with the book. Authenticated calls carry the three HMAC headers (lmts-api-key, lmts-timestamp, lmts-signature); the legacy X-API-Key is deprecated. The REST view is an indexed cache, so reconcile against on-chain balanceOf on a schedule.
Endpoints verified 2026-06-10 against the OpenAPI spec.
Authenticated calls carry the three HMAC headers (lmts-api-key + lmts-timestamp + lmts-signature) from Module 03; the legacy X-API-Key is deprecated.
Section 01
Reading your portfolio.
GET /portfolio/positions returns everything you hold in one call: CLOB positions, AMM positions, group positions, accumulated points, and current rewards. The SDK’s PortfolioFetcher.getPositions() wraps it.
How to run this
- Keep LIMITLESS_API_KEY set, this is a read-only call, no wallet signing needed.
- Save as portfolio-positions.ts, then run npx tsx portfolio-positions.ts.
- Save as portfolio_positions.py, then run python portfolio_positions.py.
- Save as main.go inside a Go module, then run go run main.go.
- You see your points total, reward balance, CLOB row count, and one line per CLOB position with YES and NO cost vs market value. Empty output is fine, you just haven’t opened positions yet.
// Module 06, Reading Your Portfolio
// Pull CLOB + AMM positions with rewards and accumulated points.
import { HttpClient, PortfolioFetcher } from '@limitless-exchange/sdk';
const httpClient = new HttpClient({
baseURL: 'https://api.limitless.exchange',
apiKey: process.env.LIMITLESS_API_KEY,
});
async function readPortfolio() {
const portfolio = new PortfolioFetcher(httpClient);
const positions = await portfolio.getPositions();
console.log('points total:', positions.accumulativePoints);
console.log('rewards :', positions.rewards);
console.log('CLOB rows :', positions.clob.length);
console.log('AMM rows :', positions.amm.length);
for (const p of positions.clob) {
const { yes, no } = p.positions;
console.log(
p.market.slug.padEnd(40),
`YES cost=${yes.cost} mktVal=${yes.marketValue}`,
`NO cost=${no.cost} mktVal=${no.marketValue}`,
);
}
}
readPortfolio().catch(console.error);
# Module 06, Reading Your Portfolio
# Pull CLOB + AMM positions with rewards and accumulated points.
import asyncio
from limitless_sdk import HttpClient
from limitless_sdk.portfolio import PortfolioFetcher
http_client = HttpClient()
async def read_portfolio() -> None:
portfolio = PortfolioFetcher(http_client)
positions = await portfolio.get_positions()
print("points total:", positions.get("accumulativePoints"))
print("rewards :", positions.get("rewards"))
print("CLOB rows :", len(positions.get("clob", [])))
print("AMM rows :", len(positions.get("amm", [])))
for p in positions.get("clob", []):
yes = p["positions"]["yes"]
no = p["positions"]["no"]
print(
f"{p['market']['slug']:<40} YES cost={yes['cost']} mktVal={yes['marketValue']}"
f" NO cost={no['cost']} mktVal={no['marketValue']}"
)
await http_client.close()
if __name__ == "__main__":
asyncio.run(read_portfolio())
// Module 06, Reading Your Portfolio
// Pull CLOB + AMM positions with rewards and accumulated points.
package main
import (
"context"
"fmt"
"log"
limitless "github.com/limitless-labs-group/limitless-exchange-go-sdk/limitless"
)
func main() {
ctx := context.Background()
client := limitless.NewHttpClient()
portfolio := limitless.NewPortfolioFetcher(client)
positions, err := portfolio.GetPositions(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("points total: %v\n", positions.AccumulativePoints)
fmt.Printf("rewards : %v\n", positions.Rewards)
fmt.Printf("CLOB rows : %d\n", len(positions.Clob))
fmt.Printf("AMM rows : %d\n", len(positions.Amm))
for _, p := range positions.Clob {
fmt.Printf("%-40s YES cost=%s mktVal=%s NO cost=%s mktVal=%s\n",
p.Market.Slug, p.Positions.Yes.Cost, p.Positions.Yes.MarketValue,
p.Positions.No.Cost, p.Positions.No.MarketValue)
}
}
Section 02
Fetching fills.
Positions tell you where you are. Fills tell you how you got there. GET /portfolio/trades returns every execution; each row has the market, side, size, price, fees, and timestamp.
// Module 06, Fetching Fills
// GET /portfolio/trades, raw HTTP (no SDK method exists for this endpoint).
import { createHmac } from 'node:crypto';
const BASE = 'https://api.limitless.exchange';
function signedHeaders(method: string, pathWithQuery: string, body = ''): Record<string, string> {
const ts = new Date().toISOString();
const msg = `${ts}\n${method}\n${pathWithQuery}\n${body}`;
const sig = createHmac('sha256', Buffer.from(process.env.LMTS_TOKEN_SECRET!, 'base64'))
.update(msg)
.digest('base64');
return {
'lmts-api-key': process.env.LMTS_TOKEN_ID!,
'lmts-timestamp': ts,
'lmts-signature': sig,
};
}
async function fetchFills() {
const res = await fetch(`${BASE}/portfolio/trades`, { headers: signedHeaders('GET', '/portfolio/trades') });
const fills = await res.json(); // flat array, AMM trades only
console.log(`Fetched ${fills.length} fills`);
for (const f of fills) {
console.log(
f.blockTimestamp, // ISO 8601
f.market.slug,
f.strategy, // 'Buy' or 'Sell'
`amount=${f.outcomeTokenAmount}`,
`price=${f.outcomeTokenPrice}`,
);
}
}
fetchFills().catch(console.error);
# Module 06, Fetching Fills
# GET /portfolio/trades, raw HTTP (no SDK method exists for this endpoint).
import asyncio, base64, datetime, hashlib, hmac, os, aiohttp
BASE = "https://api.limitless.exchange"
def signed_headers(method: str, path_with_query: str, body: str = "") -> dict[str, str]:
now = datetime.datetime.now(datetime.timezone.utc)
ts = now.strftime("%Y-%m-%dT%H:%M:%S.") + f"{now.microsecond // 1000:03d}Z"
msg = f"{ts}\n{method}\n{path_with_query}\n{body}"
sig = base64.b64encode(
hmac.new(base64.b64decode(os.environ["LMTS_TOKEN_SECRET"]),
msg.encode("utf-8"), hashlib.sha256).digest()
).decode("ascii")
return {
"lmts-api-key": os.environ["LMTS_TOKEN_ID"],
"lmts-timestamp": ts,
"lmts-signature": sig,
}
async def fetch_fills() -> None:
async with aiohttp.ClientSession() as session:
async with session.get(f"{BASE}/portfolio/trades", headers=signed_headers("GET", "/portfolio/trades")) as res:
fills = await res.json() # flat array, AMM trades only
print(f"Fetched {len(fills)} fills")
for f in fills:
print(
f["blockTimestamp"], # ISO 8601
f["market"]["slug"],
f["strategy"], # 'Buy' or 'Sell'
f"amount={f['outcomeTokenAmount']}",
f"price={f['outcomeTokenPrice']}",
)
if __name__ == "__main__":
asyncio.run(fetch_fills())
// Module 06, Fetching Fills
// GET /portfolio/trades, raw HTTP (no SDK method exists for this endpoint).
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"time"
)
type Trade struct {
BlockTimestamp string `json:"blockTimestamp"`
Strategy string `json:"strategy"`
OutcomeTokenAmount string `json:"outcomeTokenAmount"`
OutcomeTokenPrice string `json:"outcomeTokenPrice"`
CollateralAmount string `json:"collateralAmount"`
Market struct {
Slug string `json:"slug"`
Title string `json:"title"`
} `json:"market"`
}
func signedHeaders(method, pathWithQuery, body string) map[string]string {
ts := time.Now().UTC().Format("2006-01-02T15:04:05.000Z")
msg := ts + "\n" + method + "\n" + pathWithQuery + "\n" + body
secret, _ := base64.StdEncoding.DecodeString(os.Getenv("LMTS_TOKEN_SECRET"))
mac := hmac.New(sha256.New, secret)
mac.Write([]byte(msg))
sig := base64.StdEncoding.EncodeToString(mac.Sum(nil))
return map[string]string{
"lmts-api-key": os.Getenv("LMTS_TOKEN_ID"),
"lmts-timestamp": ts,
"lmts-signature": sig,
}
}
func main() {
req, _ := http.NewRequest("GET", "https://api.limitless.exchange/portfolio/trades", nil)
for k, v := range signedHeaders("GET", "/portfolio/trades", "") {
req.Header.Set(k, v)
}
res, err := http.DefaultClient.Do(req)
if err != nil { log.Fatal(err) }
defer res.Body.Close()
var fills []Trade
if err := json.NewDecoder(res.Body).Decode(&fills); err != nil { log.Fatal(err) }
fmt.Printf("Fetched %d fills\n", len(fills))
for _, f := range fills {
fmt.Printf("%s %-40s %s amount=%s price=%s\n",
f.BlockTimestamp, f.Market.Slug, f.Strategy,
f.OutcomeTokenAmount, f.OutcomeTokenPrice)
}
}
How to run this
- Set LMTS_TOKEN_ID + LMTS_TOKEN_SECRET (your scoped HMAC token). This snippet uses raw fetch/aiohttp/net/http because no SDK method wraps /portfolio/trades yet.
- Save the snippet above as fetch-fills.ts, then run npx tsx fetch-fills.ts.
- Save the snippet above as fetch_fills.py, then run python fetch_fills.py.
- Save the snippet above as main.go inside a Go module, then run go run main.go.
- Each fill prints with its timestamp, market slug, strategy (Buy or Sell), amount, and execution price, your full AMM trading history in one feed.
Section 03
Computing PnL.
PnL splits into two numbers and you need both. Realised is cash that already moved, you sold shares, the USDC hit your balance, the number can’t change. Unrealised is what your open positions would be worth if you closed them right now at the current mark, it moves every time the book does. Treat the first as your track record and the second as live exposure. The two cards below define each precisely; the code under them computes both from the endpoints you already know.
Realised PnL
Sum of (exit price − entry price) × closed size, minus fees. Comes entirely from the fills stream. Deterministic: replay the fills and you get the same number every time.
Unrealised PnL
Open size × (current mark − average entry). Needs a live mark price from the order book or mid. Moves tick by tick, so quote it alongside a timestamp.
// Module 06, Computing PnL
// Realised from fills, unrealised from open positions + live marks.
import { createHmac } from 'node:crypto';
import { HttpClient, PortfolioFetcher, MarketFetcher } from '@limitless-exchange/sdk';
const BASE = 'https://api.limitless.exchange';
function signedHeaders(method: string, pathWithQuery: string, body = ''): Record<string, string> {
const ts = new Date().toISOString();
const msg = `${ts}\n${method}\n${pathWithQuery}\n${body}`;
const sig = createHmac('sha256', Buffer.from(process.env.LMTS_TOKEN_SECRET!, 'base64'))
.update(msg)
.digest('base64');
return {
'lmts-api-key': process.env.LMTS_TOKEN_ID!,
'lmts-timestamp': ts,
'lmts-signature': sig,
};
}
const httpClient = new HttpClient({ apiKey: process.env.LIMITLESS_API_KEY });
const portfolio = new PortfolioFetcher(httpClient);
const markets = new MarketFetcher(httpClient);
async function computePnL() {
// Fills via raw HTTP (no SDK method for GET /portfolio/trades).
const res = await fetch(`${BASE}/portfolio/trades`, { headers: signedHeaders('GET', '/portfolio/trades') });
const fills = await res.json();
const positions = await portfolio.getPositions();
// Realised: net collateral flow across all fills.
const realised = fills.reduce((acc: number, f: any) => {
const flow = Number(f.collateralAmount) / 1e6; // USDC 6 decimals
return acc + (f.strategy === 'Sell' ? flow : -flow);
}, 0);
// Unrealised: (marketValue - cost) per CLOB position.
let unrealised = 0;
for (const p of positions.clob) {
const yesCost = Number(p.positions.yes.cost) / 1e6;
const yesValue = Number(p.positions.yes.marketValue) / 1e6;
if (yesCost === 0) continue;
unrealised += yesValue - yesCost;
}
console.log('realised :', realised.toFixed(2));
console.log('unrealised:', unrealised.toFixed(2));
console.log('total :', (realised + unrealised).toFixed(2));
}
computePnL().catch(console.error);
# Module 06, Computing PnL
# Realised from fills (raw HTTP), unrealised from position data (SDK).
import asyncio, base64, datetime, hashlib, hmac, os, aiohttp
from limitless_sdk import HttpClient
from limitless_sdk.portfolio import PortfolioFetcher
BASE = "https://api.limitless.exchange"
def signed_headers(method: str, path_with_query: str, body: str = "") -> dict[str, str]:
now = datetime.datetime.now(datetime.timezone.utc)
ts = now.strftime("%Y-%m-%dT%H:%M:%S.") + f"{now.microsecond // 1000:03d}Z"
msg = f"{ts}\n{method}\n{path_with_query}\n{body}"
sig = base64.b64encode(
hmac.new(base64.b64decode(os.environ["LMTS_TOKEN_SECRET"]),
msg.encode("utf-8"), hashlib.sha256).digest()
).decode("ascii")
return {
"lmts-api-key": os.environ["LMTS_TOKEN_ID"],
"lmts-timestamp": ts,
"lmts-signature": sig,
}
http_client = HttpClient()
portfolio = PortfolioFetcher(http_client)
async def compute_pnl() -> None:
# Fills via raw HTTP (no SDK method for GET /portfolio/trades).
async with aiohttp.ClientSession() as session:
async with session.get(f"{BASE}/portfolio/trades", headers=signed_headers("GET", "/portfolio/trades")) as res:
fills = await res.json()
positions = await portfolio.get_positions()
# Realised: net collateral flow across all fills.
realised = 0.0
for f in fills:
flow = float(f["collateralAmount"]) / 1e6 # USDC 6 decimals
realised += flow if f["strategy"] == "Sell" else -flow
# Unrealised: (marketValue - cost) per CLOB position.
unrealised = 0.0
for p in positions.get("clob", []):
yes_cost = float(p["positions"]["yes"]["cost"]) / 1e6
yes_value = float(p["positions"]["yes"]["marketValue"]) / 1e6
if yes_cost == 0:
continue
unrealised += yes_value - yes_cost
print(f"realised : {realised:.2f}")
print(f"unrealised: {unrealised:.2f}")
print(f"total : {realised + unrealised:.2f}")
await http_client.close()
if __name__ == "__main__":
asyncio.run(compute_pnl())
// Module 06, Computing PnL
// Realised from fills (raw HTTP), unrealised from position data (SDK).
package main
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strconv"
"time"
limitless "github.com/limitless-labs-group/limitless-exchange-go-sdk/limitless"
)
type Trade struct {
Strategy string `json:"strategy"`
CollateralAmount string `json:"collateralAmount"`
}
func signedHeaders(method, pathWithQuery, body string) map[string]string {
ts := time.Now().UTC().Format("2006-01-02T15:04:05.000Z")
msg := ts + "\n" + method + "\n" + pathWithQuery + "\n" + body
secret, _ := base64.StdEncoding.DecodeString(os.Getenv("LMTS_TOKEN_SECRET"))
mac := hmac.New(sha256.New, secret)
mac.Write([]byte(msg))
sig := base64.StdEncoding.EncodeToString(mac.Sum(nil))
return map[string]string{
"lmts-api-key": os.Getenv("LMTS_TOKEN_ID"),
"lmts-timestamp": ts,
"lmts-signature": sig,
}
}
func main() {
ctx := context.Background()
client := limitless.NewHttpClient()
portfolio := limitless.NewPortfolioFetcher(client)
// Fills via raw HTTP (no SDK method for GET /portfolio/trades).
req, _ := http.NewRequest("GET", "https://api.limitless.exchange/portfolio/trades", nil)
for k, v := range signedHeaders("GET", "/portfolio/trades", "") {
req.Header.Set(k, v)
}
res, err := http.DefaultClient.Do(req)
if err != nil { log.Fatal(err) }
defer res.Body.Close()
var fills []Trade
if err := json.NewDecoder(res.Body).Decode(&fills); err != nil { log.Fatal(err) }
positions, err := portfolio.GetPositions(ctx)
if err != nil { log.Fatal(err) }
// Realised: net collateral flow across all fills.
realised := 0.0
for _, f := range fills {
flow, _ := strconv.ParseFloat(f.CollateralAmount, 64)
flow /= 1e6 // USDC 6 decimals
if f.Strategy == "Sell" {
realised += flow
} else {
realised -= flow
}
}
// Unrealised: (marketValue - cost) per CLOB position.
unrealised := 0.0
for _, p := range positions.Clob {
yesCost, _ := strconv.ParseFloat(p.Positions.Yes.Cost, 64)
yesValue, _ := strconv.ParseFloat(p.Positions.Yes.MarketValue, 64)
if yesCost == 0 { continue }
unrealised += (yesValue - yesCost) / 1e6
}
fmt.Printf("realised : %.2f\n", realised)
fmt.Printf("unrealised: %.2f\n", unrealised)
fmt.Printf("total : %.2f\n", realised+unrealised)
}
How to run this
- Keep LIMITLESS_API_KEY (the SDK call) and LMTS_TOKEN_ID + LMTS_TOKEN_SECRET (the raw signed request) set. You’ll need at least one real fill on your account to see a non-zero realised number; otherwise both figures round to zero.
- Save the snippet above as compute-pnl.ts, then run npx tsx compute-pnl.ts.
- Save the snippet above as compute_pnl.py, then run python compute_pnl.py.
- Save the snippet above as main.go inside a Go module, then run go run main.go.
- The script prints three lines, realised, unrealised, and total, each in USDC, rounded to two decimal places.
Section 04
Reconciling with on-chain state.
The chain is the source of truth
The REST API is a fast, cached view of your account. The chain is what actually holds your tokens. When the two disagree, which happens during reorgs, backfill lag, or provider hiccups, the chain wins.
- Reconcile tokensBalance on each CLOB position against the balanceOf call on the outcome token contract
- Cross-check USDC balance against the Negrisk / CTF adapter on Base
- Run the reconciliation on a schedule (every 5 minutes) and alert on mismatch
- Treat any mismatch > dust as a hard stop, kill new orders, page a human
Mental model: the REST positions endpoint is your P&L and UX layer. balanceOf on-chain is your risk layer. Never let the two drift without noticing.
Wire positions into your control panel.
The seed positions.json you dropped in Module 02 was hand-written. As soon as your code from this module pulls real positions, write the array to $ACADEMY_DATA_DIR/positions.json on every refresh. The positions table in your panel re-renders against the new file on the next poll, one source of truth, two surfaces (panel for the laptop, raw JSON for any other consumer you wire later).
Positions and PnL: what people ask
Each answer also ships invisibly as schema.org FAQ data for search engines and AI assistants. Tap a question to expand.
-
What is the difference between /portfolio/trades and /portfolio/history?
GET /portfolio/tradesreturns a flat array in one call, no pagination, but AMM trades only; a CLOB-heavy bot reconciling realised PnL from it silently misses every limit-order fill and under-reports its book.GET /portfolio/historyis the cursor-paginated endpoint (cursor+limit) covering full activity: CLOB fills, splits, merges, and claims; loop whilenextCursoris non-null. Cache locally in NDJSON keyed bytxHashso reruns only fetch new rows. -
Why does my bot show $0.00 on AMM position rows?
Decimals. USDC has 6 decimals; outcome tokens have 18. DividingcostandmarketValueby1e6is correct for USDC, but the same fields on AMM positions hold outcome-token amounts, so reusing the divisor shows $0.00 next to right-looking CLOB numbers, no error, no warning. Build afromUnits(amount, decimals)helper (6 for USDC, 18 for outcome tokens) and never inline/ 1e6. -
Why don’t positions update right after placing an order?
The REST/portfolio/positionsresponse is assembled by an indexer, not by querying the chain on every call, and indexers lag fills by usually 1–5 seconds, occasionally longer during reorgs. A bot that places an order and reads positions on the next line computes PnL against pre-fill state, double-places, or trips a risk check on phantom exposure. Treat the order ack as truth: update local position state synchronously, and never usegetPositions()as a read-after-write primitive. -
What headers do authenticated Limitless portfolio calls need?
Three HMAC headers:lmts-api-key,lmts-timestamp, andlmts-signature, a base64 HMAC-SHA256 over the timestamp, method, path, and body, computed from your token secret (LMTS_TOKEN_SECRET). The legacyX-API-Keyheader is deprecated. No SDK method wraps/portfolio/tradesyet, so the module signs raw HTTP requests with exactly these headers;getPositions()handles the signing for you. -
How do you compute realised and unrealised PnL from the API?
Realised: sum of (exit price − entry price) × closed size, minus fees, entirely from the fills stream, so it’s deterministic and replayable. Unrealised: open size × (current mark − average entry), which needs a live mark from the order book or mid, so quote it alongside a timestamp. The module’s working version netscollateralAmountflows across fills for realised and takesmarketValueminuscostper CLOB position for unrealised.
Section 05
Module checklist.
Tick each item once you’ve actually done it. The Continue button unlocks at 5/5.
I fetched my portfolio with PortfolioFetcher.getPositions() and read CLOB + AMM rows
I pulled my AMM fills via /portfolio/trades and filtered to a date range client-side
I can explain the difference between realised and unrealised PnL
I computed total PnL from the fills stream plus open positions with live marks
I know that the on-chain state is the source of truth and have a reconciliation plan
Foundations tier wrap-up
Take it live.
You’ve got the four foundations: auth, markets, orders, and positions. The next thing your code can teach you is what real fills feel like. Open a small position with what you’ve built, the rest of the curriculum makes more sense once your bot has touched the live book.
Start trading on LimitlessTier 1 complete · Foundations
Foundations complete.
Your bot can now answer the only questions a trader actually cares about: what do I hold right now, what is it worth, and is the exchange and the chain telling me the same story? With those answered, you have the floor a real strategy can stand on.
Concretely, you can read markets, place orders, and track your portfolio from code. Three things you walk away with at the end of the Foundations tier:
A single call that returns your entire live portfolio, CLOB + AMM positions, accumulated points, and reward balance, ready to feed into a dashboard or a risk check.
A fills stream from /portfolio/trades and the realised-PnL formula that turns it into a hard number, deterministic, replayable, audit-ready.
The reconciliation mental model every production bot needs, REST is your UX layer, on-chain balanceOf is your risk layer, and the two must agree.
Without scrolling back, can you answer these?
Five questions across the API basics tier. Click each to reveal, the test is whether you can answer first.
-
What’s the difference between authenticating a GET request and signing a transaction with your wallet?
An authenticated GET proves your identity to the server, the API key says “this request came from you.” A wallet-signed transaction proves your intent to the chain, your private key signs a payload that the contract can verify on-chain. Reads need only the API key. Orders need both: the key gets the request through; the signature lets the chain accept the trade. -
Two markets quote the same mid-price. Why might one fill you 5% worse than the other?
Order book depth. Mid-price is just the average of best bid and best ask, it says nothing about how much liquidity sits at each level. A market with$50at the top of book and a thin spread will eat into the next level on any size. Walk the book one or two levels past your size before claiming the mid is your fill. -
You place a limit-buy at
$0.55for 100 shares. The mid is$0.54. Will you fill at$0.55?Probably not. A limit-buy at$0.55says “buy at$0.55or better.” If sellers sit at$0.54, you fill there first. If the book lifts to$0.56before your order rests, you don’t fill at all, the limit caps the price you’ll accept, not the price you’ll pay. Effective fill price is whatever the resting book shows, capped by your limit. -
Realised vs unrealised PnL, which one moves with the order book, and why does that matter for risk?
Unrealised. Realised PnL is closed cash, once a fill clears, that number is locked. Unrealised is open size × (mark − avg entry) and the mark moves tick by tick. For risk, that means realised is your track record (deterministic, replayable from fills) while unrealised is your live exposure (must be re-quoted every refresh, always with a timestamp). Mixing them is how dashboards lie. -
Why is the chain the source of truth, not the REST API, and what should your bot do when they disagree?
REST is a fast, indexed view. The chain is what actually holds your tokens. During reorgs, indexer lag, or provider hiccups, the two diverge. When that happens,balanceOfon-chain wins. Reconcile every few minutes; treat any non-dust mismatch as a hard stop, kill new orders, page a human, do not assume it’ll resolve itself. The size of the divergence is the size of your potential loss.
Next up: real-time. Websockets, order-book streams, rate limits, and the error-handling patterns that keep a long-running bot alive when the network hiccups.
Complete the checklist above to unlock