Compare rules
MCP tool: compare
Use when testing genuinely different ideas rather than one idea at different thresholds; that is what sweep is for.
Each variant beyond the first is charged against your rate limit.
Body parameters
variantsobject[]required2 to 10 rules to score, each { label, criteria }. label is 1 to 60 characters.
labelstringrequiredName shown against this variant's results.
criteriaobjectrequiredThe rule for this variant. Same shape as everywhere else.
platformsstring[]Venues to include: "polymarket", "kalshi". Omit for both, which is usually right.
categoriesstring[]Market categories: "sports", "politics", "crypto", "finance", "culture". Omit for all.
sportsLeaguesstring[]Leagues, e.g. ["NBA", "NFL"]. Only applies to sports markets. Max 20.
minAmountUsdnumberMinimum trade size in USD. Typical values are 10000 to 50000.
maxAmountUsdnumberMaximum trade size in USD. Must be at least minAmountUsd when both are set.
priceMinnumberMinimum entry price, 0 to 1. 0.75 and up is heavy favourites.
priceMaxnumberMaximum entry price, 0 to 1. 0.20 and below is longshots. Must be at least priceMin when both are set.
eventTypesstring[]How the trade changed the trader's position: "open", "add", "flip", "trim", "close", "unknown". Entries only is ["open", "add", "flip"], which is the usual choice. Trims and closes are exits and never settle, so they cannot be scored.
directionsstring[]Trade direction: "buy_yes", "buy_no", "sell_yes", "sell_no". Only buys are ever scored; sells never resolve.
keywordsstring[]Case-insensitive substrings matched against the market name. Any one matching is enough. Max 10.
stakenumberdefault: 100Hypothetical stake per trade in USD, 1 to 1000000. Return percentage is stake-invariant.
sinceDaysintegerdefault: 90Lookback window in days, 1 to 365.
Response
A backtest result counts every trade the rule matched in the window. settled trades are scored as wins or losses; open trades are counted but not scored. winRate and returnPct are null until something settles.
curl -X POST https://api.rivo.markets/v1/backtest/compare \
-H "Authorization: Bearer rivo_live_..." \
-H "Content-Type: application/json" \
-d '{
"variants": [
{
"label": "Longshots",
"criteria": {
"priceMax": 0.2,
"minAmountUsd": 10000
}
},
{
"label": "Favourites",
"criteria": {
"priceMin": 0.75,
"minAmountUsd": 10000
}
}
]
}'import requests
r = requests.post(
"https://api.rivo.markets/v1/backtest/compare",
headers={"Authorization": "Bearer rivo_live_..."},
json={
"variants": [
{
"label": "Longshots",
"criteria": {
"priceMax": 0.2,
"minAmountUsd": 10000,
},
},
{
"label": "Favourites",
"criteria": {
"priceMin": 0.75,
"minAmountUsd": 10000,
},
},
],
},
)
print(r.json()["data"])const res = await fetch("https://api.rivo.markets/v1/backtest/compare", {
method: "POST",
headers: {
Authorization: "Bearer rivo_live_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
variants: [
{
label: "Longshots",
criteria: {
priceMax: 0.2,
minAmountUsd: 10000,
},
},
{
label: "Favourites",
criteria: {
priceMin: 0.75,
minAmountUsd: 10000,
},
},
],
}),
});
const { data } = await res.json();{
"success": true,
"data": {
"results": [
{
"label": "Longshots",
"matched": 489,
"settled": 449,
"wins": 105,
"losses": 344,
"open": 40,
"winRate": 0.234,
"stakeUsd": 100,
"stakedUsd": 44900,
"pnlUsd": 25910,
"returnPct": 57.7,
"window": { "sinceDays": 90, "stake": 100 }
},
{
"label": "Favourites",
"matched": 1204,
"settled": 1121,
"wins": 918,
"losses": 203,
"open": 83,
"winRate": 0.819,
"stakeUsd": 100,
"stakedUsd": 112100,
"pnlUsd": 3363,
"returnPct": 3,
"window": { "sinceDays": 90, "stake": 100 }
}
]
}
}