EventTrader
AI-Native Trading
PAPER
Menu
Dark Mode
Plain English Mode
PAPER TRADING MODE — Enable real trading on your Account page
Back

Overnight NEXUS Lab Loop

How an agent works the Backtest Lab with NEXUS: rank what is actually backtested by risk-adjusted return, ask NEXUS to refute the favourite, keep only what survives. Bounded, rate-limit aware, repeatable.

The one rule. Give the loop a goal, a data source, an acceptance test and a backoff. Never an open-ended "work all night". A loop without an acceptance test does not finish, it just spins.

0Connect your agent

Everything below is one endpoint: https://cymetica.com/mcp/v1 (JSON-RPC 2.0, methods initialize, tools/list, tools/call). The ground-truth tools need no key. Deep research needs a platform key — create one at /account and send it as X-API-Key: evt_…. Full onboarding: /agent-onboarding, tool catalog: /api-docs.

POST https://cymetica.com/mcp/v1 Content-Type: application/json X-API-Key: evt_YOUR_KEY # only for keyed tools {"jsonrpc":"2.0","id":1,"method":"tools/call", "params":{"name":"get_strategy_returns","arguments":{"strategy":"<slug>"}}}

1Ground truth first (no key)

Start from what carries real backtest rows, not from an idea. Three calls, in this order:

ToolWhat it gives you
list_backtest_surfacesThe five backtest surfaces, the question each answers, its API and auth. Pick the surface that matches your goal.
get_backtest_modelsResearch-backtest models (with their tradeability caveats), themes, and the strategy types Backtest Labs accepts.
get_strategy_returnsThe live run window and returns for one strategy slug: Sharpe, return, win rate, max drawdown, trade count. Find slugs with search_ontology / resolve_ontology_concept.

Rank risk-adjusted, not by raw return

Sort Sharpe first, then max drawdown, then trade count (a high Sharpe on 20 trades is noise). A strategy with a modest return and a tiny drawdown outranks a bigger return that gave half of it back. Pair a long book with a short book that has the lowest correlated drawdown — that is the pod-shop setup worth stacking. Discard anything whose run window is shorter than your acceptance test needs.

Backtests are evidence, not forecasts. Every figure here is a historical backtest over its stated window. Nothing on this page promises a return.

2Deep research (key)

NEXUS is the lead researcher in the loop. Open one investigation per candidate, then collect the answer:

  • ask_nexus_deep {"question": "…"} → returns a job (job_id).
  • get_deep_answer {"job_id": "…"} → the answer when the job is done.

This lane runs at reduced capacity. When it is busy it fails fast with retryable: true and retry_after_s. Your agent must honour that value and back off — never tight-loop the call. A good question is adversarial: "Here is the evidence for strategy X (Sharpe, drawdown, window). Refute it. What regime breaks it?"

3Each cycle

  1. Rank the surfaces (step 1) → take the top-N plus one consensus favourite.
  2. Refute the favourite: ask NEXUS to argue against it with the evidence in hand (step 2).
  3. Keep only the strategies that survive the refutation; log the reason each one was dropped.
  4. Test the survivors against your acceptance test (for example: Sharpe ≥ your floor over ≥ N trades and a drawdown under your cap, on the latest window).
  5. Stop or sleep. Acceptance met → stop and report. Not met → sleep the larger of your cycle interval and any retry_after_s you were given, then run again.
import time, httpx MCP = "https://cymetica.com/mcp/v1" KEY = {"X-API-Key": "evt_YOUR_KEY"} def call(name, args, headers=None): r = httpx.post(MCP, json={"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": name, "arguments": args}}, headers=headers or {}, timeout=60) return r.json().get("result", {}) def cycle(candidates, floor_sharpe=2.0, max_dd=0.05, min_trades=100): ranked = sorted((call("get_strategy_returns", {"strategy": s}) for s in candidates), key=lambda r: (-(r.get("sharpe") or 0), r.get("max_drawdown") or 1)) favourite = ranked[0] job = call("ask_nexus_deep", {"question": f"Refute this strategy with its evidence: {favourite}"}, KEY) if job.get("retryable"): time.sleep(job.get("retry_after_s", 30)) # honour the backoff, then retry next cycle return None answer = call("get_deep_answer", {"job_id": job["job_id"]}, KEY) survivors = [r for r in ranked if (r.get("sharpe") or 0) >= floor_sharpe and (r.get("max_drawdown") or 1) <= max_dd and (r.get("trades") or 0) >= min_trades] return {"survivors": survivors, "refutation": answer} # goal + data source + acceptance test + backoff — never open-ended for _ in range(12): # bounded: at most 12 cycles result = cycle(["strategy-slug-a", "strategy-slug-b"]) if result and result["survivors"]: break # acceptance met — stop and report time.sleep(1800)

4From survivors to a live run

A survivor is a research result. Before any capital touches it: run it on the Backtest Lab surface that matches its type, then paper it through the same path a customer uses. Read the numbers with their window (get_strategy_returns returns the period behind every Sharpe) and re-rank after every new window. The Movers Tournament and Potential Movers screens are the same discipline applied to single names: /events/universe/movers.

Where the material lives