Skip to main content
A strategy is a JSON object with three condition trees — entry, take_profit, stop_loss — plus an execution_params block. The same shape powers the visual Strategy Builder, the backtest engine, and the paper trader.

Top-level shape

{
  "name": "Endgame BTC 5m",
  "ticker": "BTC",
  "event_type": "5m",
  "trade_logic": "always_down",
  "entry": { ... condition tree ... },
  "take_profit": { ... condition tree ... },
  "stop_loss":  { ... condition tree ... },
  "execution_params": {
    "size_usd": 7.5,
    "fill_mode": "walk_book",
    "max_fill_price": 0.985,
    "max_trades_per_market": 1
  }
}

Condition tree

A tree node is either a group (AND / OR over children) or a leaf (a typed primitive condition).

Group node

{
  "op": "AND",
  "children": [ <node>, <node>, ... ]
}
op is "AND" or "OR". Children can themselves be groups, so trees nest arbitrarily.

Leaf node primitives

{ "type": "token_price",            "side": "yes", "op": "<=", "value": 0.985 }
{ "type": "spread",                 "side": "yes", "op": ">=", "value": 0.02  }
{ "type": "time_to_resolution_s",                  "op": "<=", "value": 120   }
{ "type": "time_since_market_open_s",              "op": ">=", "value": 30    }
{ "type": "coin_move_since_open_usd",              "op": "<=", "value": -50   }
{ "type": "coin_move_since_open_pct",              "op": "<=", "value": -0.15 }
{ "type": "coin_price_volatility",   "window_sec": 60, "op": ">=", "value": 50 }
{ "type": "token_price_volatility",  "side": "yes", "window_sec": 60, "op": ">=", "value": 0.01 }
FieldNotes
typeRequired. One of the eight primitives above.
side"yes" or "no" — only for token-side primitives.
op">=", "<=", ">", "<", "==", "!=", "crosses_above", "crosses_below"
valueComparison RHS. Number.
window_secOnly for volatility primitives. Lookback.
crosses_above / crosses_below use the immediately-preceding snapshot — true if the threshold was crossed in this step.

Empty trees

An empty tree is equivalent to a constant:
  • entry: {op: "AND", children: []} → always fires on first fillable snapshot.
  • take_profit / stop_loss: {op: "AND", children: []} → never fires via that path; trade exits only at resolution.

Resolution risk modeling

If a market resolves to an outcome other than Up / Down (UMA dispute, N/A), the position can resolve to a refund rather than 0/0 / 1. The engine models this via the resolution_risk block in the backtest response — each trade’s pnl is the engine’s best estimate given Polymarket’s actual outcome.

Worked example — endgame

The “endgame sniper” strategy our public audit tracks looks like:
{
  "ticker": "BTC",
  "event_type": "5m",
  "trade_logic": "always_down",
  "entry": {
    "op": "AND",
    "children": [
      { "type": "time_to_resolution_s",       "op": "<=", "value": 120  },
      { "type": "token_price", "side": "no",   "op": "<=", "value": 0.985 },
      { "type": "coin_move_since_open_pct",    "op": "<=", "value": -0.15 }
    ]
  },
  "take_profit": { "op": "AND", "children": [] },
  "stop_loss":   { "op": "AND", "children": [] },
  "execution_params": {
    "size_usd": 7.5,
    "fill_mode": "walk_book",
    "max_fill_price": 0.985,
    "max_trades_per_market": 1
  }
}
POST to /v1/backtest; the engine replays it across the most-recent N markets and returns realised PnL.