A strategy scanner your AI agent can call. Don't backtest one strategy — sweep its entire parameter space in a single call, and read the population-level statistics that tell you whether the top result is skill or luck: a deflated Sharpe threshold and the number of trials across every strategy evaluated, so a winner that was cherry-picked from thousands of variants is exposed instead of celebrated.
Single backtests are here too (each returns a Probabilistic Sharpe Ratio), but they're the starting point, not the product. The edge is the scan: the same honest engine behind the no-code scanner, now callable one line at a time from an agent — up to a hundred million backtests in a single sweep.
The backtest engine runs entirely on Rulyfi's servers. The @rulyfi/mcp package is a thin API client and candle-fetch orchestrator — it contains no strategy engine. The strategy object format is documented in full at strategy-schema.md.
Create a key on your account page: rulyfi.com → Account → API keys. It requires a paid plan (Plus or higher). Keys look like rk_live_… and are shown once — copy it when you create it.
Point any MCP client at the package with your key.
claude mcp add rulyfi --env RULYFI_API_KEY=rk_live_your_key -- npx -y @rulyfi/mcpFor Claude Desktop (Settings → Developer → Edit Config) or Cursor (.cursor/mcp.json), add:
{
"mcpServers": {
"rulyfi": {
"command": "npx",
"args": ["-y", "@rulyfi/mcp"],
"env": { "RULYFI_API_KEY": "rk_live_your_key" }
}
}
}The server exposes seven tools: list_indicators, list_symbols, validate_strategy, run_backtest, run_scan, get_scan_results, and download_export. A typical porting loop is: have the agent translate a Python/TradingView strategy into the Rulyfi format, call validate_strategy to fix it error-by-error, then run_backtest to check it.
Base URL https://api.rulyfi.com/v1. Authenticate with a bearer header on every request:
Authorization: Bearer rk_live_<your key>| Endpoint | What it does | Billing |
|---|---|---|
| GET /v1/catalog/indicators | The 103-indicator catalog: type, params, outputs, description. | Free |
| GET /v1/catalog/symbols | Supported exchanges, timeframes, and directions. | Free |
| POST /v1/validate | Validate a strategy or scan — every error with a field path and a correct example. | Free |
| POST /v1/candles | Get a candle_ref (presigned upload, or a cache hit). | Free |
| POST /v1/backtests | Run one backtest (spot-market only). Returns summary statistics + PSR. | 1 call |
| POST /v1/scans | Create a card × grid scan; returns candle requirements. | By grid size |
| POST /v1/scans/{id}/start | Start a created scan after candles are uploaded. | — |
| GET /v1/jobs/{id} | Status, top-K leaderboard (PSR/DSR/num_trials), population stats. | Free |
| GET /v1/jobs/{id}/export | Full-population parquet export (presigned, 6h). | Free |
Every error uses one envelope. code is a stable machine-readable string; hint is present when there is a useful next step (a 429 nudges you toward run_scan).
{
"error": {
"code": "rate_limited",
"message": "Per-key rate limit exceeded (60 calls/min).",
"hint": "Testing a parameter grid? Use run_scan — it tests a whole grid far more cheaply than thousands of single backtests."
}
}Codes: invalid_request, strategy_invalid, paid_tier_required (403), rate_limited / daily_cap_exceeded (429), insufficient_credits (402), candle_ref_not_found, candles_invalid, range_invalid, not_found, method_not_allowed, internal_error, unavailable. A missing, invalid, or revoked key returns 401.
Backtesting uses your Rulyfi credits at the same rate as the scanner: 1,000 API backtest calls = 1 credit, billed as a prepaid 1,000-call block, so normal use of tens or hundreds of calls per session is effectively free. Scans are billed by the size of the parameter grid, exactly like the web scanner.
| Access | Paid tier only (Plus / Pro / Premium) |
| Backtest rate (per key) | 60 calls / minute |
| Daily backtest calls (per key) | Plus 5,000 · Pro 20,000 · Premium 50,000 |
| Concurrent scans (per key) | 2 |
| Billing | 1,000 backtest calls = 1 credit (prepaid 1,000-call block) |
When you hit a rate or daily limit the error nudges you toward run_scan, which tests a whole parameter grid far more cheaply than thousands of single backtests.
The server never fetches candles itself — you provide them. Because agents typically hold the data fixed and vary only the strategy, you upload the candles once and reference them by candle_ref on subsequent calls. Candle references are scoped to your account. Small ranges (up to 5,000 bars) can be sent inline in the candles field instead.
# 1. Ask for a candle_ref for the market + range you want to test.
POST /v1/candles
{ "exchange": "binance", "symbol": "BTCUSDT", "timeframe": "1h",
"start_ts": 1704067200000, "end_ts": 1719792000000 }
# -> cache miss: you get an upload target.
{ "candle_ref": "…", "cached": false,
"upload_url": "https://…", "wire_format": "v4-gzip" }
# 2. PUT the gzip candle wire to upload_url (the MCP package does this for you).
# 3. Backtest — reference the candles by candle_ref. Change the strategy, reuse
# the same candle_ref; no re-fetch, no re-upload.
POST /v1/backtests
{ "strategy": { … }, "candle_ref": "…", "walk_forward": false }The MCP run_backtest tool does all of this in one call: it fetches candles locally (cached on your machine), uploads them once, and runs the backtest.
Re-running an identical scan is served from the result cache for free — the same grid over the same symbols, range, and settings returns the cached leaderboard at zero credits. There is one consequence to know: a cache hit returns only the ranked rows, not a freshly computed full-population export, so a cached scan produces no parquet file. When you specifically need the full export, pass force_fresh: true on POST /v1/scans (or force_fresh in run_scan). That skips the cache and recomputes — which re-charges credits, so it is always an explicit opt-in, never automatic.
Backtest results are based on past market data and do not guarantee future returns. This is not investment advice.