inNovaAPI
Guides

Complete Betting Flow

End-to-end walkthrough from discovering markets to tracking your portfolio

Overview

This guide walks through the full lifecycle of a bet:

  1. Discover an event and its markets
  2. Check the orderbook before placing
  3. Place a market order (FOK)
  4. Monitor order status via WebSocket
  5. Track your position in the portfolio
  6. Sell (partial or full) before settlement

1. Discover Events and Markets

import requests, time, hmac, hashlib, uuid, json

BASE_URL   = "https://api.winnova.pro/api/ext/v1"
API_KEY    = "your_api_key"
API_SECRET = "your_api_secret"

def sign(method, path, body=""):
    ts        = str(int(time.time() * 1000))  # milliseconds
    nonce     = uuid.uuid4().hex
    body_hash = hashlib.sha256(body.encode()).hexdigest()
    msg       = f"{method}\n{path}\n{ts}\n{nonce}\n{body_hash}"
    sig       = hmac.new(API_SECRET.encode(), msg.encode(), hashlib.sha256).hexdigest()
    return {"X-API-KEY": API_KEY, "X-API-TIMESTAMP": ts,
            "X-API-NONCE": nonce, "X-API-SIGNATURE": sig}

def get(path, params=None):
    return requests.get(f"{BASE_URL}{path}", params=params, headers=sign("GET", path)).json()

def post(path, body):
    raw = json.dumps(body)
    hdrs = {**sign("POST", path, raw), "Content-Type": "application/json"}
    return requests.post(f"{BASE_URL}{path}", data=raw, headers=hdrs).json()

# Find an active NBA event
events = get("/events", {"status": "active", "sport": "basketball", "limit": 10})
event  = events["data"][0]
print(f"Event: {event['title']} — starts {event['game_start_time']}")

# Get its markets
markets = get(f"/events/{event['id']}/markets")
moneyline = next(m for m in markets["data"] if m["type"] == "moneyline")
print(f"Market: {moneyline['question']}")
for o in moneyline["outcomes"]:
    print(f"  {o['name']}: {o['price']:.2f}")

2. Check the Orderbook

Before placing an order, inspect depth to estimate slippage on larger sizes.

# Use the token_id from the desired outcome (e.g. the "yes" token_id)
token_id = moneyline["outcomes"][0]["token_id"]
ob = get(f"/orderbooks/{token_id}")

print("Asks (you buy at these prices):")
for level in ob["data"]["asks"][:5]:
    print(f"  price={level['price']}  size={level['size']}")

Sample response:

{
  "success": true,
  "data": {
    "asset_id": "0xabc...",
    "bids": [
      { "price": "0.54", "size": "300.00" },
      { "price": "0.53", "size": "500.00" }
    ],
    "asks": [
      { "price": "0.55", "size": "200.00" },
      { "price": "0.56", "size": "150.00" }
    ],
    "timestamp": "2026-04-05T00:00:00Z"
  }
}

3. Place a Market Order (FOK)

FOK orders fill immediately at the best available price or cancel entirely — no partial fills are left as open orders.

# Buy $100 on outcome "a" (first outcome = index a)
order = post("/orders", {
    "market_id": market_id,
    "outcome": "a",
    "amount": 100
})
order_id = order["data"]["order_id"]
print(f"Order placed: {order_id}")
const order = await makeRequest('POST', '/api/ext/v1/orders', {
  market_id: '550e8400-e29b-41d4-a716-446655440000',
  outcome: 'a',    // 'a' = first outcome, 'b' = second outcome
  amount: 100
});
const orderId = order.data.order_id;

4. Monitor Order Status (WebSocket)

Subscribe to real-time order updates rather than polling.

// After connecting (see WebSocket Connection guide)
ws.send(JSON.stringify({ action: 'subscribe', topics: [`user:${userId}:orders`] }));

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  if (msg.type === 'data' && msg.data.order_id === orderId) {
    console.log('Status:', msg.data.status);
    // "pending" -> "signing" -> "submitted" -> "matched"
    if (msg.data.status === 'matched') {
      console.log('Filled at avg price:', msg.data.avg_fill_price);
      console.log('Shares received:', msg.data.size_matched / msg.data.avg_fill_price);
    }
  }
};

Or poll via REST if WebSocket is unavailable:

import time

def wait_for_order(order_id, timeout=60):
    for _ in range(timeout):
        result = get(f"/orders/{order_id}")
        status = result["data"]["status"]
        print(f"  status: {status}")
        if status in ("matched", "won", "lost"):
            return result["data"]
        time.sleep(1)
    raise TimeoutError("Order did not settle in time")

order_data = wait_for_order(order_id)
print(f"Filled: {order_data['size_matched']} shares at {order_data['avg_fill_price']}")

5. Track Your Portfolio

Once the order is matched, a position appears in your portfolio.

portfolio = get("/portfolio/positions")
for pos in portfolio["data"]:
    if pos["market_id"] == market_id:
        print(f"Position: {pos['shares']:.4f} YES shares")
        print(f"  Avg cost:      {pos['avg_cost']:.4f}")
        print(f"  Current price: {pos['current_price']:.4f}")
        print(f"  Unrealized PnL: ${pos['unrealized_pnl']:.2f}")

Sample response:

{
  "success": true,
  "data": [
    {
      "id": "f1e2d3c4-b5a6-7890-abcd-012345678901",
      "market_id": "550e8400-e29b-41d4-a716-446655440000",
      "token_option": "yes",
      "shares": 181.82,
      "avg_cost": 0.55,
      "current_price": 0.62,
      "unrealized_pnl": 12.73,
      "status": "open"
    }
  ]
}

Subscribe to live balance updates (triggered after position changes):

ws.send(JSON.stringify({ action: 'subscribe', topics: [`user:${userId}:balance`] }));

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  if (msg.type === 'data' && msg.topic === `user:${userId}:balance`) {
    console.log('Balance updated:', msg.data.balance);
  }
};

6. Sell Your Position

Sell some or all shares before the market settles.

# Sell 50 shares
sell_order = post("/orders", {
    "market_id": market_id,
    "outcome": "a",
    "side": "SELL",
    "shares": 50
})
print("Sell order:", sell_order["data"]["order_id"])
// Sell all shares
await makeRequest('POST', '/api/ext/v1/orders', {
  market_id: '550e8400-e29b-41d4-a716-446655440000',
  outcome: 'a',
  side: 'SELL',
  shares: 181.82
});

Settlement

When the event concludes, WinNova automatically settles all positions:

  • Won positions: shares are redeemed at $1.00 each
  • Lost positions: shares expire at $0.00

You can track settlement via the user:{userID}:orders WebSocket topic — order status will transition to won or lost.


Full Python Script

"""
Full betting flow: discover -> check -> bet -> wait -> portfolio
"""
# (paste the sign/get/post helpers from Step 1 here)

# 1. Pick event
events  = get("/events", {"status": "active", "sport": "basketball"})
event   = events["data"][0]

# 2. Pick market
markets = get(f"/events/{event['id']}/markets")
market  = next(m for m in markets["data"] if m["type"] == "moneyline")
market_id = market["id"]

# 3. Check book (use token_id of the outcome you want to trade)
token_id = market["outcomes"][0]["token_id"]
ob = get(f"/orderbooks/{token_id}")
best_ask = ob["data"]["asks"][0]["price"]
print(f"Best ask: {best_ask}")

# 4. Place FOK
order    = post("/orders", {"market_id": market_id, "outcome": "a", "amount": 50})
order_id = order["data"]["order_id"]

# 5. Wait for fill
filled   = wait_for_order(order_id)
print(f"Filled {filled['size_matched']} shares at {filled['avg_fill_price']}")

# 6. Check portfolio
portfolio = get("/portfolio/positions")
pos = next((p for p in portfolio["data"] if p["market_id"] == market_id), None)
if pos:
    print(f"Position: {pos['shares']:.4f} shares, PnL: ${pos['unrealized_pnl']:.2f}")

Next Steps