Markets
SportsPerp launches with 68 markets — 20 EPL teams and 48 rostered players. Every market is a perpetual future priced against the composite OBV Index, collateralized in USDC, and settled on Solana.
Market types
| Type | ID range | Count | Notes |
|---|---|---|---|
| Team | 0–19 | 20 | Contiguous — one market per EPL club, alphabetically assigned |
| Player | 20–99 | 48 | Sparse — IDs are permanent even as players transfer, get injured, or drop out of roster |
The market_type field on MarketConfig is 0 for teams, 1 for players. Market IDs are stored as u16 and used as PDA seeds.
Why stable, sparse IDs
A traditional listing system would re-index markets whenever the player roster changes — new signings get the next available slot, dropped players free their ID, existing markets shift. That’s a disaster for a perp protocol:
- Open positions reference market IDs. Reassigning an ID would either silently reassign positions (destructive) or force-close them (user-hostile).
- Historical candles are keyed by market ID. A reassignment severs the price history.
SportsPerp instead uses stable, sparse IDs in the 20–99 range for players. A player’s ID is never reused, and the market stays open — unaffected — through injuries, absences, and rotations; the market is priced and tradable the entire time, so open positions carry through those events with their price history intact.
A market is only put on the path to sunset when the player actually leaves the roster (roster.json regeneration drops them, e.g. a transfer out of the EPL) — the oracle stops receiving pushes, and once staleness passes 72 hours the market is permissionlessly sunset (see Market Lifecycle). Sunset is a one-way door: it settles and closes every remaining open position at the frozen oracle price. Positions open at the moment of sunset are not preserved through it — they are paid out at that price, not carried forward.
The roster as single source of truth
roster.json at the repo root defines every market: ID, name, team affiliation, canonical source ID, position group, and metadata. The file is:
- File-watched. The engine hot-reloads when roster.json changes — no service restart required.
- Consumed by every service. Crank, oracle pusher, keeper, liquidator, frontend, and init scripts all read from the same file. No drift.
- Versioned. Currently v4 (every player entry carries an
sb_player_idfor stable identity across roster rotations). Changes are backed up automatically before being applied.
Roster updates are generated by a script that pulls the current EPL player pool from the data partner’s REST feed, applies the selection algorithm described in the next section, and fuzzy-matches against the existing roster to preserve market IDs.
Player selection algorithm
To keep player markets tradable, the roster algorithm enforces quality and diversity constraints:
- Fetch all EPL players (~535) from the data partner’s REST feed.
- Filter by minimum playing time: minutes ≥ 900 (~319 eligible).
- Rank each player by their composite z-score within position group (OBV 55% + Form 30% + Minutes 15%).
- Select the top 48 subject to:
- Position floors: ≥ 8 FW, ≥ 15 MF, ≥ 10 DF, ≥ 3 GK
- Team cap: ≤ 5 players per club (prevents stacking on the best teams)
- Fuzzy-match against existing roster names to preserve stable IDs.
- Back up the previous roster before writing.
The cap on players-per-team is deliberate: a trader wanting leveraged exposure to the top club gets the team market (ID 0–19); player markets are for speculating on individual performance, not dodging the team-market liquidity.
Market volatility characteristics
Teams and players have different risk profiles:
| Dimension | Team markets | Player markets |
|---|---|---|
| Signal window | Whole club across 38 matches | Individual across 24–38 appearances |
| Volatility drivers | Results, manager changes, European fixtures | Injuries, transfers, rotations, suspensions |
| Default max leverage | 5x | 5x (same base; confidence scaling narrows in practice) |
Production oracle confidence is uniform across every market by liveness, not by market type: 300 bps between matches (1.0× leverage) and 600 bps during a live match (0.6× on new opens only — closes are never confidence-gated). There is no player-vs-team confidence differential today. The design direction is a per-market dynamic confidence override: automated, source-attributed availability signals (e.g. a confirmed in-match exit, a pre-kickoff lineup absence) can widen a specific player’s market confidence within a bounded [300, 1000] bps range, on a mandatory TTL, throttling new-open leverage without touching existing positions or their exits. This channel is not yet built — today, a transfer rumor, a minor injury, or a rotation decision has no effect on confidence or leverage; the index itself is what moves (slowly, and only at the bounded Minutes weight — see Composite Index Design).
Current state
- Program ID:
6d4fSCD7mNy7aDNS2mXUxYpZjFFQKBKwAsM5kojKQA6h(devnet) - Markets deployed: 20 teams + 48 players = 68 active
- Next update: weekly roster regeneration as the EPL season progresses
Further reading
- Contract Specifications — the full per-market spec: fees, leverage, funding interval.
- Market Lifecycle — initialization, pausing, sunsetting, settlement.
- Composite Index Design — how market prices are derived.