Skip to main content
Glama

Backtest a stock and ETF portfolio

backtest_portfolio
Read-onlyIdempotent

Use this when the user describes a portfolio of US stocks and ETFs with weights (e.g. "60% VTI, 40% BND", "equal parts AAPL, MSFT, NVDA") and wants to know how it would have done or how risky it is: returns over YTD, 1 year and 5 years (rebalanced quarterly), max drawdown, volatility, Sharpe, beta vs SPY, dividend yield and the correlation between holdings, charted against SPY, with a link that opens it on Redstone to save. 2 to 20 holdings. Not for live brokerage accounts, trading or allocation advice.

Tool call template

Replace each placeholder with a value matching the input schema. Only required parameters are included; add optional parameters as needed.

{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "arguments": {
      "positions": "<positions>"
    },
    "name": "backtest_portfolio"
  }
}

Input Schema

NameRequiredDescriptionDefault
positionsYes

Output Schema

NameRequiredDescriptionDefault
urlYesOpens Redstone's New portfolio form filled in with this mix, to save it and follow it on a dashboard
asOfYesMarket date (YYYY-MM-DD) of the latest close behind every number
holdingsYes
benchmarkYesSPY, for comparison
portfolioYesBacktest of the weights, rebalanced back to them every quarter
correlationYes

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed10 schema fields changed
    • changedOutput schema / properties / benchmark / anyOf
      Previous value: -[
      -  {
      -    "additionalProperties": false,
      -    "properties": {
      -      "alpha1yPct": {
      -        "description": "Annualized, versus SPY",
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "beta1y": {
      -        "description": "Versus SPY, from daily returns over the past year",
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "maxDrawdown1yPct": {
      -        "description": "Worst peak-to-trough fall over the past year (negative)",
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "name": {
      -        "type": "string"
      -      },
      -      "price": {
      -        "description": "Latest close in USD",
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "return1dPct": {
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "return1yPct": {
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "return5yAnnualizedPct": {
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "returnYtdPct": {
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "sector": {
      -        "description": "GICS sector for stocks, category for ETFs",
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      },
      -      "sharpe1y": {
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "sortino1y": {
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "statsSince": {
      -        "description": "YYYY-MM-DD, set only when there is less than a year of history: the 1Y figures (return, drawdown, Sharpe, Sortino, beta, alpha) then run from this first close, and so does the YTD return if it falls this year. Screens on those metrics leave such securities out",
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      },
      -      "ticker": {
      -        "type": "string"
      -      },
      -      "type": {
      -        "enum": [
      -          "stock",
      -          "etf"
      -        ],
      -        "type": "string"
      -      },
      -      "url": {
      -        "description": "Redstone page for this security",
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "return1dPct",
      -      "returnYtdPct",
      -      "return1yPct",
      -      "return5yAnnualizedPct",
      -      "maxDrawdown1yPct",
      -      "sharpe1y",
      -      "sortino1y",
      -      "beta1y",
      -      "alpha1yPct",
      -      "statsSince",
      -      "ticker",
      -      "name",
      -      "type",
      -      "sector",
      -      "price",
      -      "url"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]New value: +[
      +  {
      +    "additionalProperties": false,
      +    "properties": {
      +      "alpha1yPct": {
      +        "description": "Annualized, versus SPY",
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "beta1y": {
      +        "description": "Versus SPY, from daily returns over the past year",
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "dividendYieldPct": {
      +        "description": "Trailing 12-month dividend yield: dividends that went ex in the past year over the latest close. 0 pays none; null when unknown (paid in another currency)",
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "maxDrawdown1yPct": {
      +        "description": "Worst peak-to-trough fall over the past year (negative)",
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "name": {
      +        "type": "string"
      +      },
      +      "price": {
      +        "description": "Latest close in USD",
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "return1dPct": {
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "return1yPct": {
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "return5yAnnualizedPct": {
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "returnYtdPct": {
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "sector": {
      +        "description": "GICS sector for stocks, category for ETFs",
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "sharpe1y": {
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "sortino1y": {
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "statsSince": {
      +        "description": "YYYY-MM-DD, set only when there is less than a year of history: the 1Y figures (return, drawdown, volatility, Sharpe, Sortino, beta, alpha, yield) then run from this first close, and so does the YTD return if it falls this year. Screens on those metrics leave such securities out",
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "ticker": {
      +        "type": "string"
      +      },
      +      "type": {
      +        "enum": [
      +          "stock",
      +          "etf"
      +        ],
      +        "type": "string"
      +      },
      +      "url": {
      +        "description": "Redstone page for this security",
      +        "type": "string"
      +      },
      +      "volatility1yPct": {
      +        "description": "Annualized standard deviation of daily returns over the past year",
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      }
      +    },
      +    "required": [
      +      "return1dPct",
      +      "returnYtdPct",
      +      "return1yPct",
      +      "return5yAnnualizedPct",
      +      "maxDrawdown1yPct",
      +      "sharpe1y",
      +      "sortino1y",
      +      "beta1y",
      +      "alpha1yPct",
      +      "volatility1yPct",
      +      "dividendYieldPct",
      +      "statsSince",
      +      "ticker",
      +      "name",
      +      "type",
      +      "sector",
      +      "price",
      +      "url"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "type": "null"
      +  }
      +]
    • addedOutput schema / properties / holdings / items / properties / dividendYieldPct
      Added value: +{
      +  "description": "Trailing 12-month dividend yield: dividends that went ex in the past year over the latest close. 0 pays none; null when unknown (paid in another currency)",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • changedOutput schema / properties / holdings / items / properties / statsSince / description
      Previous value: -"YYYY-MM-DD, set only when there is less than a year of history: the 1Y figures (return, drawdown, Sharpe, Sortino, beta, alpha) then run from this first close, and so does the YTD return if it falls this year. Screens on those metrics leave such securities out"New value: +"YYYY-MM-DD, set only when there is less than a year of history: the 1Y figures (return, drawdown, volatility, Sharpe, Sortino, beta, alpha, yield) then run from this first close, and so does the YTD return if it falls this year. Screens on those metrics leave such securities out"
    • addedOutput schema / properties / holdings / items / properties / volatility1yPct
      Added value: +{
      +  "description": "Annualized standard deviation of daily returns over the past year",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • changedOutput schema / properties / holdings / items / required
      Previous value: -[
      -  "return1dPct",
      -  "returnYtdPct",
      -  "return1yPct",
      -  "return5yAnnualizedPct",
      -  "maxDrawdown1yPct",
      -  "sharpe1y",
      -  "sortino1y",
      -  "beta1y",
      -  "alpha1yPct",
      -  "statsSince",
      -  "ticker",
      -  "name",
      -  "type",
      -  "sector",
      -  "price",
      -  "url",
      -  "weightPct"
      -]New value: +[
      +  "return1dPct",
      +  "returnYtdPct",
      +  "return1yPct",
      +  "return5yAnnualizedPct",
      +  "maxDrawdown1yPct",
      +  "sharpe1y",
      +  "sortino1y",
      +  "beta1y",
      +  "alpha1yPct",
      +  "volatility1yPct",
      +  "dividendYieldPct",
      +  "statsSince",
      +  "ticker",
      +  "name",
      +  "type",
      +  "sector",
      +  "price",
      +  "url",
      +  "weightPct"
      +]
    • addedOutput schema / properties / portfolio / properties / dividendYieldPct
      Added value: +{
      +  "description": "Trailing 12-month dividend yield: dividends that went ex in the past year over the latest close. 0 pays none; null when unknown (paid in another currency)",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • changedOutput schema / properties / portfolio / properties / statsSince / description
      Previous value: -"YYYY-MM-DD, set only when there is less than a year of history: the 1Y figures (return, drawdown, Sharpe, Sortino, beta, alpha) then run from this first close, and so does the YTD return if it falls this year. Screens on those metrics leave such securities out"New value: +"YYYY-MM-DD, set only when there is less than a year of history: the 1Y figures (return, drawdown, volatility, Sharpe, Sortino, beta, alpha, yield) then run from this first close, and so does the YTD return if it falls this year. Screens on those metrics leave such securities out"
    • addedOutput schema / properties / portfolio / properties / volatility1yPct
      Added value: +{
      +  "description": "Annualized standard deviation of daily returns over the past year",
      +  "type": [
      +    "number",
      +    "null"
      +  ]
      +}
    • changedOutput schema / properties / portfolio / required
      Previous value: -[
      -  "return1dPct",
      -  "returnYtdPct",
      -  "return1yPct",
      -  "return5yAnnualizedPct",
      -  "maxDrawdown1yPct",
      -  "sharpe1y",
      -  "sortino1y",
      -  "beta1y",
      -  "alpha1yPct",
      -  "statsSince"
      -]New value: +[
      +  "return1dPct",
      +  "returnYtdPct",
      +  "return1yPct",
      +  "return5yAnnualizedPct",
      +  "maxDrawdown1yPct",
      +  "sharpe1y",
      +  "sortino1y",
      +  "beta1y",
      +  "alpha1yPct",
      +  "volatility1yPct",
      +  "dividendYieldPct",
      +  "statsSince"
      +]
    • changedOutput schema / properties / url / description
      Previous value: -"Where to build and save this portfolio on Redstone"New value: +"Opens Redstone's New portfolio form filled in with this mix, to save it and follow it on a dashboard"
  2. Changed5 schema fields changed
    • changedOutput schema / properties / benchmark / anyOf
      Previous value: -[
      -  {
      -    "additionalProperties": false,
      -    "properties": {
      -      "alpha1yPct": {
      -        "description": "Annualized, versus SPY",
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "beta1y": {
      -        "description": "Versus SPY, from daily returns over the past year",
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "maxDrawdown1yPct": {
      -        "description": "Worst peak-to-trough fall over the past year (negative)",
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "name": {
      -        "type": "string"
      -      },
      -      "price": {
      -        "description": "Latest close in USD",
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "return1dPct": {
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "return1yPct": {
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "return5yAnnualizedPct": {
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "returnYtdPct": {
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "sector": {
      -        "description": "GICS sector for stocks, category for ETFs",
      -        "type": [
      -          "string",
      -          "null"
      -        ]
      -      },
      -      "sharpe1y": {
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "sortino1y": {
      -        "type": [
      -          "number",
      -          "null"
      -        ]
      -      },
      -      "ticker": {
      -        "type": "string"
      -      },
      -      "type": {
      -        "enum": [
      -          "stock",
      -          "etf"
      -        ],
      -        "type": "string"
      -      },
      -      "url": {
      -        "description": "Redstone page for this security",
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "return1dPct",
      -      "returnYtdPct",
      -      "return1yPct",
      -      "return5yAnnualizedPct",
      -      "maxDrawdown1yPct",
      -      "sharpe1y",
      -      "sortino1y",
      -      "beta1y",
      -      "alpha1yPct",
      -      "ticker",
      -      "name",
      -      "type",
      -      "sector",
      -      "price",
      -      "url"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]New value: +[
      +  {
      +    "additionalProperties": false,
      +    "properties": {
      +      "alpha1yPct": {
      +        "description": "Annualized, versus SPY",
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "beta1y": {
      +        "description": "Versus SPY, from daily returns over the past year",
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "maxDrawdown1yPct": {
      +        "description": "Worst peak-to-trough fall over the past year (negative)",
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "name": {
      +        "type": "string"
      +      },
      +      "price": {
      +        "description": "Latest close in USD",
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "return1dPct": {
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "return1yPct": {
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "return5yAnnualizedPct": {
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "returnYtdPct": {
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "sector": {
      +        "description": "GICS sector for stocks, category for ETFs",
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "sharpe1y": {
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "sortino1y": {
      +        "type": [
      +          "number",
      +          "null"
      +        ]
      +      },
      +      "statsSince": {
      +        "description": "YYYY-MM-DD, set only when there is less than a year of history: the 1Y figures (return, drawdown, Sharpe, Sortino, beta, alpha) then run from this first close, and so does the YTD return if it falls this year. Screens on those metrics leave such securities out",
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "ticker": {
      +        "type": "string"
      +      },
      +      "type": {
      +        "enum": [
      +          "stock",
      +          "etf"
      +        ],
      +        "type": "string"
      +      },
      +      "url": {
      +        "description": "Redstone page for this security",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "return1dPct",
      +      "returnYtdPct",
      +      "return1yPct",
      +      "return5yAnnualizedPct",
      +      "maxDrawdown1yPct",
      +      "sharpe1y",
      +      "sortino1y",
      +      "beta1y",
      +      "alpha1yPct",
      +      "statsSince",
      +      "ticker",
      +      "name",
      +      "type",
      +      "sector",
      +      "price",
      +      "url"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "type": "null"
      +  }
      +]
    • addedOutput schema / properties / holdings / items / properties / statsSince
      Added value: +{
      +  "description": "YYYY-MM-DD, set only when there is less than a year of history: the 1Y figures (return, drawdown, Sharpe, Sortino, beta, alpha) then run from this first close, and so does the YTD return if it falls this year. Screens on those metrics leave such securities out",
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • changedOutput schema / properties / holdings / items / required
      Previous value: -[
      -  "return1dPct",
      -  "returnYtdPct",
      -  "return1yPct",
      -  "return5yAnnualizedPct",
      -  "maxDrawdown1yPct",
      -  "sharpe1y",
      -  "sortino1y",
      -  "beta1y",
      -  "alpha1yPct",
      -  "ticker",
      -  "name",
      -  "type",
      -  "sector",
      -  "price",
      -  "url",
      -  "weightPct"
      -]New value: +[
      +  "return1dPct",
      +  "returnYtdPct",
      +  "return1yPct",
      +  "return5yAnnualizedPct",
      +  "maxDrawdown1yPct",
      +  "sharpe1y",
      +  "sortino1y",
      +  "beta1y",
      +  "alpha1yPct",
      +  "statsSince",
      +  "ticker",
      +  "name",
      +  "type",
      +  "sector",
      +  "price",
      +  "url",
      +  "weightPct"
      +]
    • addedOutput schema / properties / portfolio / properties / statsSince
      Added value: +{
      +  "description": "YYYY-MM-DD, set only when there is less than a year of history: the 1Y figures (return, drawdown, Sharpe, Sortino, beta, alpha) then run from this first close, and so does the YTD return if it falls this year. Screens on those metrics leave such securities out",
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • changedOutput schema / properties / portfolio / required
      Previous value: -[
      -  "return1dPct",
      -  "returnYtdPct",
      -  "return1yPct",
      -  "return5yAnnualizedPct",
      -  "maxDrawdown1yPct",
      -  "sharpe1y",
      -  "sortino1y",
      -  "beta1y",
      -  "alpha1yPct"
      -]New value: +[
      +  "return1dPct",
      +  "returnYtdPct",
      +  "return1yPct",
      +  "return5yAnnualizedPct",
      +  "maxDrawdown1yPct",
      +  "sharpe1y",
      +  "sortino1y",
      +  "beta1y",
      +  "alpha1yPct",
      +  "statsSince"
      +]
  3. First observed

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already establish read-only, idempotent, non-destructive behavior, and the description adds meaningful computation details: quarterly rebalancing, benchmark comparison against SPY, and a shareable Redstone link. It stops short of explaining handling of invalid or unknown tickers.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

It is front-loaded with the usage trigger, then layers outputs, constraints, and exclusions without wasted text. Though dense, every clause contributes to correct invocation.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema available, the description need not enumerate return values, and it still summarizes the key computed metrics and charting behavior. It gives enough context for an agent to decide whether and how to call the tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already documents ticker format and weight rescaling, but the description adds the 2-to-20 holdings range and practical input forms such as '60% VTI, 40% BND' and 'equal parts AAPL, MSFT, NVDA'. This meaningfully helps an agent construct the positions array.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('backtest ... portfolio') and immediately scopes it to weighted US stock/ETF portfolios with named example inputs. The trigger conditions and listed outputs make it clearly distinct from sibling tools like compare_securities and get_security_metrics.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit when-to-use conditions (user describes a weighted portfolio and wants historical performance or risk) plus concrete examples. It also provides exclusions ('Not for live brokerage accounts, trading or allocation advice') and the 2-to-20-holding constraint.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources