Skip to content

ojo-network/turbine-py-client

Repository files navigation

Turbine Python Client

A Python client for market makers to interact with the Turbine CLOB (Central Limit Order Book) prediction markets API.

Claude Skill

We have a Claude skill published that can help you write a trading bot. Run this command to get started:

curl -sSL turbinefi.com/claude | bash

Overview

This client provides a clean, typed interface for:

  • Creating and signing EIP-712 orders
  • Submitting orders to the Turbine orderbook
  • Managing positions and tracking fills
  • Subscribing to real-time orderbook updates via WebSocket

Table of Contents

  1. Installation
  2. Getting API Credentials
  3. Quick Start
  4. Architecture
  5. Authentication
  6. Order Management
  7. Market Data
  8. WebSocket Streaming
  9. Data Types
  10. API Reference
  11. Examples
  12. Development

Installation

pip install turbine-py-client

Or install from source:

git clone https://github.com/ojo-network/turbine-py-client.git
cd turbine-py-client
pip install -e .

Getting API Credentials

To trade on Turbine, you need:

  1. Wallet Private Key - Your Ethereum wallet's private key for signing orders
  2. API Key ID - Identifier for your API key
  3. API Private Key - Ed25519 private key for authenticating API requests

Self-Service Registration

You can register for API credentials directly using your wallet:

from turbine_client import TurbineClient

# Request API credentials (only need to do this once!)
credentials = TurbineClient.request_api_credentials(
    host="https://api.turbinefi.com",
    private_key="your_wallet_private_key",
)

print(f"API Key ID: {credentials['api_key_id']}")
print(f"API Private Key: {credentials['api_private_key']}")
# SAVE THESE! The private key cannot be retrieved later.

This will:

  1. Sign a message with your wallet to prove ownership
  2. Generate new Ed25519 API credentials linked to your wallet
  3. Return the credentials (save the private key - it's only shown once!)

Quick Start

Full Trading Example

from turbine_client import TurbineClient
from turbine_client.types import Outcome, Side

# Initialize with all credentials
client = TurbineClient(
    host="https://api.turbinefi.com",
    chain_id=137,  # Polygon mainnet
    private_key="your_wallet_private_key",
    api_key_id="your_api_key_id",
    api_private_key="your_api_private_key",
)

# Get the latest BTC quick market
market = client.get_quick_market("BTC")
print(f"Market: {market.question}")

# Place a limit buy order
order = client.create_limit_buy(
    market_id=market.market_id,
    outcome=Outcome.YES,
    price=500000,      # 50% (price scaled by 1e6)
    size=1_000_000,    # 1 share (6 decimals)
)

result = client.post_order(order)
print(f"Order submitted: {result['orderHash']}")

# Check orderbook
orderbook = client.get_orderbook(market.market_id)
print(f"Best bid: {orderbook.bids[0].price / 10000:.2f}%")
print(f"Best ask: {orderbook.asks[0].price / 10000:.2f}%")

# Cancel the order
client.cancel_order(
    order_hash=result['orderHash'],
    market_id=market.market_id,
    side=Side.BUY,
)

client.close()

Read-Only Access (No Authentication)

from turbine_client import TurbineClient

# Public endpoints - no auth required
client = TurbineClient(
    host="https://api.turbinefi.com",
    chain_id=137,
)

# Get all markets
markets = client.get_markets()

# Get orderbook for a market
orderbook = client.get_orderbook(market_id="0x...")

# Get recent trades
trades = client.get_trades(market_id="0x...")

Architecture

Project Structure

turbine_py_client/
├── __init__.py                 # Main exports
├── client.py                   # TurbineClient class
├── signer.py                   # EIP-712 order signing
├── auth.py                     # Bearer token generation (Ed25519)
├── config.py                   # Chain configurations
├── constants.py                # Constants (endpoints, chain IDs)
├── exceptions.py               # Custom exceptions
├── types.py                    # Data types and models
├── order_builder/
│   ├── __init__.py
│   ├── builder.py              # OrderBuilder class
│   └── helpers.py              # Price/size utilities
├── http/
│   ├── __init__.py
│   └── client.py               # HTTP request handling
├── ws/
│   ├── __init__.py
│   └── client.py               # WebSocket client
└── utils.py                    # Utility functions

Design Patterns

  1. Modular Authentication - Separate concerns for EIP-712 signing vs Bearer tokens
  2. Builder Pattern - OrderBuilder encapsulates order creation and signing
  3. Dataclass Types - Clean, serializable data structures with type hints
  4. Async WebSocket - Non-blocking real-time updates

Authentication

Turbine uses two authentication mechanisms:

1. EIP-712 Order Signing (Required for Trading)

All orders must be signed using EIP-712 structured data. The client handles this automatically.

Domain Separator:

{
    "name": "Turbine",
    "version": "1",
    "chainId": 84532,  # Chain-specific
    "verifyingContract": "0x..."  # Settlement contract
}

Order Type:

Order(
    bytes32 marketId,
    address trader,
    uint8 side,        # 0=BUY, 1=SELL
    uint8 outcome,     # 0=YES, 1=NO
    uint256 price,
    uint256 size,
    uint256 nonce,
    uint256 expiration,
    address makerFeeRecipient
)

2. Bearer Token Authentication (For Private Endpoints)

Some endpoints require Ed25519 bearer tokens:

# Token format: base64url(payload).base64url(signature)
# Payload: {"kid": keyId, "ts": timestamp, "n": nonce}

Order Management

Creating Orders

from turbine_client import OrderArgs, Side, Outcome

# Basic limit order
order = OrderArgs(
    market_id="0x1234...",
    side=Side.BUY,
    outcome=Outcome.YES,
    price=500000,       # 50%
    size=10000000,      # 10 shares
    expiration=int(time.time()) + 86400,  # 24 hours
)

signed = client.create_order(order)
result = client.post_order(signed)

Price Representation

Prices are scaled by 1,000,000 (1e6):

  • 500000 = 50% (even odds)
  • 250000 = 25%
  • 750000 = 75%
  • Range: 1 to 999,999

Order Lifecycle

  1. Create - Build order parameters
  2. Sign - EIP-712 signature via eth_account
  3. Submit - POST to /api/v1/orders
  4. Match - Engine matches against orderbook
  5. Settle - On-chain settlement
  6. Confirm - Position updated

Canceling Orders

# Cancel single order
client.cancel_order(
    order_hash="0x...",
    market_id="0x...",
    side=Side.BUY
)

# Cancel all orders for a market
client.cancel_market_orders(market_id="0x...")

Market Data

Get Markets

# All markets
markets = client.get_markets()

# Filter by chain
markets = client.get_markets(chain_id=137)

# Single market
market = client.get_market(market_id="0x...")

Get Orderbook

# Full orderbook
orderbook = client.get_orderbook(market_id="0x...")

# Filter by outcome
yes_book = client.get_orderbook(market_id="0x...", outcome=Outcome.YES)

# Access bids/asks
for bid in orderbook.bids:
    print(f"Bid: {bid.price} x {bid.size}")

Get Trades

# Recent trades (last 100)
trades = client.get_trades(market_id="0x...")

# Trade details
for trade in trades:
    print(f"{trade.timestamp}: {trade.price} x {trade.size}")

Get Statistics

# Market stats
stats = client.get_stats(market_id="0x...")
print(f"24h Volume: {stats.volume_24h}")
print(f"Last Price: {stats.last_price}")

# Platform stats
platform = client.get_platform_stats()
print(f"Total Markets: {platform.market_count}")

Quick Markets (15-minute BTC/ETH)

# Get active quick market
qm = client.get_quick_market(asset="BTC")
print(f"Strike: ${qm.start_price / 1e8}")
print(f"Expires: {qm.end_time}")

# Price feed
price = client.get_quick_market_price(asset="BTC")
print(f"Current BTC: ${price.price_usd}")

WebSocket Streaming

Basic Subscription

import asyncio
from turbine_client import TurbineWSClient

async def main():
    ws = TurbineWSClient(host="wss://api.turbinefi.com")

    async with ws.connect() as stream:
        # Subscribe to market
        await stream.subscribe(market_id="0x...")

        async for message in stream:
            if message.type == "orderbook":
                print(f"Orderbook update: {len(message.data.bids)} bids")
            elif message.type == "trade":
                print(f"Trade: {message.data.price} x {message.data.size}")

asyncio.run(main())

Message Types

# Orderbook update
{
    "type": "orderbook",
    "marketId": "0x...",
    "data": {
        "bids": [{"price": 500000, "size": 10000}],
        "asks": [{"price": 510000, "size": 5000}],
        "lastUpdate": 1705000000
    }
}

# Trade execution
{
    "type": "trade",
    "marketId": "0x...",
    "data": {
        "price": 505000,
        "size": 5000,
        "outcome": 0,
        "side": 0,
        "maker": "0x...",
        "taker": "0x..."
    }
}

# Quick market update
{
    "type": "quick_market",
    "data": {
        "asset": "BTC",
        "marketId": "0x...",
        "startPrice": 95000000000,
        "resolved": false
    }
}

Data Types

Core Types

from dataclasses import dataclass
from enum import Enum, IntEnum

class Side(IntEnum):
    BUY = 0
    SELL = 1

class Outcome(IntEnum):
    YES = 0
    NO = 1

@dataclass
class OrderArgs:
    market_id: str
    side: Side
    outcome: Outcome
    price: int          # 0 to 1,000,000
    size: int           # 6 decimals
    expiration: int     # Unix timestamp
    nonce: int = 0      # Auto-generated if 0
    maker_fee_recipient: str = "0x0000000000000000000000000000000000000000"

@dataclass
class SignedOrder:
    market_id: str
    trader: str
    side: int
    outcome: int
    price: int
    size: int
    nonce: int
    expiration: int
    maker_fee_recipient: str
    signature: str
    order_hash: str

@dataclass
class OrderBookSnapshot:
    market_id: str
    bids: list[PriceLevel]
    asks: list[PriceLevel]
    last_update: int

@dataclass
class PriceLevel:
    price: int
    size: int

@dataclass
class Trade:
    market_id: str
    price: int
    size: int
    outcome: int
    side: int
    maker: str
    taker: str
    timestamp: int
    trade_hash: str

@dataclass
class Position:
    market_id: str
    user_address: str
    yes_shares: int
    no_shares: int
    invested: int
    last_trade_price: int

@dataclass
class Market:
    id: str
    question: str
    description: str
    category: str
    expiration_time: int
    contract_address: str
    chain_id: int
    resolved: bool
    winning_outcome: int | None

API Reference

Public Endpoints (No Auth)

Method Endpoint Description
get_markets() GET /api/v1/markets List all markets
get_orderbook(market_id) GET /api/v1/orderbook/{id} Get orderbook
get_trades(market_id) GET /api/v1/trades/{id} Get trade history
get_stats(market_id) GET /api/v1/stats/{id} Get market stats
get_platform_stats() GET /api/v1/platform/stats Get platform stats
get_holders(market_id) GET /api/v1/holders/{id} Get top holders
get_quick_market(asset) GET /api/v1/quick-markets/{asset} Get active quick market

Authenticated Endpoints (Bearer Token)

Method Endpoint Description
post_order(order) POST /api/v1/orders Submit signed order
get_orders(trader) GET /api/v1/orders Get user's open orders
cancel_order(hash) DELETE /api/v1/orders/{hash} Cancel order
get_positions(user) GET /api/v1/positions/{market} Get user position
get_user_positions(addr) GET /api/v1/users/{addr}/positions All positions
get_user_activity(addr) GET /api/v1/users/{addr}/activity Trading activity

Relayer Endpoints (Gasless)

Method Endpoint Description
ctf_approval(request) POST /api/v1/relayer/ctf-approval Gasless CTF approval
ctf_redemption(request) POST /api/v1/relayer/ctf-redemption Gasless redemption

Examples

Market Making Bot

import asyncio
from turbine_client import TurbineClient, TurbineWSClient, OrderArgs, Side, Outcome

class SimpleMarketMaker:
    def __init__(self, client: TurbineClient, market_id: str):
        self.client = client
        self.market_id = market_id
        self.spread = 20000  # 2% spread

    async def run(self):
        ws = TurbineWSClient(host=self.client.host)

        async with ws.connect() as stream:
            await stream.subscribe(market_id=self.market_id)

            async for msg in stream:
                if msg.type == "orderbook":
                    await self.update_quotes(msg.data)

    async def update_quotes(self, orderbook):
        # Cancel existing orders
        self.client.cancel_market_orders(self.market_id)

        # Calculate mid price
        best_bid = orderbook.bids[0].price if orderbook.bids else 400000
        best_ask = orderbook.asks[0].price if orderbook.asks else 600000
        mid = (best_bid + best_ask) // 2

        # Place new quotes
        buy_order = self.client.create_order(OrderArgs(
            market_id=self.market_id,
            side=Side.BUY,
            outcome=Outcome.YES,
            price=mid - self.spread // 2,
            size=1000000,
            expiration=int(time.time()) + 300,
        ))

        sell_order = self.client.create_order(OrderArgs(
            market_id=self.market_id,
            side=Side.SELL,
            outcome=Outcome.YES,
            price=mid + self.spread // 2,
            size=1000000,
            expiration=int(time.time()) + 300,
        ))

        self.client.post_order(buy_order)
        self.client.post_order(sell_order)

Position Monitoring

from turbine_client import TurbineClient

client = TurbineClient(
    host="https://api.turbinefi.com",
    chain_id=137,
    api_key_id="...",
    api_private_key="..."
)

# Get all positions
positions = client.get_user_positions(
    address="0x...",
    chain_id=137
)

for pos in positions:
    market = client.get_market(pos.market_id)
    print(f"\n{market.question}")
    print(f"  YES: {pos.yes_shares / 1e6:.2f} shares")
    print(f"  NO:  {pos.no_shares / 1e6:.2f} shares")
    print(f"  Cost Basis: ${pos.invested / 1e6:.2f}")

Development

Setup

# Clone repo
git clone https://github.com/ojo-network/turbine-py-client.git
cd turbine-py-client

# Create virtual environment
python -m venv venv
source venv/bin/activate

# Install dev dependencies
pip install -e ".[dev]"

Running Tests

pytest tests/

Type Checking

mypy turbine_py_client/

Linting

ruff check turbine_py_client/
ruff format turbine_py_client/

Chain Configuration

Chain Chain ID
Polygon 137

Error Handling

from turbine_client.exceptions import (
    TurbineApiError,
    OrderValidationError,
    SignatureError,
    AuthenticationError,
)

try:
    client.post_order(signed_order)
except OrderValidationError as e:
    print(f"Invalid order: {e}")
except AuthenticationError as e:
    print(f"Auth failed: {e}")
except TurbineApiError as e:
    print(f"API error ({e.status_code}): {e.message}")

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Run tests and linting
  5. Submit a pull request

License

MIT License - see LICENSE


Links

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors