Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.simmer.markets/llms.txt

Use this file to discover all available pages before exploring further.

Use client.ensure_can_trade() alongside position sizing: sizing decides how much, this pre-flight decides whether to trade at all given current wallet balance.

client.ensure_can_trade()

A one-call pre-flight that catches underfunded wallets before you try to place an order. Every rejected trade round-trips to the backend, logs a failure, and gets retried on the next cron tick — replacing the loop with a single status fetch per run is a pure win for skill reliability and observability.
Available in simmer-sdk >= 0.11.1. Collateral-agnostic — reads pUSD on V2 (post-2026-04-28 cutover), USDC.e on V1, so it keeps working across the migration without any code change on your side.

Quick start

from simmer_sdk import SimmerClient

client = SimmerClient()
preflight = client.ensure_can_trade(min_usd=1.0)

if not preflight["ok"]:
    # Skip cleanly — harness + automaton reporting distinguish this from "skill broken"
    print(f"Skip: {preflight['reason']} (balance ${preflight['balance']:.2f} {preflight['collateral']})")
    return

# Cap your per-run size to leave room for fees + slippage
order_size = min(MY_MAX_BET, preflight["max_safe_size"])

Arguments

ArgDefaultMeaning
min_usd1.0Minimum viable trade size in active collateral. Below this, ok=False.
venueclient’s venueOnly "polymarket" runs the check. Other venues short-circuit to ok=True.
safety_buffer0.02Fraction of balance kept as fee/slippage buffer. max_safe_size = balance × (1 − safety_buffer).

Returns

A dict with the following keys:
FieldMeaning
okTrue if balance ≥ min_usd (or non-polymarket venue).
balanceActive collateral balance in USD-equivalent units.
collateral"pUSD" (V2), "USDC.e" (V1), or "" (non-polymarket).
exchange_version"v1" or "v2" — matches server-side flag.
reason"ok", "insufficient_balance", "no_wallet", "balance_unavailable", or "skipped_non_polymarket".
max_safe_sizebalance × (1 − safety_buffer), or 0.0 when ok=False.

When to call it

Call ensure_can_trade() once per skill run, before any market discovery or signal generation. Running it at the top of your loop costs one REST call and eliminates every downstream rejected order when the wallet is under-funded.
  • Underfunded → emit a skip report (skip_reason="insufficient_balance") and return. The automaton reporter will surface this cleanly, distinct from “skill broken.”
  • Sized correctly → clamp your per-run MAX_BET_USD (or equivalent) to max_safe_size so you leave headroom for fees + price slippage.

Why not just trust client.get_portfolio()?

You can — but ensure_can_trade() bundles three things you’d otherwise re-implement in every skill:
  1. Collateral-agnostic balance selection: reads the correct token for the active exchange_version (pUSD on V2, USDC.e on V1).
  2. Failure-mode distinction: returns a stable reason string across RPC outages, missing wallets, and genuine zero-balance cases.
  3. Safety buffer math: the max_safe_size return is already clamped for fees and slippage — no off-by-one between skills.

Sell pre-flight pattern

ensure_can_trade() covers buys (do I have collateral?). For sells, the equivalent pre-flight is “re-fetch positions immediately before each attempt.” This catches the most common sell-side bug: a stop-loss / take-profit loop that fires every N seconds with a cached shares value, then submits stale orders after a previous sell already filled. Polymarket rejects the second attempt with Insufficient shares to sell.

Pattern

def safe_sell(client, market_id, side, max_shares=None):
    positions = client.get_positions(venue="polymarket")
    pos = next((p for p in positions if p.market_id == market_id), None)
    if not pos:
        return None  # position cleared (sold / redeemed / resolved)
    fresh_shares = pos.shares_yes if side == "yes" else pos.shares_no
    if fresh_shares < 5.0:  # Polymarket's 5-share minimum
        return None
    sell_size = min(fresh_shares, max_shares) if max_shares else fresh_shares
    return client.trade(market_id=market_id, side=side, action="sell", shares=sell_size)
The reference polymarket-weather-trader skill uses this pattern in its exit logic.

Server-side backstop

If a stale sell slips through, Simmer’s server pre-checks the on-chain position before submitting and fails fast with a diagnostic message instead of round-tripping to Polymarket. This is a backstop, not a substitute: refreshing positions in your loop avoids the network round-trip entirely and lets your skill skip cleanly without writing a failure row.

When to call it

For passive monitors / GTC strategies — at the top of every monitor cycle. For active traders — once per cycle is plenty; positions don’t change between sell decisions within the same run unless your skill is multi-threaded.