title: Seesaw Trading Skill description: Autonomous trading guide for Seesaw binary prediction markets on Solana. Covers market lifecycle, order placement, price feeds, and trading strategies. skill_name: seesaw-trading skill_version: '1.1.0'
Seesaw Trading Skill#
This skill provides comprehensive guidance for autonomous trading on Seesaw, a permissionless binary prediction market protocol on Solana.
Protocol Overview#
Seesaw enables trading on configurable-duration binary outcomes: will an asset's price end UP or DOWN relative to its starting price? Users trade YES/NO shares through an on-chain order book with Pyth oracle integration.
Key Characteristics#
- Configurable durations: Markets run from 60 seconds to 7 days (default: 15 minutes)
- Binary outcomes: UP (price increased or equal) or DOWN (price decreased)
- Permissionless: Anyone can trade, create markets, or run cranks
- Pyth Oracle: Exclusive use of Pyth Network for price data
- Basis points pricing: All prices in bps [0, 10000] where 10000 = 100%
Market Lifecycle#
PENDING → CREATED → TRADING → SETTLING → RESOLVED → CLOSED
Lifecycle Stages#
| Stage | Description | User Actions |
|---|---|---|
| PENDING | Market not yet created | Can call create_market |
| CREATED | Market exists, awaiting start | Wait for t_start |
| TRADING | Active trading window | place_order, cancel_order |
| SETTLING | Trading ended, awaiting resolution | Wait for snapshots |
| RESOLVED | Outcome determined (UP/DOWN) | Call redeem |
| CLOSED | All positions settled | Market complete |
Time Calculations#
// docs-check: semantic
// Calculate market boundaries from any timestamp
const EPOCH_DURATION = 900; // default 15 minutes; configurable per market (60–604800)
function getMarketId(timestamp: number): bigint {
return BigInt(Math.floor(timestamp / EPOCH_DURATION));
}
function getMarketTimes(marketId: bigint) {
const tStart = Number(marketId) * EPOCH_DURATION;
const tEnd = tStart + EPOCH_DURATION;
return { tStart, tEnd };
}
// Example: Get current market
const now = Math.floor(Date.now() / 1000);
const currentMarketId = getMarketId(now);
const { tStart, tEnd } = getMarketTimes(currentMarketId);
Fetching Price Data#
Pyth Network Integration#
Seesaw uses Pyth Network exclusively for price feeds. Use the Hermes SSE streaming API for real-time prices.
Known Feed IDs#
// docs-check: semantic
const PYTH_FEED_IDS = {
'BTC/USD': '0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43',
'ETH/USD': '0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace',
'SOL/USD': '0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d',
'AVAX/USD': '0x93da3352f9f1d105fdfe4971cfa80e9dd777bfc5d0f683ebb6e1294b92137bb7',
'LINK/USD': '0x8ac0c70fff57e9aefdf5edf44b51d62c2d433653cbb2cf5cc06bb115af04d221',
'ARB/USD': '0x3fa4252848f9f0a1480be62745a4629d9eb1322aebab8a791e344b3b9c1adcf5',
'OP/USD': '0x385f64d993f7b77d8182ed5003d97c60aa3361f3cecfe711544d2d59165e9bdf',
};
Streaming Real-Time Prices#
// docs-check: semantic
const STREAM_FEED_IDS = {
'BTC/USD': '0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43',
} as const;
interface PythStreamPriceUpdate {
price: {
price: string;
expo: number;
publish_time: number;
};
}
interface PythStreamMessage {
parsed?: PythStreamPriceUpdate[];
}
function buildPythStreamProxyUrl(feedId: string): string {
const url = new URL('/api/pyth/stream', 'https://api.seesaw.markets');
url.searchParams.set('ids[]', feedId);
return url.toString();
}
// Browser/EventSource paths should use a configured app proxy that injects
// PYTH_API_KEY server-side. Do not hard-code indexer REST aliases here.
const feedId = STREAM_FEED_IDS['BTC/USD'].replace(/^0x/, '');
const url = buildPythStreamProxyUrl(feedId);
const eventSource = new EventSource(url);
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data) as PythStreamMessage;
if (data.parsed && Array.isArray(data.parsed)) {
for (const update of data.parsed) {
const price = parseFloat(update.price.price);
const expo = update.price.expo;
const actualPrice = price * Math.pow(10, expo);
const publishTime = update.price.publish_time;
console.log(`Price: $${actualPrice.toFixed(2)} at ${publishTime}`);
}
}
};
One-Time Price Fetch#
// docs-check: semantic
interface HermesPriceUpdate {
price: {
price: string;
expo: number;
};
}
interface HermesLatestPriceResponse {
parsed?: HermesPriceUpdate[];
}
async function fetchCurrentPrice(feedId: string): Promise<number> {
const id = feedId.replace(/^0x/, '');
const url = `https://pyth.dourolabs.app/hermes/v2/updates/price/latest?ids[]=${id}&parsed=true`;
const response = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.PYTH_API_KEY}` },
});
const data = (await response.json()) as HermesLatestPriceResponse;
if (data.parsed && data.parsed.length > 0) {
const update = data.parsed[0];
const price = parseFloat(update.price.price);
const expo = update.price.expo;
return price * Math.pow(10, expo);
}
throw new Error('No price data available');
}
Order Book Mechanics#
Understanding YES/NO Share Pricing#
Seesaw uses a single canonical order book for YES shares. NO orders are automatically converted:
| User Intent | User Price | Canonical Side | Canonical Price |
|---|---|---|---|
| Buy YES @ 6000 | 6000 bps | BID | 6000 |
| Sell YES @ 6000 | 6000 bps | ASK | 6000 |
| Buy NO @ 4000 | 4000 bps | ASK | 6000 (10000 - 4000) |
| Sell NO @ 4000 | 4000 bps | BID | 6000 (10000 - 4000) |
Key insight: Buying NO at 40% is equivalent to selling YES at 60%.
Price Interpretation#
Price in bps → Implied probability
6000 bps = 60% probability of UP
4000 bps = 40% probability of UP (or 60% probability of DOWN)
Collateral calculation:
collateral = (price_bps * quantity) / 10000
Example: Buy 100 YES shares at 6000 bps
collateral = (6000 * 100) / 10000 = 60 USDT
Tick Rounding#
Orders are rounded to tick boundaries (default: 100 bps):
- Bids: Round DOWN (buyer gets better price)
- Asks: Round UP (seller gets better price)
// docs-check: semantic
function roundToTick(price: number, tickSize: number, side: 'bid' | 'ask'): number {
if (side === 'bid') {
return Math.floor(price / tickSize) * tickSize;
} else {
const remainder = price % tickSize;
return remainder > 0 ? price + (tickSize - remainder) : price;
}
}
// Examples:
roundToTick(6050, 100, 'bid'); // → 6000
roundToTick(6050, 100, 'ask'); // → 6100
Trading Instructions#
1. Place Order#
Place a limit order on the order book.
// docs-check: semantic
import type { Address } from '@solana/addresses';
import { OrderSide, OrderType } from '@seesaw/core';
interface PlaceOrderParams {
market: Address; // Market account PDA
side: OrderSide; // 0=BuyYes, 1=SellYes, 2=BuyNo, 3=SellNo
priceBps: number; // Price in basis points [1, 9999]
quantity: bigint; // Number of shares
orderType: OrderType; // 0=Limit, 1=PostOnly, 2=ImmediateOrCancel
}
Required Accounts (18 total, plus an optional 3-account referral tail):
market- Market PDAorderbook- Orderbook PDAuser_position- User's position PDA (created if needed)user_token_account- User's USDT accountvault- Market vault PDAuser- User's wallet (signer)config- Protocol config PDAtreasury_token_account- Must equalconfig.treasury_recipients[protocol_treasury_index]- Plus: token/system programs, settlement mint, YES/NO escrows, user YES/NO ATAs, YES/NO mints, trader ledger
Use the SDK builders (buildPlaceOrderIx in @seesaw/core, place_order in
the Python/Rust SDKs) rather than assembling the metas by hand — or the
trustless resolver, which derives and validates the full
account set from chain state.
2. Cancel Order#
Remove an open order from the book.
// docs-check: semantic
import type { Address } from '@solana/addresses';
interface CancelOrderParams {
market: Address;
orderId: bigint; // The order's unique u64 id (from placement or the on-chain book)
}
3. Redeem#
Claim winnings after market resolution by burning winning tokens.
// docs-check: semantic
import type { Address } from '@solana/addresses';
interface RedeemParams {
market: Address;
amount: bigint; // Amount of tokens to redeem
tokenType: 'yes' | 'no'; // Which token to redeem (0=Yes, 1=No)
}
// Payout calculation:
// if outcome == UP: burn YES tokens → receive USDT 1:1
// if outcome == DOWN: burn NO tokens → receive USDT 1:1
// if outcome == EXPIRED: burn either → receive USDT at 50%
Additional Instructions#
4. Mint Shares (0x07)#
Mint YES/NO share pairs by depositing USDT collateral.
5. Withdraw Shares (0x0A)#
Withdraw trader-ledger position shares as SPL tokens.
6. Snapshot End (0x04)#
Capture the end price snapshot after trading ends. Permissionless.
7. Resolve Market (0x05)#
Determine market outcome based on price snapshots. Permissionless.
8. Expire Market (0x06)#
Expire a market that hasn't been resolved within the expiration window.
9. Close Market (0x1E)#
Close a resolved/expired market and reclaim rent.
10. Force Close (0x1B)#
Force-close a position in a resolved/expired market.
11. Create Market (0x03)#
Create a new prediction market for a given asset and duration.
12. Initialize Config (0x00)#
One-time protocol configuration setup (taker_fee_bps, tick_size_bps, plus the 8 protocol treasury recipient accounts and the default settlement mint). The capped-linear-decay fee curve (fee_cap_bps, decay_rate_bps) and three-way split (protocol_fee_bps, default_creator_fee_bps, referral_share_bps_of_fee) initialize to protocol defaults and are retuned via UpdateFeeConfig (0x1F).
13. Claim Creator Fees (0x23)#
Permissionless sweep of deferred creator fees from the market vault to the market creator's token account.
14. Update Fee Config (0x1F)#
Admin-only. Retunes the fee curve parameters and the three-way split. Preserves the invariant protocol + creator + referral == 10_000.
15. Set Referrer (0x21)#
Permissionless. Writes a ReferralAccount PDA attributing a referrer to the user. First-touch and immutable after creation; expiry only stops future referral accrual and does not allow picking a new referrer. While active, every taker fee the user pays routes 40% to that referrer when the referral triple is attached.
16. Init Referrer Earnings Account (0x22)#
Permissionless. Creates the per-referrer ReferrerEarningsAccount PDA (once, lazily).
17. Claim Referrer Earnings (0x24)#
Permissionless. Transfers the referrer's accumulated earnings from their bound referrer_treasury shard (one of 8 sharded PDAs) to their token account. Funds always go to the referrer regardless of caller.
API Endpoints#
The Seesaw indexer provides REST endpoints for market data. Use the
versioned /api/v1/ prefix.
For an indexer-free alternative — every market, order book, position, and
balance read straight from any Solana RPC node — see the
Trustless SDK.
List Markets#
GET /api/v1/markets?state={state}&limit={limit}&offset={offset}
Response:
{
"markets": [
{
"address": "...",
"marketId": "1234567",
"pythFeed": "...",
"state": 1,
"outcome": null,
"tStart": "2024-01-15T10:00:00Z",
"tEnd": "2024-01-15T10:15:00Z",
"startPrice": "68543210000",
"totalVolume": "1000000",
"totalTrades": 42
}
],
"total": 100,
"limit": 50,
"offset": 0
}
Get Current Market#
GET /api/v1/markets/current
Response:
{
"market": {
"id": "BTC-1H-2026-07-04T15:00Z",
"question": "Will BTC close above $110,000?"
},
"status": "trading"
}
Get Market Details#
GET /api/v1/markets/{marketId}
Response includes orderbook:
{
"market": {
"id": "BTC-1H-2026-07-04T15:00Z",
"question": "Will BTC close above $110,000?"
},
"orderbook": {
"bids": [{ "price": 6000, "quantity": "1000", "orders": 3 }],
"asks": [{ "price": 6100, "quantity": "500", "orders": 2 }]
}
}
Trading Strategy Framework#
Decision Factors#
When deciding whether to trade, consider:
- Price Position: Is current price above or below start price?
- Time Remaining: How much time until market ends?
- Momentum: Is price trending in a direction?
- Market Prices: What probabilities does the market imply?
- Confidence: How confident is the oracle price?
Computing Market Position#
// docs-check: semantic
interface MarketPosition {
delta: number; // currentPrice - startPrice
deltaPct: number; // Percentage change
status: 'above' | 'below' | 'at';
distanceToFlip: number;
isAbove: boolean;
isBelow: boolean;
}
function computeMarketPosition(startPrice: number, currentPrice: number): MarketPosition {
const delta = currentPrice - startPrice;
const deltaPct = delta / startPrice;
const status = deltaPct > 0.0001 ? 'above' : deltaPct < -0.0001 ? 'below' : 'at';
return {
delta,
deltaPct,
status,
distanceToFlip: Math.abs(delta),
isAbove: status === 'above',
isBelow: status === 'below',
};
}
const samplePosition = computeMarketPosition(100, 101);
console.log(samplePosition.status, samplePosition.distanceToFlip);
Implied Probability Heuristic#
// docs-check: semantic
interface MarketPosition {
delta: number;
deltaPct: number;
status: 'above' | 'below' | 'at';
distanceToFlip: number;
isAbove: boolean;
isBelow: boolean;
}
function computeImpliedProbability(
position: MarketPosition,
volatilityFactor: number = 0.02
): number {
// Logistic function centered at start price
const k = 1 / volatilityFactor;
const probability = 1 / (1 + Math.exp(-k * position.deltaPct));
return Math.max(0.01, Math.min(0.99, probability));
}
const probability = computeImpliedProbability({
delta: 1,
deltaPct: 0.01,
status: 'above',
distanceToFlip: 1,
isAbove: true,
isBelow: false,
});
console.log(probability);
Trading Decision Logic#
// docs-check: semantic
interface MarketPosition {
delta: number;
deltaPct: number;
status: 'above' | 'below' | 'at';
distanceToFlip: number;
isAbove: boolean;
isBelow: boolean;
}
interface TradingSignal {
action: 'buy_yes' | 'buy_no' | 'hold';
confidence: number;
reason: string;
suggestedPrice: number;
suggestedQuantity: number;
}
function computeMarketPosition(startPrice: number, currentPrice: number): MarketPosition {
const delta = currentPrice - startPrice;
const deltaPct = delta / startPrice;
const status = deltaPct > 0.0001 ? 'above' : deltaPct < -0.0001 ? 'below' : 'at';
return {
delta,
deltaPct,
status,
distanceToFlip: Math.abs(delta),
isAbove: status === 'above',
isBelow: status === 'below',
};
}
function computeImpliedProbability(
position: MarketPosition,
volatilityFactor: number = 0.02
): number {
const k = 1 / volatilityFactor;
const probability = 1 / (1 + Math.exp(-k * position.deltaPct));
return Math.max(0.01, Math.min(0.99, probability));
}
function analyzeTradeOpportunity(
startPrice: number,
currentPrice: number,
marketBestBid: number, // Best YES bid in bps
marketBestAsk: number, // Best YES ask in bps
timeRemainingSeconds: number,
volatilityEstimate: number = 0.02
): TradingSignal {
const position = computeMarketPosition(startPrice, currentPrice);
const impliedProb = computeImpliedProbability(position, volatilityEstimate);
// Convert to basis points
const impliedPriceBps = Math.round(impliedProb * 10000);
// Market mid price
const marketMid = (marketBestBid + marketBestAsk) / 2;
// Look for mispricing
const priceDiff = impliedPriceBps - marketMid;
const threshold = 200; // 2% edge threshold
// Time decay factor - less aggressive near end
const timeFactor = Math.max(0.3, timeRemainingSeconds / 900);
if (priceDiff > threshold * timeFactor) {
// Market underpricing YES (or overpricing NO)
// Buy YES below our fair value
return {
action: 'buy_yes',
confidence: Math.min(1, Math.abs(priceDiff) / 500),
reason: `YES underpriced by ${priceDiff} bps`,
suggestedPrice: Math.min(impliedPriceBps - 50, marketBestAsk),
suggestedQuantity: calculatePositionSize(priceDiff, timeFactor),
};
} else if (priceDiff < -threshold * timeFactor) {
// Market overpricing YES (or underpricing NO)
// Buy NO
return {
action: 'buy_no',
confidence: Math.min(1, Math.abs(priceDiff) / 500),
reason: `NO underpriced by ${-priceDiff} bps`,
suggestedPrice: 10000 - Math.max(impliedPriceBps + 50, marketBestBid),
suggestedQuantity: calculatePositionSize(-priceDiff, timeFactor),
};
}
return {
action: 'hold',
confidence: 0,
reason: 'No significant mispricing detected',
suggestedPrice: 0,
suggestedQuantity: 0,
};
}
function calculatePositionSize(edgeBps: number, timeFactor: number): number {
// Kelly-inspired sizing: bet more when edge is larger
// Scale down near market end (timeFactor)
const baseSize = 100; // Base position in shares
const edgeFactor = Math.min(3, Math.abs(edgeBps) / 100);
return Math.floor(baseSize * edgeFactor * timeFactor);
}
const signal = analyzeTradeOpportunity(100, 101, 5_800, 6_100, 600);
console.log(signal.action, signal.suggestedQuantity);
Fee Structure#
Seesaw uses a capped-linear-decay taker fee curve. Makers pay 0. The taker fee is split three ways on every fill.
fee_bps(p) = min(fee_cap_bps, decay_rate_bps × (10_000 − p) / 10_000)
Defaults: fee_cap_bps = 200 (2.00% cap), decay_rate_bps = 600 (6.00% slope).
| Fill price | Effective taker fee |
|---|---|
| 0.00 - 0.67 | 2.00% (capped) |
| 0.80 | 1.20% |
| 0.90 | 0.60% |
| 0.95 | 0.30% |
| 0.99 | 0.06% |
Three-Way Split#
Every taker fee is split three ways (bps of fee, summing to 10_000):
- Protocol treasury: 50% (5_000 bps of fee)
- Market creator: 10% (1_000 bps of fee)
- Referral pool: 40% (4_000 bps of fee) — routed to the taker's referrer if
set_referrerwas called, else to the protocol treasury
// docs-check: semantic
import { DEFAULT_FEE_SPLIT, computeAndSplit } from '@seesaw/core';
function calculateFees(
notional: bigint,
priceBps: number,
isTaker: boolean,
hasReferrer: boolean,
feeCapBps: number = 200,
decayRateBps: number = 600
): {
total: bigint;
protocolFee: bigint;
creatorFee: bigint;
referralFee: bigint;
} {
if (!isTaker) {
return { total: 0n, protocolFee: 0n, creatorFee: 0n, referralFee: 0n };
}
// Matches on-chain rounding: total fee rounds up, creator/referral shares
// round down, and all dust stays with protocol.
const split = computeAndSplit(notional, priceBps, feeCapBps, decayRateBps, DEFAULT_FEE_SPLIT);
// If taker has no referrer, the 40% referral share flows to protocol treasury
const protocolFee = hasReferrer ? split.protocol : split.protocol + split.referral;
const referralFee = hasReferrer ? split.referral : 0n;
return {
total: split.total,
protocolFee,
creatorFee: split.creator,
referralFee,
};
}
Resolution Rules#
Sampling Rule A#
P_start= First Pyth price wherepublish_time >= t_startP_end= First Pyth price wherepublish_time >= t_end
Outcome Determination#
Important: Equality results in UP outcome.
Payout Calculation#
PDA Derivation#
// docs-check: semantic
import { address } from '@solana/addresses';
import {
deriveMarketPda,
deriveOrderbookPda,
deriveVaultPda,
derivePositionPda,
deriveConfigPda,
deriveAssetPda,
deriveYesMintPda,
deriveNoMintPda,
} from '@seesaw/core';
const pythFeedId = new Uint8Array(32);
const durationSeconds = 900n;
const marketId = 1n;
const creatorAddress = address('11111111111111111111111111111111');
const userAddress = address('SysvarC1ock11111111111111111111111111111111');
// Market PDA uses 6 seeds:
// ["seesaw", "market", pyth_feed_id(32), duration_seconds(8 LE), market_id(8 LE), creator(32)]
const [marketPda] = await deriveMarketPda(pythFeedId, durationSeconds, marketId, creatorAddress);
const [orderbookPda] = await deriveOrderbookPda(marketPda);
const [vaultPda] = await deriveVaultPda(marketPda);
const [positionPda] = await derivePositionPda(marketPda, userAddress);
// Additional PDA derivations
const [configPda] = await deriveConfigPda();
const [assetPda] = await deriveAssetPda(pythFeedId);
const [yesMintPda] = await deriveYesMintPda(marketPda);
const [noMintPda] = await deriveNoMintPda(marketPda);
console.log({ orderbookPda, vaultPda, positionPda, configPda, assetPda, yesMintPda, noMintPda });
Risk Management#
Position Limits#
- Maximum order size:
max_order_size(default 1,000,000,000,000 base units = 1,000,000 USDT; admin-tunable viaUpdateOperationalParams0x2F) - No per-user order limit: an earlier
max_orders_per_userfield was retired and is no longer enforced - Maximum orderbook depth: selected deep orderbook tier, 64-4096 bids plus 64-4096 asks
Key Invariants (Never Violated)#
- Solvency:
vault >= max(total_yes_shares, total_no_shares) + accumulated_creator_fees - Conservation: Every trade conserves value (collateral in = shares out)
- No Negative Exposure: All share balances >= 0
- No Crossed Book:
best_bid < best_askwhen both exist
Error Handling#
Common errors to handle:
Errors are a custom #[repr(u32)] SeesawError enum (category-prefixed hex codes, NOT
Anchor 6000+). Representative codes:
| Error Code | Name | Meaning |
|---|---|---|
| 0x1002 | InvalidState | Market not in the expected state |
| 0x1005 | AlreadyResolved | Market already resolved |
| 0x2002 | StaleOracle | Oracle price too old |
| 0x3001 | InvalidQuantity | Order quantity invalid |
| 0x3004 | WouldCross | PostOnly order would cross book |
| 0x3007 | SlippageExceeded | Fill price outside tolerance |
| 0x4001 | MathOverflow | Arithmetic overflow |
| 0x6001 | ProtocolPaused | Protocol is paused |
Complete Trading Flow Example#
// docs-check: semantic
interface TradingFlowMarket {
address: string;
pythFeed: string;
startPrice: string;
startPriceExpo: number;
tEnd: string;
}
interface TradingFlowOrderbook {
bids: Array<{ price: number }>;
asks: Array<{ price: number }>;
}
interface TradingSignal {
action: 'buy_yes' | 'buy_no' | 'hold';
confidence: number;
reason: string;
suggestedPrice: number;
suggestedQuantity: number;
}
interface PythLatestPriceResponse {
parsed: Array<{
price: {
price: string;
expo: number;
};
}>;
}
function analyzeTradeOpportunity(
startPrice: number,
currentPrice: number,
bestBid: number,
bestAsk: number,
timeRemainingSeconds: number
): TradingSignal {
const midpoint = Math.floor((bestBid + bestAsk) / 2);
if (timeRemainingSeconds < 60) {
return {
action: 'hold',
confidence: 0.2,
reason: 'market is near expiry',
suggestedPrice: midpoint,
suggestedQuantity: 0,
};
}
return {
action: currentPrice >= startPrice ? 'buy_yes' : 'buy_no',
confidence: 0.6,
reason:
currentPrice >= startPrice ? 'current price is above start' : 'current price is below start',
suggestedPrice: midpoint,
suggestedQuantity: 1_000_000,
};
}
async function executeTradingStrategy() {
// 1. Get current market
const marketResponse = await fetch('/api/v1/markets/current');
const { market, status } = (await marketResponse.json()) as {
market: TradingFlowMarket;
status: string;
};
if (status !== 'trading') {
console.log('No active market, waiting...');
return;
}
// 2. Stream live price
const feedId = market.pythFeed.replace(/^0x/, '');
const priceUrl = `https://pyth.dourolabs.app/hermes/v2/updates/price/latest?ids[]=${feedId}&parsed=true`;
const priceResponse = await fetch(priceUrl, {
headers: { Authorization: `Bearer ${process.env.PYTH_API_KEY}` },
});
const priceData = (await priceResponse.json()) as PythLatestPriceResponse;
const currentPrice =
parseFloat(priceData.parsed[0].price.price) * Math.pow(10, priceData.parsed[0].price.expo);
// 3. Parse market data
const startPrice = Number(market.startPrice) * Math.pow(10, market.startPriceExpo);
const tEnd = new Date(market.tEnd).getTime() / 1000;
const now = Date.now() / 1000;
const timeRemaining = tEnd - now;
// 4. Get orderbook
const obResponse = await fetch(`/api/v1/markets/${market.address}`);
const { orderbook } = (await obResponse.json()) as { orderbook: TradingFlowOrderbook };
const bestBid = orderbook.bids[0]?.price || 0;
const bestAsk = orderbook.asks[0]?.price || 10000;
// 5. Analyze opportunity
const signal = analyzeTradeOpportunity(startPrice, currentPrice, bestBid, bestAsk, timeRemaining);
console.log('Trading signal:', signal);
// 6. Execute if confident
if (signal.action !== 'hold' && signal.confidence > 0.5) {
// Build and send transaction...
console.log(`Executing: ${signal.action} at ${signal.suggestedPrice} bps`);
}
}
// Run every 30 seconds during active markets
const strategyTimer = setInterval(() => void executeTradingStrategy(), 30_000);
clearInterval(strategyTimer);
Instruction Discriminators#
| Discriminator | Instruction | Description |
|---|---|---|
| 0x00 | InitializeConfig | One-time protocol setup |
| 0x01 | UpdateAuthority | Name pending protocol authority |
| 0x02 | ClaimAuthority | Pending authority accepts control |
| 0x03 | CreateMarket | Create a new prediction market |
| 0x04 | SnapshotEnd | Capture end price after trading window |
| 0x05 | ResolveMarket | Determine outcome from price snapshots |
| 0x06 | ExpireMarket | Late-resolve or expire after timeout |
| 0x07 | MintShares | Mint YES/NO share pairs from USDT |
| 0x08 | DepositFunds | Deposit stablecoin into trader ledger |
| 0x09 | WithdrawFunds | Withdraw stablecoin from trader ledger |
| 0x0A | WithdrawShares | Withdraw shares as SPL tokens |
| 0x0B | PlaceOrder | Place order on the order book |
| 0x0C | PlaceMultiplePostOnlyOrders | Batch post-only placement |
| 0x0D | SwapWithFreeFunds | IOC trade using ledger free funds |
| 0x0E | PlaceLimitOrderWithFreeFunds | Limit/PostOnly using ledger free funds |
| 0x0F | PlaceMultiplePostOnlyOrdersWithFreeFunds | Batch post-only using ledger free funds |
| 0x10 | CancelOrder | Cancel an open order |
| 0x11 | CancelMultipleOrdersById | Bulk cancel explicit order IDs |
| 0x12 | CancelAllOrders | Cancel all caller-owned orders |
| 0x13 | CancelUpTo | Threshold cancel |
| 0x14 | ReduceOrder | Reduce a resting order |
| 0x15 | CancelMultipleOrdersByIdWithFreeFunds | Bulk cancel to ledger free funds |
| 0x16 | CancelAllOrdersWithFreeFunds | Cancel all to ledger free funds |
| 0x17 | CancelUpToWithFreeFunds | Threshold cancel to ledger free funds |
| 0x18 | ReduceOrderWithFreeFunds | Reduce order to ledger free funds |
| 0x19 | ReclaimExpiredOrder | Reclaim expired resting order |
| 0x1A | Redeem | Burn winning tokens for USDT |
| 0x1B | ForceClose | Force-close expired position |
| 0x1C | MarkPositionSettled | Mark empty position settled |
| 0x1D | ClosePosition | Close settled position PDA |
| 0x1E | CloseMarket | Close market and reclaim rent |
| 0x1F | UpdateFeeConfig | Retune curve + three-way split (admin) |
| 0x20 | UpdateTreasuryRecipients | Rotate all 8 protocol treasury recipients |
| 0x21 | SetReferrer | Attribute a referrer to a user |
| 0x22 | InitReferrerEarningsAccount | Create per-referrer earnings PDA |
| 0x23 | ClaimCreatorFees | Sweep deferred creator fees |
| 0x24 | ClaimReferrerEarnings | Sweep referrer earnings to referrer's ATA |
| 0x25 | Pause | Pause protocol trading |
| 0x26 | Unpause | Resume protocol trading |
| 0x27 | EnablePostOnlyMode | Enable protocol-wide PostOnly mode |
| 0x28 | DisablePostOnlyMode | Disable protocol-wide PostOnly mode |
| 0x29 | UpdateTickSize | Update order-book tick size |
| 0x2A | UpdateMinRestingNotional | Update minimum resting notional |
| 0x2B | UpdateMarketCap | Update market max total shares |
| 0x2C | SetMarketEmergencyStatus | Rotate market emergency status |
| 0x2D | ForceCancelMarketOrders | Admin force-cancel market order |
| 0x2E | EnsureTraderLedgerSpace | Allocate or grow trader ledger PDA |
| 0x2F | UpdateOperationalParams | Tune post-deploy operational parameters |
| 0x30 | ProposePythProgramId | Frozen compatibility ABI; rejects with OracleProgramIdFrozen |
| 0x31 | ApplyPythProgramId | Frozen compatibility ABI; rejects with OracleProgramIdFrozen |
| 0x32 | UpdateMarketDefaults | Update default taker fee |
| 0x33 | EnsureDeepOrderbookSpace | Allocate or grow deep orderbook PDA |
| 0x34 | SetPauser | Configure escalation-only pauser authority |
| 0x35 | TopUpCloserRewards | Fund closer reward budget shortfall |
Quick Reference#
When to Trade#
BUY YES when:
- Price is above start AND market underpricing YES
- Strong upward momentum with time remaining
- Market ask price < your fair value estimate
BUY NO when:
- Price is below start AND market underpricing NO
- Strong downward momentum with time remaining
- Market bid price > your fair value estimate
HOLD when:
- No significant mispricing
- Very little time remaining (< 1 minute)
- High oracle confidence interval
- Already at position limits