_ registry / mcp http-sse · checked 1h ago

emidlabs-backtest-mcp

https://mcp.backtest.emidlabs.com

Registry code: fc5e1d6993b1bef9

api record

Tools for running EmidLabs strategy backtests against historical crypto OHLCV data. Read the strategy-dsl-spec resource first for the exact JSON shape submit_backtest expects. list_available_assets is optional — only call it to explore what's available or to recover from an unknown-asset error, not before every submit_backtest.

endpoint
https://mcp.backtest.emidlabs.com/mcp
protocol
http-sse ·2025-06-18
authentication
none observed
public key
none — nobody has proven they own this listing
karma
0 · newcomer
reachable
live
uptime, 30 days
100%

90 days 100%· all time 100%

latency
706ms

last good check

priced tools
0

of 9 tools

_ answered our checks, 90 days 1 checks · signed record
  • unknown → live
_ used through this hub 30 days

The one measurement on this page that an operator cannot produce by editing a file on its own server: somebody else chose it, and paid to. Read the accounts before the calls — volume from one account is one relationship, and calling yourself is the cheap half. Both are what the ranking is built from, printed so the order can be checked rather than taken on trust.

accounts
0

distinct, expensive to fake

calls served
0

successful, last 30 days

_ what it can do 9 tools
9 auth-required 9 of 9 classified

Price is per tool, not per server. An agent whose handshake is open can hold tools that demand a key or a payment, and one figure for the whole agent sends callers into a wall.

  • submit_backtest_batch auth-required never probed

    Submits ONE strategy against MANY asset pairs in a single call — the same backtest you'd get from calling submit_backtest once per asset, without needing dozens of round trips. Returns immediately with a batchId (pass it to get_backtest_batch_results to fetch every asset's outcome, paginated) plus the per-asset id/status assigned right away. Best-effort: one bad asset pair (unknown, or its date range outside coverage) shows up with an error in that item only — every other asset in the batch is unaffected. Optionally accepts confirmationSources, applied identically to every asset in the batch — see submit_backtest's own confirmationSources argument for the full semantics.

    mcp-tool

    {
      "type": "object",
      "properties": {
        "finalDate": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "ISO date string, e.g. \"2025-06-01\"."
        },
        "assetPairs": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": [
              "string",
              "null"
            ]
          },
          "default": null,
          "description": "e.g. [\"BTC-USDC\", \"ETH-USDC\", \"SOL-USDC\"]. Every asset gets the exact same strategySnapshotJson and date range."
        },
        "initialDate": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "ISO date string, e.g. \"2025-01-01\"."
        },
        "backtestApiKey": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "Your EmidLabs backtest API key (created in the Console). Not needed if this connector was added with a static 'x-api-key' header."
        },
        "backtestBaseUrl": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "Defaults to the public production API. Override only for self-hosted/staging use."
        },
        "confirmationSources": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "Optional. A JSON-encoded STRING (not a native array/object — pass it exactly like a quoted string value), containing the same shape as submit_backtest's own confirmationSources: [{\"sourceId\":\"<a submit_confirmation_source id>\",\"signalType\":\"entry\",\"validityWindow\":{\"count\":1}}]. Applied identically to every asset pair in this batch (one shared gate, not one per asset). Passed as a raw JSON string rather than a native array because this tool already has one array parameter (assetPairs) — a second one crashes the MCP SDK's own parameter marshaller. A sourceId that doesn't exist, isn't Completed, or belongs to another account fails that item only (same best-effort semantics as an unknown asset pair), not the whole batch."
        },
        "strategySnapshotJson": {
          "type": [
            "object",
            "null"
          ],
          "default": null,
          "properties": {
            "score": {
              "type": [
                "object",
                "null"
              ],
              "description": "Integer weight per condition name (keys must match \"conditions\"). The engine sums the weights of every true condition on a candle; compare the total against a threshold in \"decision.entry\" (e.g. \"score >= 50\").",
              "additionalProperties": {
                "type": "integer"
              }
            },
            "inputs": {
              "type": [
                "object",
                "null"
              ],
              "description": "Named computed values, one JSON-string expression each, evaluated once per candle. Raw fields: close, open, high, low, volume. Built-in functions (case-sensitive, lowercase — no others exist, e.g. avg()/SMA() uppercase/close[N] bracket indexing are NOT supported): ema(series,period), sma(series,period), rsi(series,period), atr(period), adx(period), adxPlusDi(period), adxMinusDi(period), crossUp(a,b), crossDown(a,b), highest(series,n), lowest(series,n), change(series), shift(series,n) [look-back only, NaN before enough history], any(boolSeries,n), all(boolSeries,n), count(boolSeries,n) [over the n candles before the current one], swingHigh(series,confirmBars), swingLow(series,confirmBars) [confirmed N-bar swing point, true confirmBars candles AFTER the actual peak/trough — never at the peak itself, so it can't repaint between backtest and live], body(), range(), upperWick(), lowerWick(), isBullish(), isBearish(), abs(x), min(a,b), max(a,b). There is no volumeSma()/volumeSpike() — volume is a plain series like close/open/high/low, so use sma(volume,period) and volume > sma(volume,period) * multiplier instead. Candlestick patterns (all no-arg, boolean, read only closed-candle OHLC, same in backtest and live): single-candle hammer(), shootingStar(), doji(), bullishMarubozu(), bearishMarubozu(), spinningTop(), dragonflyDoji(), gravestoneDoji(), longLeggedDoji(); two-candle bullishEngulfing(), bearishEngulfing(), piercingLine(), darkCloudCover(), bullishHarami(), bearishHarami(), haramiCross(), tweezerTop(), tweezerBottom(); three-plus-candle morningStar(), eveningStar(), threeWhiteSoldiers(), threeBlackCrows(), threeInsideUp(), threeInsideDown(), threeOutsideUp(), threeOutsideDown(), risingThreeMethods(), fallingThreeMethods(). Caveat: hammer()/shootingStar() are shape-only, no prior-trend check (same shape is Hammer in a downtrend but Hanging Man in an uptrend, and vice versa for shootingStar/Inverted Hammer) — pair with a trend/momentum condition rather than using the shape alone. An input may only reference inputs defined above it (no forward/circular references). Example: {\"emaFast\":\"ema(close, 9)\",\"emaSlow\":\"ema(close, 21)\"}",
              "additionalProperties": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "decision": {
              "type": [
                "object",
                "null"
              ],
              "properties": {
                "exit": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Optional boolean expression; closes an open position when true, independent of riskManagement. If a candle's stop-loss/take-profit and \"exit\" would both trigger, the stop-loss/take-profit wins and exit is not evaluated for that position that candle. If omitted, positions only close via stop-loss/take-profit."
                },
                "entry": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Boolean expression evaluated per candle; a trade opens when true."
                }
              }
            },
            "conditions": {
              "type": [
                "object",
                "null"
              ],
              "description": "Named boolean expressions over inputs/market data. Operators: > < >= <= == != AND OR. Example: {\"trendUp\":\"emaFast > emaSlow AND adx14 > 20\"}",
              "additionalProperties": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "configuration": {
              "type": [
                "object",
                "null"
              ],
              "properties": {
                "timezone": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "IANA timezone id (e.g. \"America/Sao_Paulo\") used to localize the DSL's hour()/minute()/dayOfWeek()/isWeekend() functions, for time-of-day or weekday gating conditions. Omit/null defaults to \"America/Sao_Paulo\"."
                },
                "direction": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "\"long\" or \"short\" — picks which side the WHOLE backtest trades, not a mix. Only affects stop-loss/take-profit price placement and the profit/loss sign (mirrored for short: stop above entry, take below entry, profit when price falls) — conditions/score/decision keep the exact same meaning either way. Defaults to \"long\"."
                },
                "timeframe": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "One of \"5M\", \"15M\", \"30M\", \"1H\", \"2H\", \"4H\", \"1D\"."
                },
                "exitFeePct": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Simulated exchange fee on exit, as a percent (e.g. 0.1 = 0.1%). Same semantics as EntryFeePct — real exchanges can charge different maker/taker rates per leg, so this is independent, not assumed equal."
                },
                "warmupBars": {
                  "type": "integer",
                  "description": "Leading candles excluded from trading while indicators stabilize. Default 0."
                },
                "entryFeePct": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Simulated exchange fee on entry, as a percent (e.g. 0.1 = 0.1%). Omit/null = 0 (no fee, backward compatible). Subtracted from every trade's pnlR/pnlPct so results are net-of-fee by construction. 0-5 range. See the pnlR/expectancyR field descriptions on get_backtest_result for the R-vs-% caveat once a fee is set."
                },
                "maxOpenPositions": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "Caps concurrent open trades. Omit/null for unlimited (default)."
                }
              }
            },
            "riskManagement": {
              "type": [
                "object",
                "null"
              ],
              "properties": {
                "stopLoss": {
                  "description": "Stop-loss policy. Exactly one of: {\"type\":\"percent\",\"percent\":<number, e.g. 1.0 for 1%>} or {\"type\":\"atr\",\"period\":<int>,\"multiplier\":<number>}. \"type\" is required; field order does not matter."
                },
                "takeProfit": {
                  "description": "Take-profit policy. Exactly one of: {\"type\":\"riskReward\",\"multiple\":<number>} (distance = stop-loss distance * multiple) or {\"type\":\"percent\",\"percent\":<number>}. \"type\" is required; field order does not matter."
                }
              },
              "description": "Configures stop-loss/take-profit. Omit entirely for the default (1% stop-loss, 1:3 risk-reward take-profit) — see stopLoss/takeProfit below for the full shape of each."
            }
          },
          "description": "Same Strategy DSL object submit_backtest takes — see that tool's description for the full shape."
        }
      }
    }
    arguments 196 lines
  • get_backtest_batch_results auth-required 1h ago

    Fetches every asset's outcome from a submit_backtest_batch call, paginated. By default (waitForCompletion: true) polls internally until every item in the batch is done — a cheap check, not one that pages through everything — so one call returns the finished page. Each item has the same aggregate metrics and recency fields (recentTradeCount/recentAvgPnlR/recentOutcomes) get_backtest_result returns for a single backtest, plus assetPair/status/error, so the single-result and batch-result shapes never drift apart. totalCount/completedCount/failedCount/pendingCount come back on every page, not just the last, so you can tell the batch is done from a single pageSize=1 call.

    mcp-tool

    {
      "type": "object",
      "properties": {
        "page": {
          "type": "integer",
          "default": 1,
          "description": "1-based page number. Default 1."
        },
        "batchId": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "The batchId returned by submit_backtest_batch."
        },
        "pageSize": {
          "type": "integer",
          "default": 20,
          "description": "Items per page, 1-100. Default 20."
        },
        "pollTimeoutMs": {
          "type": "integer",
          "default": 300000,
          "description": "Defaults to 300000 (5 minutes) — a batch's slowest item determines the total wait, so this is higher than get_backtest_result's default."
        },
        "backtestApiKey": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "Your EmidLabs backtest API key. Not needed if this connector was added with a static 'x-api-key' header."
        },
        "backtestBaseUrl": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "Defaults to the public production API."
        },
        "waitForCompletion": {
          "type": "boolean",
          "default": true,
          "description": "If true (default), polls internally until every item in the batch is done or pollTimeoutMs elapses."
        }
      }
    }
    arguments 49 lines
  • get_backtest_result auth-required 1h ago

    Fetches a submitted backtest by id. By default (waitForCompletion: true) polls internally until it finishes, so one call returns one final answer — no need to poll from the caller's side. Only aggregate metrics are returned here, no trade-by-trade detail — call get_backtest_trades for that (paginated, sortable). Fetching many results from the same submit_backtest_batch call? Use get_backtest_batch_results instead.

    mcp-tool

    {
      "type": "object",
      "properties": {
        "id": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "The id returned by submit_backtest."
        },
        "pollTimeoutMs": {
          "type": "integer",
          "default": 120000,
          "description": "Defaults to 120000 (2 minutes)."
        },
        "backtestApiKey": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "Your EmidLabs backtest API key. Not needed if this connector was added with a static 'x-api-key' header."
        },
        "backtestBaseUrl": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "Defaults to the public production API."
        },
        "waitForCompletion": {
          "type": "boolean",
          "default": true,
          "description": "If true (default), polls internally until the backtest finishes or pollTimeoutMs elapses."
        }
      }
    }
    arguments 39 lines
  • get_backtest_trades auth-required 1h ago

    Fetches the closed-trade list for a completed backtest, paginated and sortable — the trade-by-trade detail get_backtest_result deliberately omits. Only closed trades ever appear; a position still open when the backtest's date range ends isn't represented here or anywhere else. Example: sortBy="exitTime", sortDirection="desc", pageSize=5 for the most recently closed trades.

    mcp-tool

    {
      "type": "object",
      "properties": {
        "id": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "The id returned by submit_backtest."
        },
        "page": {
          "type": "integer",
          "default": 1,
          "description": "1-based page number. Default 1."
        },
        "sortBy": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "One of: number, pnlR, pnlPct, entryTime, exitTime. Defaults to number (closing order)."
        },
        "pageSize": {
          "type": "integer",
          "default": 20,
          "description": "Trades per page, 1-100. Default 20."
        },
        "sortDirection": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "\"asc\" or \"desc\". Defaults to asc."
        },
        "backtestApiKey": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "Your EmidLabs backtest API key. Not needed if this connector was added with a static 'x-api-key' header."
        },
        "backtestBaseUrl": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "Defaults to the public production API."
        }
      }
    }
    arguments 55 lines
  • submit_confirmation_source auth-required never probed

    Runs a strategy against historical data purely to produce a signal timeline used to confirm OTHER backtests — not a backtest itself. Walks the same candles and evaluates the same DSL as submit_backtest, but never simulates a position: no trade, no PnL, no WinRate/drawdown, none of that applies here, because this strategy is never meant to be traded on its own. Returns immediately with an id and status — call get_confirmation_source to check completion, then pass that id as a confirmationSources sourceId in submit_backtest (e.g. only count an XRP entry once a BTC-neutral confirmation source agrees). Costs the same capacity as a regular backtest of the same size — the compute is identical, it just skips trade simulation.

    mcp-tool

    {
      "type": "object",
      "properties": {
        "assetPair": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "e.g. \"BTC-USDC\"."
        },
        "finalDate": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "ISO date string, e.g. \"2025-06-01\"."
        },
        "initialDate": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "ISO date string, e.g. \"2025-01-01\"."
        },
        "backtestApiKey": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "Your EmidLabs backtest API key (created in the Console). Not needed if this connector was added with a static 'x-api-key' header."
        },
        "backtestBaseUrl": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "Defaults to the public production API. Override only for self-hosted/staging use."
        },
        "strategySnapshotJson": {
          "type": [
            "object",
            "null"
          ],
          "default": null,
          "properties": {
            "score": {
              "type": [
                "object",
                "null"
              ],
              "description": "Integer weight per condition name (keys must match \"conditions\"). The engine sums the weights of every true condition on a candle; compare the total against a threshold in \"decision.entry\" (e.g. \"score >= 50\").",
              "additionalProperties": {
                "type": "integer"
              }
            },
            "inputs": {
              "type": [
                "object",
                "null"
              ],
              "description": "Named computed values, one JSON-string expression each, evaluated once per candle. Raw fields: close, open, high, low, volume. Built-in functions (case-sensitive, lowercase — no others exist, e.g. avg()/SMA() uppercase/close[N] bracket indexing are NOT supported): ema(series,period), sma(series,period), rsi(series,period), atr(period), adx(period), adxPlusDi(period), adxMinusDi(period), crossUp(a,b), crossDown(a,b), highest(series,n), lowest(series,n), change(series), shift(series,n) [look-back only, NaN before enough history], any(boolSeries,n), all(boolSeries,n), count(boolSeries,n) [over the n candles before the current one], swingHigh(series,confirmBars), swingLow(series,confirmBars) [confirmed N-bar swing point, true confirmBars candles AFTER the actual peak/trough — never at the peak itself, so it can't repaint between backtest and live], body(), range(), upperWick(), lowerWick(), isBullish(), isBearish(), abs(x), min(a,b), max(a,b). There is no volumeSma()/volumeSpike() — volume is a plain series like close/open/high/low, so use sma(volume,period) and volume > sma(volume,period) * multiplier instead. Candlestick patterns (all no-arg, boolean, read only closed-candle OHLC, same in backtest and live): single-candle hammer(), shootingStar(), doji(), bullishMarubozu(), bearishMarubozu(), spinningTop(), dragonflyDoji(), gravestoneDoji(), longLeggedDoji(); two-candle bullishEngulfing(), bearishEngulfing(), piercingLine(), darkCloudCover(), bullishHarami(), bearishHarami(), haramiCross(), tweezerTop(), tweezerBottom(); three-plus-candle morningStar(), eveningStar(), threeWhiteSoldiers(), threeBlackCrows(), threeInsideUp(), threeInsideDown(), threeOutsideUp(), threeOutsideDown(), risingThreeMethods(), fallingThreeMethods(). Caveat: hammer()/shootingStar() are shape-only, no prior-trend check (same shape is Hammer in a downtrend but Hanging Man in an uptrend, and vice versa for shootingStar/Inverted Hammer) — pair with a trend/momentum condition rather than using the shape alone. An input may only reference inputs defined above it (no forward/circular references). Example: {\"emaFast\":\"ema(close, 9)\",\"emaSlow\":\"ema(close, 21)\"}",
              "additionalProperties": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "decision": {
              "type": [
                "object",
                "null"
              ],
              "properties": {
                "exit": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Optional boolean expression; closes an open position when true, independent of riskManagement. If a candle's stop-loss/take-profit and \"exit\" would both trigger, the stop-loss/take-profit wins and exit is not evaluated for that position that candle. If omitted, positions only close via stop-loss/take-profit."
                },
                "entry": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Boolean expression evaluated per candle; a trade opens when true."
                }
              }
            },
            "conditions": {
              "type": [
                "object",
                "null"
              ],
              "description": "Named boolean expressions over inputs/market data. Operators: > < >= <= == != AND OR. Example: {\"trendUp\":\"emaFast > emaSlow AND adx14 > 20\"}",
              "additionalProperties": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "configuration": {
              "type": [
                "object",
                "null"
              ],
              "properties": {
                "timezone": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "IANA timezone id (e.g. \"America/Sao_Paulo\") used to localize the DSL's hour()/minute()/dayOfWeek()/isWeekend() functions, for time-of-day or weekday gating conditions. Omit/null defaults to \"America/Sao_Paulo\"."
                },
                "direction": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "\"long\" or \"short\" — picks which side the WHOLE backtest trades, not a mix. Only affects stop-loss/take-profit price placement and the profit/loss sign (mirrored for short: stop above entry, take below entry, profit when price falls) — conditions/score/decision keep the exact same meaning either way. Defaults to \"long\"."
                },
                "timeframe": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "One of \"5M\", \"15M\", \"30M\", \"1H\", \"2H\", \"4H\", \"1D\"."
                },
                "exitFeePct": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Simulated exchange fee on exit, as a percent (e.g. 0.1 = 0.1%). Same semantics as EntryFeePct — real exchanges can charge different maker/taker rates per leg, so this is independent, not assumed equal."
                },
                "warmupBars": {
                  "type": "integer",
                  "description": "Leading candles excluded from trading while indicators stabilize. Default 0."
                },
                "entryFeePct": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Simulated exchange fee on entry, as a percent (e.g. 0.1 = 0.1%). Omit/null = 0 (no fee, backward compatible). Subtracted from every trade's pnlR/pnlPct so results are net-of-fee by construction. 0-5 range. See the pnlR/expectancyR field descriptions on get_backtest_result for the R-vs-% caveat once a fee is set."
                },
                "maxOpenPositions": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "Caps concurrent open trades. Omit/null for unlimited (default)."
                }
              }
            },
            "riskManagement": {
              "type": [
                "object",
                "null"
              ],
              "properties": {
                "stopLoss": {
                  "description": "Stop-loss policy. Exactly one of: {\"type\":\"percent\",\"percent\":<number, e.g. 1.0 for 1%>} or {\"type\":\"atr\",\"period\":<int>,\"multiplier\":<number>}. \"type\" is required; field order does not matter."
                },
                "takeProfit": {
                  "description": "Take-profit policy. Exactly one of: {\"type\":\"riskReward\",\"multiple\":<number>} (distance = stop-loss distance * multiple) or {\"type\":\"percent\",\"percent\":<number>}. \"type\" is required; field order does not matter."
                }
              },
              "description": "Configures stop-loss/take-profit. Omit entirely for the default (1% stop-loss, 1:3 risk-reward take-profit) — see stopLoss/takeProfit below for the full shape of each."
            }
          },
          "description": "The Strategy DSL object — identical shape to submit_backtest's own. riskManagement is accepted but never used (no position is ever opened), so it's fine to omit."
        }
      }
    }
    arguments 182 lines
  • list_available_assets auth-required never probed

    Lists every asset pair with real historical data, each with its supported timeframes and the date range actually available. Optional to call — submit_backtest already returns a clear error (with the real available range) when an asset or date range doesn't have data, so this is for up-front exploration or for recovering from an "unknown asset" error, not a required step before every submit_backtest.

    mcp-tool

    {
      "type": "object",
      "properties": {
        "backtestApiKey": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "Your EmidLabs backtest API key. Not needed if this connector was added with a static 'x-api-key' header."
        },
        "backtestBaseUrl": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "Defaults to the public production API. Override only for self-hosted/staging use."
        }
      }
    }
    arguments 21 lines
  • submit_backtest auth-required never probed

    Submits a strategy for backtesting against historical OHLCV data. Returns immediately with an id and status — call get_backtest_result to fetch the outcome once it finishes. Testing the same strategy against many assets? Use submit_backtest_batch instead — one call per asset here adds up fast. Optionally accepts confirmationSources to only count a candidate Entry/Exit as a real trade once corroborated by a submit_confirmation_source result (multi-timeframe or cross-asset confirmation) — see the confirmationSources argument and submit_confirmation_source's own description.

    mcp-tool

    {
      "type": "object",
      "properties": {
        "assetPair": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "e.g. \"BTC-USDC\"."
        },
        "finalDate": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "ISO date string, e.g. \"2025-06-01\"."
        },
        "initialDate": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "ISO date string, e.g. \"2025-01-01\"."
        },
        "backtestApiKey": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "Your EmidLabs backtest API key (created in the Console). Not needed if this connector was added with a static 'x-api-key' header."
        },
        "backtestBaseUrl": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "Defaults to the public production API. Override only for self-hosted/staging use."
        },
        "confirmationSources": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "sourceId"
            ],
            "properties": {
              "sourceId": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The id returned by submit_confirmation_source (same account only) whose signal timeline this backtest's candidates must be corroborated by."
              },
              "signalType": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "\"entry\" or \"exit\" — which of the source's signal types counts as confirming. Defaults to \"entry\" when omitted."
              },
              "validityWindow": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "count": {
                    "type": "integer",
                    "description": "Number of candles of the source's own timeframe. Defaults to 1 when omitted."
                  }
                },
                "description": "An OBJECT, not a bare number — e.g. { \"count\": 5 }. Defaults to { \"count\": 1 } when omitted."
              }
            },
            "description": "One confirmation requirement: this backtest's own candidate Entry/Exit only counts as a trade once sourceId's own signal timeline is corroborated within validityWindow."
          },
          "default": null,
          "description": "Optional. Each entry names a submit_confirmation_source result (same account only, must already be Completed) that every candidate Entry/Exit must be corroborated by before it's simulated as a trade — an unconfirmed candidate is dropped before trade simulation, never appears in get_backtest_trades or affects PnlR/WinRate/etc. See ConfirmedSignalsCount/UnconfirmedSignalsCount on get_backtest_result. A sourceId that doesn't exist, isn't Completed, or belongs to another account fails this submission immediately (unlike live, this is synchronous/batch — letting it through would produce a confusing zero-trade result with no explanation)."
        },
        "strategySnapshotJson": {
          "type": [
            "object",
            "null"
          ],
          "default": null,
          "properties": {
            "score": {
              "type": [
                "object",
                "null"
              ],
              "description": "Integer weight per condition name (keys must match \"conditions\"). The engine sums the weights of every true condition on a candle; compare the total against a threshold in \"decision.entry\" (e.g. \"score >= 50\").",
              "additionalProperties": {
                "type": "integer"
              }
            },
            "inputs": {
              "type": [
                "object",
                "null"
              ],
              "description": "Named computed values, one JSON-string expression each, evaluated once per candle. Raw fields: close, open, high, low, volume. Built-in functions (case-sensitive, lowercase — no others exist, e.g. avg()/SMA() uppercase/close[N] bracket indexing are NOT supported): ema(series,period), sma(series,period), rsi(series,period), atr(period), adx(period), adxPlusDi(period), adxMinusDi(period), crossUp(a,b), crossDown(a,b), highest(series,n), lowest(series,n), change(series), shift(series,n) [look-back only, NaN before enough history], any(boolSeries,n), all(boolSeries,n), count(boolSeries,n) [over the n candles before the current one], swingHigh(series,confirmBars), swingLow(series,confirmBars) [confirmed N-bar swing point, true confirmBars candles AFTER the actual peak/trough — never at the peak itself, so it can't repaint between backtest and live], body(), range(), upperWick(), lowerWick(), isBullish(), isBearish(), abs(x), min(a,b), max(a,b). There is no volumeSma()/volumeSpike() — volume is a plain series like close/open/high/low, so use sma(volume,period) and volume > sma(volume,period) * multiplier instead. Candlestick patterns (all no-arg, boolean, read only closed-candle OHLC, same in backtest and live): single-candle hammer(), shootingStar(), doji(), bullishMarubozu(), bearishMarubozu(), spinningTop(), dragonflyDoji(), gravestoneDoji(), longLeggedDoji(); two-candle bullishEngulfing(), bearishEngulfing(), piercingLine(), darkCloudCover(), bullishHarami(), bearishHarami(), haramiCross(), tweezerTop(), tweezerBottom(); three-plus-candle morningStar(), eveningStar(), threeWhiteSoldiers(), threeBlackCrows(), threeInsideUp(), threeInsideDown(), threeOutsideUp(), threeOutsideDown(), risingThreeMethods(), fallingThreeMethods(). Caveat: hammer()/shootingStar() are shape-only, no prior-trend check (same shape is Hammer in a downtrend but Hanging Man in an uptrend, and vice versa for shootingStar/Inverted Hammer) — pair with a trend/momentum condition rather than using the shape alone. An input may only reference inputs defined above it (no forward/circular references). Example: {\"emaFast\":\"ema(close, 9)\",\"emaSlow\":\"ema(close, 21)\"}",
              "additionalProperties": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "decision": {
              "type": [
                "object",
                "null"
              ],
              "properties": {
                "exit": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Optional boolean expression; closes an open position when true, independent of riskManagement. If a candle's stop-loss/take-profit and \"exit\" would both trigger, the stop-loss/take-profit wins and exit is not evaluated for that position that candle. If omitted, positions only close via stop-loss/take-profit."
                },
                "entry": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Boolean expression evaluated per candle; a trade opens when true."
                }
              }
            },
            "conditions": {
              "type": [
                "object",
                "null"
              ],
              "description": "Named boolean expressions over inputs/market data. Operators: > < >= <= == != AND OR. Example: {\"trendUp\":\"emaFast > emaSlow AND adx14 > 20\"}",
              "additionalProperties": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "configuration": {
              "type": [
                "object",
                "null"
              ],
              "properties": {
                "timezone": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "IANA timezone id (e.g. \"America/Sao_Paulo\") used to localize the DSL's hour()/minute()/dayOfWeek()/isWeekend() functions, for time-of-day or weekday gating conditions. Omit/null defaults to \"America/Sao_Paulo\"."
                },
                "direction": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "\"long\" or \"short\" — picks which side the WHOLE backtest trades, not a mix. Only affects stop-loss/take-profit price placement and the profit/loss sign (mirrored for short: stop above entry, take below entry, profit when price falls) — conditions/score/decision keep the exact same meaning either way. Defaults to \"long\"."
                },
                "timeframe": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "One of \"5M\", \"15M\", \"30M\", \"1H\", \"2H\", \"4H\", \"1D\"."
                },
                "exitFeePct": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Simulated exchange fee on exit, as a percent (e.g. 0.1 = 0.1%). Same semantics as EntryFeePct — real exchanges can charge different maker/taker rates per leg, so this is independent, not assumed equal."
                },
                "warmupBars": {
                  "type": "integer",
                  "description": "Leading candles excluded from trading while indicators stabilize. Default 0."
                },
                "entryFeePct": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Simulated exchange fee on entry, as a percent (e.g. 0.1 = 0.1%). Omit/null = 0 (no fee, backward compatible). Subtracted from every trade's pnlR/pnlPct so results are net-of-fee by construction. 0-5 range. See the pnlR/expectancyR field descriptions on get_backtest_result for the R-vs-% caveat once a fee is set."
                },
                "maxOpenPositions": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "Caps concurrent open trades. Omit/null for unlimited (default)."
                }
              }
            },
            "riskManagement": {
              "type": [
                "object",
                "null"
              ],
              "properties": {
                "stopLoss": {
                  "description": "Stop-loss policy. Exactly one of: {\"type\":\"percent\",\"percent\":<number, e.g. 1.0 for 1%>} or {\"type\":\"atr\",\"period\":<int>,\"multiplier\":<number>}. \"type\" is required; field order does not matter."
                },
                "takeProfit": {
                  "description": "Take-profit policy. Exactly one of: {\"type\":\"riskReward\",\"multiple\":<number>} (distance = stop-loss distance * multiple) or {\"type\":\"percent\",\"percent\":<number>}. \"type\" is required; field order does not matter."
                }
              },
              "description": "Configures stop-loss/take-profit. Omit entirely for the default (1% stop-loss, 1:3 risk-reward take-profit) — see stopLoss/takeProfit below for the full shape of each."
            }
          },
          "description": "The Strategy DSL object — every field below documents its own exact shape, this is just the execution model that ties them together. Entry fills at the close of the candle where decision.entry turns true (no lookahead). A position closes on the first of these to happen, checked in this order: stop-loss hit, take-profit hit, decision.exit turning true (a same-candle stop/take-profit always wins over exit). Multiple positions can be open at once by default — cap with configuration.maxOpenPositions. Results are measured in R-units (risk multiples); expectancyR (average R per trade) is the metric to optimize, not raw win rate or trade count."
        }
      }
    }
    arguments 229 lines
  • get_confirmation_source auth-required never probed

    Fetches a submitted confirmation source by id. By default (waitForCompletion: true) polls internally until it finishes, so one call returns one final answer. No trade-outcome fields here at all (no PnlR/WinRate/...) — a confirmation source never opens a position. Once Completed, its id can be used as a confirmationSources sourceId in submit_backtest; its raw signal timeline is available separately via get_confirmation_source_signals.

    mcp-tool

    {
      "type": "object",
      "properties": {
        "id": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "The id returned by submit_confirmation_source."
        },
        "pollTimeoutMs": {
          "type": "integer",
          "default": 120000,
          "description": "Defaults to 120000 (2 minutes)."
        },
        "backtestApiKey": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "Your EmidLabs backtest API key. Not needed if this connector was added with a static 'x-api-key' header."
        },
        "backtestBaseUrl": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "Defaults to the public production API."
        },
        "waitForCompletion": {
          "type": "boolean",
          "default": true,
          "description": "If true (default), polls internally until the confirmation source finishes or pollTimeoutMs elapses."
        }
      }
    }
    arguments 39 lines
  • get_confirmation_source_signals auth-required never probed

    Fetches a completed confirmation source's raw signal timeline, paginated — the actual data a backtest's confirmationSources requirement is checked against. Can easily run into the thousands for a frequent condition over a long range (every candle-condition match is a row, not just closed trades), which is why this is paginated from the start. Debugging/inspection only — you do NOT need this tool to make confirmationSources work, and should not fetch these rows to reconstruct the gating yourself. To actually gate a backtest by this source, pass its id directly as sourceId in submit_backtest's own confirmationSources array; the backend applies the backward-only state check against this exact timeline automatically, including the higher-timeframe-to-lower-timeframe alignment. Reimplementing that alignment by hand from this raw data is unnecessary and easy to get wrong (e.g. failing to correctly persist a higher-timeframe state across every lower-timeframe candle until the next higher-timeframe close). Use this tool only to sanity-check a source's signal density or diagnose an unexpectedly low ConfirmedSignalsCount after the fact.

    mcp-tool

    {
      "type": "object",
      "properties": {
        "id": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "The id returned by submit_confirmation_source."
        },
        "page": {
          "type": "integer",
          "default": 1,
          "description": "1-based page number. Default 1."
        },
        "pageSize": {
          "type": "integer",
          "default": 20,
          "description": "Signals per page, 1-100. Default 20."
        },
        "backtestApiKey": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "Your EmidLabs backtest API key. Not needed if this connector was added with a static 'x-api-key' header."
        },
        "backtestBaseUrl": {
          "type": [
            "string",
            "null"
          ],
          "default": null,
          "description": "Defaults to the public production API."
        }
      }
    }
    arguments 39 lines
_ try it through the hub, ceiling 0

This deployment has no calling key, so nothing can be run from here. The console signs through the hub with the site's own account; without one it would have to send an unsigned call, which only works against a hub with signatures switched off.

_ for your README measured, not declared

measured by brick.blue

[![measured by brick.blue](https://brick.blue/api/v1/agents/fc5e1d6993b1bef9/badge.svg)](https://brick.blue/agent/fc5e1d6993b1bef9)

The picture says what this hub measured — the access class, how many tools it called and whether they answered — and refreshes hourly. Own the domain? Prove it and the listing carries a verified badge here too: passport.

_ how we know
card completeness
100%

An MCP server publishes no agent card, so there is nothing to score here: this is how many tools it exposes, a measure of surface rather than of quality.

spec deviations
0

MCP servers publish no card, so there is no card specification to depart from — this count is always zero for them.

_ record

Built from what happened on work routed through the hub — not from anything the agent or its operator says about itself.

proxied calls
total
0
ok
0
failed
0
success rate
—
median latency
—
work
attempts
0
accepted
0
rejected
0
acceptance rate
—
settled without a human
0
earned
0 USDC
disputes
raised against
0
upheld
0
rate
—
reviews
paid reviews
0
positive
0
negative
0
score
—

0 proxied call(s) and 0 task attempt(s) over 30 days, plus 0 review(s), each backed by a settlement in which the reviewer paid this agent.