Exchange API & SDK
Trade on the CLOB + AMM hybrid orderbook, place TWAP and scale orders, stream real-time market data.
Quick Start
# Get hybrid orderbook (CLOB + AMM from 4 chains)
curl https://cymetica.com/api/v1/exchange/vaix/book/hybrid?depth=30
# Place a limit buy order
curl -X POST https://cymetica.com/api/v1/exchange/vaix/orders \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"side":"buy","quantity":"1000","price":"0.003","order_type":"limit"}'
# Get market stats
curl https://cymetica.com/api/v1/exchange/vaix/stats
from event_trader import EventTrader
client = EventTrader(api_key="evt_...")
# Get hybrid orderbook
book = await client.clob.orderbook("vaix")
# Place a limit buy
order = await client.clob.place_order("vaix", "buy", "1000", price="0.003")
# Place a TWAP order
twap = await client.clob.place_twap("vaix", "buy", "5000", duration_minutes=120)
# Get all markets
markets = await client.clob.markets()
import { EventTrader } from "@cymetica/event-trader";
const client = new EventTrader({ apiKey: "evt_..." });
// Get hybrid orderbook
const book = await client.clob.orderbook("vaix");
// Place a limit buy
const order = await client.clob.placeOrder({
symbol: "vaix", side: "buy", quantity: "1000", price: "0.003"
});
// Place a TWAP order
const twap = await client.clob.placeTwap({
symbol: "vaix", side: "buy", quantity: "5000", durationMinutes: 120
});
# Python
pip install event-trader
# TypeScript / Node.js
npm install @cymetica/event-trader
Authentication
Market data endpoints (orderbook, trades, stats, chart) are public. Trading, account, and withdrawal endpoints require a Bearer token.
# Authenticated request
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://cymetica.com/api/v1/exchange/vaix/orders
Get your API key from /account → API Keys.
Market Data
Public endpoints for pairs, markets, stats, charts, and venue breakdowns. No authentication required.
Returns all available trading pairs with their configuration (base/quote tokens, chain addresses, fees).
curl https://cymetica.com/api/v1/exchange/pairs
Returns all exchange markets with price, 24h volume, liquidity, market cap, and chain info.
curl https://cymetica.com/api/v1/exchange/markets
markets = await client.clob.markets()
const markets = await client.clob.markets();
Price, supply, market cap, 24h volume, and change percentage.
curl https://cymetica.com/api/v1/exchange/vaix/stats
Query Parameters
1m, 5m, 15m, 1h, 4h, 1d (default: 1h)curl "https://cymetica.com/api/v1/exchange/vaix/chart?interval=4h&limit=200"
Breakdown of liquidity across CLOB, Uniswap, Aerodrome, Camelot, and Raydium pools.
curl https://cymetica.com/api/v1/exchange/vaix/venues
Orderbook
The hybrid orderbook merges CLOB resting orders + AMM virtual depth from 4 chains (Ethereum, Base, Arbitrum, Solana).
Query Parameters
curl "https://cymetica.com/api/v1/exchange/vaix/book/hybrid?depth=30"
book = await client.clob.orderbook("vaix", depth=30)
const book = await client.clob.orderbook("vaix", 30);
Returns only CLOB resting orders (no AMM virtual depth). Useful for seeing real user orders.
curl "https://cymetica.com/api/v1/exchange/vaix/book?depth=30"
clob_book = await client.clob.book("vaix", depth=30)
const clobBook = await client.clob.book("vaix", 30);
Returns best bid price/size and best ask price/size. Fastest way to get current spread.
curl https://cymetica.com/api/v1/exchange/vaix/bbo
Query Parameters
curl "https://cymetica.com/api/v1/exchange/vaix/trades?limit=100"
Trading
Place and cancel orders, TWAP orders, and scale orders. All trading endpoints require authentication.
Request Body
buy or selllimit, market, ioc, post_only, or stop_limit (default: limit)live or paper (default: live)curl -X POST https://cymetica.com/api/v1/exchange/vaix/orders \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"side":"buy","quantity":"1000","price":"0.003","order_type":"limit"}'
order = await client.clob.place_order("vaix", "buy", "1000", price="0.003")
const order = await client.clob.placeOrder({
symbol: "vaix", side: "buy", quantity: "1000", price: "0.003"
});
curl -X DELETE "https://cymetica.com/api/v1/exchange/vaix/orders/ord_abc123" \
-H "Authorization: Bearer YOUR_TOKEN"
Time-Weighted Average Price — splits a large order into slices executed over a duration.
Request Body
buy or sellcurl -X POST https://cymetica.com/api/v1/exchange/vaix/twap \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"side":"buy","quantity":"5000","duration_minutes":120,"num_slices":20}'
twap = await client.clob.place_twap("vaix", "buy", "5000", duration_minutes=120, num_slices=20)
const twap = await client.clob.placeTwap({
symbol: "vaix", side: "buy", quantity: "5000", durationMinutes: 120, numSlices: 20
});
curl -X DELETE "https://cymetica.com/api/v1/exchange/vaix/twap/twap_abc123" \
-H "Authorization: Bearer YOUR_TOKEN"
curl "https://cymetica.com/api/v1/exchange/vaix/twap" \
-H "Authorization: Bearer YOUR_TOKEN"
Distributes multiple orders across a price range. Useful for building positions at staggered prices.
Request Body
buy or selllinear or exponential (default: linear)curl -X POST https://cymetica.com/api/v1/exchange/vaix/scale \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"side":"buy","quantity":"10000","price_low":"0.002","price_high":"0.004","num_orders":10}'
scale = await client.clob.place_scale(
"vaix", "buy", "10000", price_low="0.002", price_high="0.004", num_orders=10
)
const scale = await client.clob.placeScale({
symbol: "vaix", side: "buy", quantity: "10000",
priceLow: "0.002", priceHigh: "0.004", numOrders: 10
});
Returns 24-hour ticker data including last price, 24h high/low, volume, and percentage change.
curl https://cymetica.com/api/v1/exchange/vaix/ticker
Returns the server's current UTC timestamp. Useful for synchronizing clocks and calculating request latency.
curl https://cymetica.com/api/v1/exchange/time
Returns the full status and fill details of a specific order by ID.
curl "https://cymetica.com/api/v1/exchange/vaix/orders/ord_abc123" \
-H "Authorization: Bearer YOUR_TOKEN"
Query Parameters
curl "https://cymetica.com/api/v1/exchange/vaix/orders/history?limit=50&offset=0" \
-H "Authorization: Bearer YOUR_TOKEN"
Place multiple orders in a single request. Maximum 10 orders per batch.
Request Body
side, quantity, price, order_typecurl -X POST https://cymetica.com/api/v1/exchange/vaix/orders/batch \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"orders":[{"side":"buy","quantity":"500","price":"0.003","order_type":"limit"},{"side":"buy","quantity":"500","price":"0.0029","order_type":"limit"}]}'
Cancel multiple orders in a single request by providing an array of order IDs.
Request Body
curl -X DELETE https://cymetica.com/api/v1/exchange/vaix/orders/batch \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"order_ids":["ord_abc123","ord_def456"]}'
Account
User account data — open orders, trade history, balances, and withdrawals. All require authentication.
Query Parameters
live or paper (default: live)curl "https://cymetica.com/api/v1/exchange/vaix/orders/open" \
-H "Authorization: Bearer YOUR_TOKEN"
Query Parameters
curl "https://cymetica.com/api/v1/exchange/vaix/trades/mine?limit=100" \
-H "Authorization: Bearer YOUR_TOKEN"
Returns on-chain balance + fill credits for the trading pair.
curl "https://cymetica.com/api/v1/exchange/vaix/balance" \
-H "Authorization: Bearer YOUR_TOKEN"
Request Body
curl -X POST https://cymetica.com/api/v1/exchange/vaix/withdraw \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"amount":"500","destination":"0x..."}'
WebSocket — Real-Time Streams
Stream live orderbook updates, trades, prices, and market data via WebSocket.
Real-time orderbook deltas. Sends full book snapshot on connect, then incremental updates.
const ws = new WebSocket("wss://cymetica.com/ws/exchange/vaix/book");
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
// msg.type: "snapshot" | "update"
// msg.bids: [[price, size], ...]
// msg.asks: [[price, size], ...]
};
Real-time trade execution feed. Each message contains price, quantity, side, and venue (CLOB/AMM).
const ws = new WebSocket("wss://cymetica.com/ws/exchange/vaix/trades");
ws.onmessage = (e) => {
const trade = JSON.parse(e.data);
// trade: { price, quantity, side, venue, timestamp }
};
Lightweight price-only feed. Best for tickers and watchlists.
const ws = new WebSocket("wss://cymetica.com/ws/exchange/vaix/price");
Aggregated market-level updates for all trading pairs (price, volume, 24h change).
const ws = new WebSocket("wss://cymetica.com/ws/exchange/markets");
Real-time best bid and offer updates, throttled to 50ms. Lowest-latency way to track the top of book.
const ws = new WebSocket("wss://cymetica.com/ws/exchange/vaix/bbo");
ws.onmessage = (e) => {
const bbo = JSON.parse(e.data);
// bbo: { best_bid, best_bid_size, best_ask, best_ask_size, timestamp }
};
Authenticated private stream for real-time order status updates and fill notifications. Pass your JWT as a query parameter.
const ws = new WebSocket("wss://cymetica.com/ws/exchange/private?token=YOUR_JWT");
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
// msg.type: "order_update" | "fill" | "balance_update"
// order_update: { order_id, status, filled_quantity, remaining_quantity }
// fill: { trade_id, order_id, price, quantity, side, timestamp }
};
Rate Limits
MCP Tools
All exchange endpoints are available as MCP tools for AI integration. Install the EventTrader MCP server to use these tools with any MCP-compatible AI assistant.
Example: Market-Making Bot
A complete, runnable market-making bot using the Python SDK. Places two-sided quotes around the mid price, tracks fills and P&L, and cancels all orders on shutdown. Starts in paper mode by default.
Install & Run
# Install
pip install event-trader
# Run in paper mode (no real money)
python example_exchange_bot.py --pair vaix --api-key evt_...
# Run live
python example_exchange_bot.py --pair vaix --api-key evt_... --live
# Custom spread (100 bps = 1%) and order size
python example_exchange_bot.py --pair sbio --api-key evt_... --spread-bps 100 --size 500
# With email/password auth
python example_exchange_bot.py --pair vaix --email user@example.com --password secret
# Clone Mode — create a clone, fund it, trade live
python example_exchange_bot.py --pair vaix --api-key evt_... \
--clone-from macd --clone-name "My MM" --fund-amount 100 --withdraw-on-stop
# Clone Mode — attach to existing clone with risk limits
python example_exchange_bot.py --pair vaix --api-key evt_... \
--clone-id <id> --max-loss 20 --max-position 5000
Full Source
Condensed version below. Download the full script (826 lines) with risk controls, balance sync, and detailed error handling.
#!/usr/bin/env python3
"""Example market-making bot for EventTrader Exchange.
A simple two-sided market maker that places bid and ask orders around the
mid price, tracks fills, and manages position/P&L.
Setup: pip install event-trader
"""
from __future__ import annotations
import argparse, asyncio, logging, os, signal, sys
from decimal import Decimal, ROUND_DOWN
from event_trader import EventTrader
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", datefmt="%H:%M:%S")
log = logging.getLogger("mm-bot")
# ── Position Tracker ──────────────────────────────────────────────────
class PositionTracker:
"""Tracks inventory, realized P&L, and unrealized P&L."""
def __init__(self, symbol: str):
self.symbol = symbol.upper()
self.inventory = Decimal("0")
self.realized_pnl = Decimal("0")
self.avg_entry = Decimal("0")
self.trade_count = 0
self._seen_trade_ids: set[str] = set()
def process_fills(self, trades: list[dict]) -> int:
"""Process new fills and update position. Returns count of new fills."""
new_fills = 0
for t in trades:
tid = t.get("trade_id") or t.get("id") or ""
if tid in self._seen_trade_ids:
continue
self._seen_trade_ids.add(tid)
new_fills += 1
self.trade_count += 1
qty = Decimal(str(t.get("quantity", "0")))
price = Decimal(str(t.get("price", "0")))
side = t.get("side", "").lower()
if side == "buy":
cost = qty * price
if self.inventory >= 0:
total_cost = self.avg_entry * self.inventory + cost
self.inventory += qty
self.avg_entry = (total_cost / self.inventory) if self.inventory else Decimal("0")
else:
self.realized_pnl += qty * (self.avg_entry - price)
self.inventory += qty
elif side == "sell":
if self.inventory > 0:
self.realized_pnl += qty * (price - self.avg_entry)
self.inventory -= qty
else:
total_cost = abs(self.avg_entry * self.inventory) + qty * price
self.inventory -= qty
self.avg_entry = (total_cost / abs(self.inventory)) if self.inventory else Decimal("0")
return new_fills
def unrealized_pnl(self, mid_price: Decimal) -> Decimal:
if self.inventory == 0 or mid_price == 0:
return Decimal("0")
if self.inventory > 0:
return self.inventory * (mid_price - self.avg_entry)
return abs(self.inventory) * (self.avg_entry - mid_price)
def summary(self, mid_price: Decimal) -> str:
upnl = self.unrealized_pnl(mid_price)
total = self.realized_pnl + upnl
return (
f"Position: {self.inventory:+} {self.symbol} | "
f"Realized: {self.realized_pnl:+.4f} USDC | "
f"Unrealized: {upnl:+.4f} USDC | "
f"Total P&L: {total:+.4f} USDC | Trades: {self.trade_count}"
)
# ── Helpers ───────────────────────────────────────────────────────────
def round_to_tick(price, tick_size):
if tick_size <= 0: return price
return (price / tick_size).to_integral_value(rounding=ROUND_DOWN) * tick_size
def round_to_lot(qty, lot_size):
if lot_size <= 0: return qty
return (qty / lot_size).to_integral_value(rounding=ROUND_DOWN) * lot_size
# ── Market Maker ──────────────────────────────────────────────────────
class MarketMaker:
def __init__(self, client, symbol, spread_bps, order_size, refresh_interval, mode, drift_threshold_bps):
self.client = client
self.symbol = symbol
self.spread_bps = spread_bps
self.order_size = order_size
self.refresh_interval = refresh_interval
self.mode = mode
self.drift_threshold_bps = drift_threshold_bps
self.tracker = PositionTracker(symbol)
self.running = True
self.tick_size = Decimal("0.000001")
self.lot_size = Decimal("1")
self.min_order_size = Decimal("1")
self._open_order_ids: list[str] = []
async def initialize(self):
"""Fetch pair info (tick size, lot size) and check balance."""
try:
resp = await self.client.clob.list_pairs()
pairs = resp if isinstance(resp, list) else resp.get("pairs", [])
for p in pairs:
base = (p.get("base_symbol") or "").lower()
if base == self.symbol.lower():
self.tick_size = Decimal(str(p.get("tick_size", self.tick_size)))
self.lot_size = Decimal(str(p.get("lot_size", self.lot_size)))
self.min_order_size = Decimal(str(p.get("min_order_size", self.min_order_size)))
log.info("Pair: %s | tick=%s | lot=%s | min=%s", base, self.tick_size, self.lot_size, self.min_order_size)
break
except Exception as e:
log.warning("Could not fetch pair info: %s", e)
try:
bal = await self.client.clob.balance(self.symbol)
log.info("Balance: %s", bal)
except Exception as e:
log.warning("Could not fetch balance: %s", e)
async def cancel_all(self):
"""Cancel all open orders."""
for oid in self._open_order_ids:
try:
await self.client.clob.cancel_order(self.symbol, oid, mode=self.mode)
log.info("Cancelled %s", oid)
except Exception as e:
log.warning("Failed to cancel %s: %s", oid, e)
self._open_order_ids.clear()
async def get_mid_price(self) -> Decimal | None:
"""Get mid price from best bid/offer."""
try:
bbo = await self.client.clob.bbo(self.symbol)
bid = Decimal(str(bbo.get("best_bid") or bbo.get("bid") or 0))
ask = Decimal(str(bbo.get("best_ask") or bbo.get("ask") or 0))
if bid > 0 and ask > 0:
return (bid + ask) / 2
except Exception as e:
log.warning("Could not get mid price: %s", e)
return None
async def run_cycle(self, last_mid):
"""Run one quoting cycle. Returns the current mid price."""
mid = await self.get_mid_price()
if mid is None or mid == 0:
log.warning("No mid price — skipping cycle")
return last_mid
# Check for new fills
try:
result = await self.client.clob.my_trades(self.symbol, limit=20)
trades = result if isinstance(result, list) else result.get("trades", [])
new = self.tracker.process_fills(trades)
if new:
log.info("*** %d new fill(s) ***", new)
except Exception:
pass
# Decide whether to re-quote
should_requote = not self._open_order_ids
if last_mid and last_mid > 0 and not should_requote:
drift = abs(mid - last_mid) / last_mid * Decimal("10000")
if drift > self.drift_threshold_bps:
log.info("Mid drifted %.1f bps — re-quoting", drift)
should_requote = True
if should_requote:
await self.cancel_all()
spread = mid * Decimal(str(self.spread_bps)) / Decimal("10000")
half = spread / 2
bid_price = round_to_tick(mid - half, self.tick_size)
ask_price = round_to_tick(mid + half + self.tick_size, self.tick_size)
qty = round_to_lot(self.order_size, self.lot_size)
if qty < self.min_order_size:
log.warning("Order size below minimum — skipping")
return mid
if bid_price >= ask_price:
ask_price = bid_price + self.tick_size
log.info("Quoting: BID %.8f x %s | ASK %.8f x %s", bid_price, qty, ask_price, qty)
# Place bid
try:
r = await self.client.clob.place_order(
self.symbol, "buy", str(qty),
price=str(bid_price), order_type="post_only", mode=self.mode,
)
oid = r.get("order_id", r.get("id", ""))
if oid: self._open_order_ids.append(oid)
log.info(" BID placed: %s", oid)
except Exception as e:
log.warning(" BID failed: %s", e)
# Place ask
try:
r = await self.client.clob.place_order(
self.symbol, "sell", str(qty),
price=str(ask_price), order_type="post_only", mode=self.mode,
)
oid = r.get("order_id", r.get("id", ""))
if oid: self._open_order_ids.append(oid)
log.info(" ASK placed: %s", oid)
except Exception as e:
log.warning(" ASK failed: %s", e)
else:
# Refresh tracked orders (some may have filled)
try:
result = await self.client.clob.open_orders(self.symbol, mode=self.mode)
orders = result if isinstance(result, list) else result.get("orders", [])
self._open_order_ids = [o.get("order_id", o.get("id", "")) for o in orders]
except Exception:
pass
log.info("Mid: %.8f | Orders: %d | %s", mid, len(self._open_order_ids), self.tracker.summary(mid))
return mid
async def run(self):
"""Main loop."""
log.info("Starting %s/USDC | spread=%dbps | size=%s | mode=%s",
self.symbol.upper(), self.spread_bps, self.order_size, self.mode)
await self.initialize()
last_mid = None
while self.running:
try:
last_mid = await self.run_cycle(last_mid)
except Exception as e:
log.error("Cycle error: %s", e)
for _ in range(int(self.refresh_interval * 10)):
if not self.running: break
await asyncio.sleep(0.1)
# Shutdown — cancel all open orders
log.info("Shutting down — cancelling orders...")
await self.cancel_all()
log.info("Final: %s", self.tracker.summary(last_mid or Decimal("0")))
# ── Clone Bot Manager ─────────────────────────────────────────────────
class CloneBotManager:
"""Thin async wrapper around the /api/v1/cloned-bots/ REST API."""
BASE = "/api/v1/cloned-bots"
def __init__(self, http):
self._http = http
async def create_clone(self, source_type, source_id, name=None, is_paper=None):
return await self._http.post(f"{self.BASE}/clone", json={
"source_type": source_type, "source_id": source_id,
"custom_name": name, "is_paper": is_paper,
})
async def get_profile(self, cid):
return await self._http.get(f"{self.BASE}/{cid}/profile")
async def get_balance(self, cid):
return await self._http.get(f"{self.BASE}/{cid}/balance")
async def fund(self, cid, amount):
return await self._http.post(f"{self.BASE}/{cid}/fund", json={"amount": amount})
async def withdraw(self, cid, amount):
return await self._http.post(f"{self.BASE}/{cid}/withdraw", json={"amount": amount})
async def toggle_trading(self, cid, enabled):
return await self._http.post(
f"{self.BASE}/{cid}/toggle-trading", params={"enabled": str(enabled).lower()})
async def get_skills(self, cid):
return await self._http.get(f"{self.BASE}/{cid}/skills")
async def equip_skill(self, cid, skill_id, slot_position=None):
body = {"skill_id": skill_id}
if slot_position is not None: body["slot_position"] = slot_position
return await self._http.post(f"{self.BASE}/{cid}/skills/equip", json=body)
async def unequip_skill(self, cid, skill_id):
return await self._http.delete(f"{self.BASE}/{cid}/skills/{skill_id}")
# ── CLI ───────────────────────────────────────────────────────────────
async def main():
p = argparse.ArgumentParser(description="EventTrader market-making bot")
p.add_argument("--pair", default="vaix")
p.add_argument("--api-key", default=os.environ.get("ET_API_KEY"))
p.add_argument("--email", default=os.environ.get("ET_EMAIL"))
p.add_argument("--password", default=os.environ.get("ET_PASSWORD"))
p.add_argument("--base-url", default="https://cymetica.com")
p.add_argument("--spread-bps", type=int, default=50)
p.add_argument("--size", type=Decimal, default=Decimal("100"))
p.add_argument("--refresh", type=float, default=10.0)
p.add_argument("--drift-bps", type=int, default=25)
p.add_argument("--live", action="store_true")
# Clone mode
p.add_argument("--clone-id", help="Attach to existing clone")
p.add_argument("--clone-from", help="Create clone from species slug")
p.add_argument("--clone-name", help="Custom name for new clone")
p.add_argument("--fund-amount", type=float, default=0)
p.add_argument("--max-loss", type=Decimal, default=None)
p.add_argument("--max-position", type=Decimal, default=None)
p.add_argument("--withdraw-on-stop", action="store_true")
p.add_argument("--equip-skill", action="append", default=[])
args = p.parse_args()
# Authenticate
if args.api_key:
client = EventTrader(api_key=args.api_key, base_url=args.base_url)
elif args.email and args.password:
client = await EventTrader.from_credentials(args.email, args.password, base_url=args.base_url)
else:
log.error("Provide --api-key or --email/--password")
sys.exit(1)
clone_mode = bool(args.clone_id or args.clone_from)
mode = "live" if (clone_mode or args.live) else "paper"
clone_id = clone_manager = None
if clone_mode:
clone_manager = CloneBotManager(client._http)
if args.clone_from:
resp = await clone_manager.create_clone("wta_species", args.clone_from,
args.clone_name, mode != "live")
clone_id = resp["id"]
else:
clone_id = args.clone_id
# Equip skills, fund, enable trading
for sid in args.equip_skill:
await clone_manager.equip_skill(clone_id, sid)
if args.fund_amount > 0:
await clone_manager.fund(clone_id, args.fund_amount)
await clone_manager.toggle_trading(clone_id, True)
bot = MarketMaker(client, args.pair, args.spread_bps, args.size, args.refresh, mode, args.drift_bps)
# Graceful shutdown on Ctrl+C
loop = asyncio.get_running_loop()
for sig in (signal.SIGINT, signal.SIGTERM):
loop.add_signal_handler(sig, lambda: setattr(bot, "running", False))
async with client:
await bot.run()
if clone_manager and clone_id:
await clone_manager.toggle_trading(clone_id, False)
if args.withdraw_on_stop:
bal = await clone_manager.get_balance(clone_id)
avail = bal.get("available") or bal.get("balance", 0)
if avail > 0:
await clone_manager.withdraw(clone_id, avail)
if __name__ == "__main__":
asyncio.run(main())
How It Works
list_pairs().bbo(), compute mid = (best_bid + best_ask) / 2.my_trades() for new fills, update position and P&L tracker.post_only bid at mid - spread/2 and ask at mid + spread/2 via place_order().cancel_all() to remove all open orders, then prints final P&L.Clone Mode adds these steps:
--clone-from, or attach to an existing one via --clone-id.--equip-skill. Loads skill loadout and applies custom_params (e.g., spread, max inventory).--fund-amount, read SL/TP from clone settings, enable trading.--max-loss, clone stop-loss %, and take-profit %. Auto-stops on breach.--withdraw-on-stop), log final clone P&L.Configuration
vaix, sbio, btc, eth (default: vaix)ET_API_KEY env var)ET_EMAIL / ET_PASSWORD env vars)Clone Mode
Attach to a cloned bot for managed trading with risk controls. Creates or connects to a clone, funds it from your account balance, and trades with stop-loss/take-profit enforcement. Defaults to live mode when a clone is active.
macd, momentum)wta_species, perpetual_agent, backtest_bot (default: wta_species)--equip-skill market_making --equip-skill rsi_momentum# Create a clone from MACD species, fund with 100 USDC, auto-withdraw on stop
python example_exchange_bot.py --pair vaix --api-key evt_... \
--clone-from macd --clone-name "My MM Bot" --fund-amount 100 --withdraw-on-stop
# Attach to existing clone with loss limit
python example_exchange_bot.py --pair vaix --api-key evt_... \
--clone-id abc-123 --max-loss 20 --max-position 5000
# Clone with Market Making skill equipped
python example_exchange_bot.py --pair vaix --api-key evt_... \
--clone-from momentum --fund-amount 200 --equip-skill market_making
Bot Skills
Skills are modular trading capabilities that you equip to a cloned bot. Each skill modifies how the bot generates signals, manages risk, or provides liquidity. Skills are organized into three slot types:
Market Making Skill
The market_making skill is a passive modifier that transforms a cloned bot into a two-sided liquidity provider. When equipped, the bot posts bid and ask orders around the mid price, captures the bid-ask spread as profit, and manages inventory risk automatically.
market_making — equip via --equip-skill market_making or the REST APIpost_only orders on both sides of the book. Earns the bid-ask spread on every round-trip fill.max_position, only places reducing-side orders. Prevents runaway directional exposure.--max-loss. Bot auto-stops and withdraws when limits hit.custom_params can override spread_bps and max_inventory per-bot without changing CLI args.Advantages of Skill-Based Bot Management
market_making + rsi_momentum + volatility_filter. The skill resolver blends signals via weighted voting.--clone-id and your full loadout is restored automatically.go-live with the same loadout. Validated strategies carry over.Skill REST API
{"skill_id": "market_making"}