{
  "openapi": "3.1.0",
  "info": {
    "title": "Backtesting",
    "version": "1.0.0",
    "description": "tastytrade options backtester",
    "contact": {
      "name": "tastytrade API support",
      "email": "api.support@tastytrade.com",
      "url": "https://developer.tastytrade.com"
    }
  },
  "servers": [
    {
      "url": "https://backtester.vast.tastyworks.com",
      "description": "Backtester API"
    }
  ],
  "paths": {
    "/backtests": {
      "get": {
        "summary": "List Your Backtests",
        "description": "Returns the IDs of every backtest the authenticated user has submitted. Use an ID with `GET /backtests/{id}` to poll status or read results.",
        "operationId": "getUsersBacktests",
        "responses": {
          "200": {
            "description": "An array of backtest IDs belonging to the current user.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized."
          },
          "429": {
            "description": "Request rate exceeded reasonable thresholds. Back off exponentially before retrying."
          }
        },
        "tags": [
          "backtesting"
        ],
        "x-tt-class": "read",
        "x-money-moving": false,
        "x-idempotent": true,
        "x-rate-limit-bucket": "read"
      },
      "post": {
        "summary": "Create A Backtest",
        "description": "Submits a backtest definition (underlying `symbol`, a `startDate`/`endDate` window, and one or more `legs`) and starts the run. A `201` means the backtest is pending and still running; a `200` means it is already completed. Either way the body includes an `id` to poll with `GET /backtests/{id}`.",
        "operationId": "postBacktests",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BacktestPost"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Backtest completed and ready to read. The body is a fully populated backtest object.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BacktestGet"
                }
              }
            }
          },
          "201": {
            "description": "Backtest accepted and still running. Poll `GET /backtests/{id}` and watch `status` until it reaches `completed`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BacktestGet"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400",
            "description": "The request body was malformed or did not match the backtest schema (for example a missing required leg field or an invalid `strikeSelection` value)."
          },
          "401": {
            "description": "Unauthorized."
          },
          "404": {
            "$ref": "#/components/responses/404",
            "description": "Backtest not found."
          },
          "429": {
            "description": "Request rate exceeded reasonable thresholds. Back off exponentially before retrying."
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "tags": [
          "backtesting"
        ],
        "x-tt-class": "mutating",
        "x-money-moving": false,
        "x-idempotent": false,
        "x-rate-limit-bucket": "write"
      }
    },
    "/backtests/{id}": {
      "get": {
        "summary": "Get Backtest By ID",
        "description": "Fetches a single backtest by its ID. Poll this endpoint to watch `status` move through `pending`, `running`, and `completed`; while it runs the object reports `progress` and an `ETA` you can use to space out polls. Once `completed`, read `statistics`, `trials`, `snapshots`, and `notices`.",
        "operationId": "getBacktests",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The backtest object, including its current status and (once completed) results.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BacktestGet"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400",
            "description": "The supplied backtest ID was malformed."
          },
          "401": {
            "description": "Unauthorized."
          },
          "404": {
            "$ref": "#/components/responses/404",
            "description": "No backtest exists with the supplied ID for this user."
          },
          "429": {
            "description": "Request rate exceeded reasonable thresholds. Poll on an interval rather than tightly looping, and back off on 429."
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "tags": [
          "backtesting"
        ],
        "x-tt-class": "read",
        "x-money-moving": false,
        "x-idempotent": true,
        "x-rate-limit-bucket": "read"
      }
    },
    "/backtests/{id}/logs": {
      "get": {
        "summary": "Get Backtest Logs",
        "description": "Returns the step-by-step execution logs for the specified backtest, useful for understanding how trials were opened and closed during the run.",
        "operationId": "getBacktestsLogs",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The execution log for the backtest."
          },
          "400": {
            "$ref": "#/components/responses/400",
            "description": "The supplied backtest ID was malformed."
          },
          "401": {
            "description": "Unauthorized."
          },
          "404": {
            "$ref": "#/components/responses/404",
            "description": "No backtest exists with the supplied ID for this user."
          },
          "429": {
            "description": "Request rate exceeded reasonable thresholds. Back off exponentially before retrying."
          }
        },
        "tags": [
          "backtesting"
        ],
        "x-tt-class": "read",
        "x-money-moving": false,
        "x-idempotent": true,
        "x-rate-limit-bucket": "read"
      }
    },
    "/backtests/{id}/cancel": {
      "post": {
        "summary": "Cancel A Running Backtest",
        "description": "Stops a `pending` or `running` backtest early. Returns `204 No Content` on success. Useful for aborting a long run you no longer need.",
        "operationId": "cancelBacktests",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "The backtest was cancelled. No response body is returned."
          },
          "400": {
            "$ref": "#/components/responses/400",
            "description": "The supplied backtest ID was malformed."
          },
          "401": {
            "description": "Unauthorized."
          },
          "404": {
            "$ref": "#/components/responses/404",
            "description": "No backtest exists with the supplied ID for this user."
          },
          "429": {
            "description": "Request rate exceeded reasonable thresholds. Back off exponentially before retrying."
          }
        },
        "tags": [
          "backtesting"
        ],
        "x-tt-class": "mutating",
        "x-money-moving": false,
        "x-idempotent": false,
        "x-rate-limit-bucket": "write"
      }
    },
    "/available-dates": {
      "get": {
        "summary": "List Available Date Ranges",
        "description": "Returns each symbol with historical backtest data alongside the `startDate` and `endDate` of its available coverage. Call this before creating a backtest to confirm a symbol has history for your requested window.",
        "operationId": "getAvailableDates",
        "responses": {
          "200": {
            "description": "An array of symbols, each with the inclusive range of dates that have backtest history.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AvailableSymbolDates"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/400",
            "description": "The request was malformed."
          },
          "401": {
            "description": "Unauthorized."
          },
          "429": {
            "description": "Request rate exceeded reasonable thresholds. Back off exponentially before retrying."
          },
          "500": {
            "$ref": "#/components/responses/500"
          }
        },
        "tags": [
          "backtesting"
        ],
        "x-tt-class": "read",
        "x-money-moving": false,
        "x-idempotent": true,
        "x-rate-limit-bucket": "read"
      }
    },
    "/simulate-trade": {
      "post": {
        "summary": "Simulate A Single Trade",
        "description": "Returns historical pricing for one trade without defining a full backtest. Supply an `underlying` and one or more `legs` (each leg uses tastytrade/OCC symbology), optionally bounded by `startTime`/`endTime`. The response is a time series of price points with the trade `effect` (debit or credit), `underlyingPrice`, and `delta`.",
        "operationId": "simulateTrade",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "underlying": {
                    "type": "string",
                    "description": "Underlying symbol of the trade"
                  },
                  "startTime": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Optionally, start time of the trade in ISO 8601 format."
                  },
                  "endTime": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Optionally, end time of the trade in ISO 8601 format."
                  },
                  "legs": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "symbol": {
                          "type": "string",
                          "description": "The leg symbol uses the tastytrade / OCC symbology convention - https://developer.tastytrade.com/api-overview/#tastytrade-symbology"
                        },
                        "direction": {
                          "type": "string",
                          "enum": [
                            "long",
                            "short"
                          ]
                        },
                        "quantity": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              },
              "examples": {
                "coveredCall": {
                  "summary": "Covered call",
                  "value": {
                    "underlying": "SPY",
                    "legs": [
                      {
                        "symbol": "SPY   190227C00275000",
                        "direction": "short",
                        "quantity": 1
                      },
                      {
                        "symbol": "SPY",
                        "direction": "long",
                        "quantity": 100
                      }
                    ]
                  }
                },
                "putRatioSpread": {
                  "summary": "Put ratio spread",
                  "value": {
                    "underlying": "SPY",
                    "legs": [
                      {
                        "symbol": "SPY   190227P00265000",
                        "direction": "long",
                        "quantity": 3
                      },
                      {
                        "symbol": "SPY   190227P00275000",
                        "direction": "short",
                        "quantity": 1
                      }
                    ]
                  }
                },
                "longCallButterfly": {
                  "summary": "Long call butterfly",
                  "value": {
                    "underlying": "SPY",
                    "legs": [
                      {
                        "symbol": "SPY   190227C00270000",
                        "direction": "long",
                        "quantity": 2
                      },
                      {
                        "symbol": "SPY   190227C00275000",
                        "direction": "short",
                        "quantity": 1
                      },
                      {
                        "symbol": "SPY   190227C00265000",
                        "direction": "short",
                        "quantity": 1
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A time-ordered series of price points for the simulated trade.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "dateTime": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "price": {
                        "type": "string"
                      },
                      "effect": {
                        "type": "string",
                        "enum": [
                          "debit",
                          "credit"
                        ]
                      },
                      "underlyingPrice": {
                        "type": "string"
                      },
                      "delta": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request body was malformed, for example an invalid leg `symbol` or `direction`."
          },
          "401": {
            "description": "Unauthorized."
          },
          "404": {
            "description": "No history is available for the requested underlying or time range."
          },
          "429": {
            "description": "Request rate exceeded reasonable thresholds. Back off exponentially before retrying."
          }
        },
        "tags": [
          "backtesting"
        ],
        "x-tt-class": "mutating",
        "x-money-moving": false,
        "x-idempotent": false,
        "x-rate-limit-bucket": "write"
      }
    }
  },
  "components": {
    "schemas": {
      "BacktestPost": {
        "type": "object",
        "properties": {
          "symbol": {
            "type": "string",
            "description": "Underlying symbol the strategy trades. Confirm history exists for your date range via `GET /available-dates`.",
            "example": "SPY"
          },
          "startDate": {
            "type": "string",
            "format": "date",
            "description": "First date of the historical window to backtest, in ISO 8601 `YYYY-MM-DD` format.",
            "example": "2022-01-01"
          },
          "endDate": {
            "type": "string",
            "format": "date",
            "description": "Last date of the historical window to backtest, in ISO 8601 `YYYY-MM-DD` format.",
            "example": "2022-12-31"
          },
          "legs": {
            "type": "array",
            "items": {
              "type": "object",
              "allOf": [
                {
                  "$ref": "#/components/schemas/Leg"
                }
              ]
            }
          },
          "entryConditions": {
            "allOf": [
              {
                "$ref": "#/components/schemas/EntryConditions"
              }
            ]
          },
          "exitConditions": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ExitConditions"
              }
            ]
          }
        },
        "description": "The definition of a backtest to run: an underlying `symbol`, a historical `startDate`/`endDate` window, one or more strategy `legs`, and optional `entryConditions`/`exitConditions` controlling when trials open and close."
      },
      "BacktestGet": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique identifier for this backtest. Use it with `GET /backtests/{id}` to poll status and read results.",
            "example": "b1f8c2a4-9e7d-4a31-8c6f-2d5e1a0b3c7e"
          },
          "symbol": {
            "type": "string",
            "description": "Underlying symbol the strategy trades.",
            "example": "SPY"
          },
          "startDate": {
            "type": "string",
            "format": "date",
            "description": "First date of the backtested historical window, in ISO 8601 `YYYY-MM-DD` format.",
            "example": "2022-01-01"
          },
          "endDate": {
            "type": "string",
            "format": "date",
            "description": "Last date of the backtested historical window, in ISO 8601 `YYYY-MM-DD` format.",
            "example": "2022-12-31"
          },
          "legs": {
            "type": "array",
            "items": {
              "type": "object",
              "allOf": [
                {
                  "$ref": "#/components/schemas/Leg"
                }
              ]
            }
          },
          "entryConditions": {
            "allOf": [
              {
                "$ref": "#/components/schemas/EntryConditions"
              }
            ]
          },
          "exitConditions": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ExitConditions"
              }
            ]
          },
          "ETA": {
            "type": "number",
            "description": "Estimated time remaining until the run completes. Use it to space out polls while the backtest is still running."
          },
          "progress": {
            "type": "number",
            "description": "Completion progress of the run."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "running",
              "completed"
            ],
            "description": "Current run state. Moves through `pending`, `running`, and `completed`; read results once it is `completed`.",
            "example": "completed"
          },
          "statistics": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Aggregate performance metrics for the run, populated once the backtest completes."
          },
          "trials": {
            "type": "array",
            "items": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Trial"
                }
              ]
            }
          },
          "snapshots": {
            "type": "array",
            "items": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Snapshot"
                }
              ]
            }
          },
          "notices": {
            "description": "Conditional annotations appearing if specific conditions are met (for example overlapping ranges due to a stock split).",
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": []
          }
        },
        "description": "A backtest object returned when creating or polling a backtest. Includes the original definition plus run state (`status`, `progress`, `ETA`) and, once completed, the results in `statistics`, `trials`, `snapshots`, and `notices`."
      },
      "Snapshot": {
        "type": "object",
        "properties": {
          "dateTime": {
            "type": "string",
            "description": "Timestamp of this snapshot in ISO 8601 format.",
            "example": "2022-01-03T14:30:00Z"
          },
          "profitLoss": {
            "type": "number",
            "description": "Cumulative strategy profit or loss at this point in the run.",
            "example": 128.5
          },
          "underlyingPrice": {
            "type": "number",
            "description": "Price of the underlying symbol at this point in the run.",
            "example": 477.71
          }
        },
        "description": "A single point in the backtest's time series, capturing strategy profit/loss and the underlying price at a given moment."
      },
      "Trial": {
        "type": "object",
        "properties": {
          "openDateTime": {
            "type": "string",
            "description": "Timestamp the trial opened, in ISO 8601 format.",
            "example": "2022-01-03T14:30:00Z"
          },
          "closeDateTime": {
            "type": "string",
            "description": "Timestamp the trial closed, in ISO 8601 format.",
            "example": "2022-01-20T20:00:00Z"
          },
          "profitLoss": {
            "type": "number",
            "description": "Realized profit or loss for this trial.",
            "example": 128.5
          }
        },
        "description": "A single simulated trade within the backtest, with its open and close times and realized profit/loss."
      },
      "Leg": {
        "type": "object",
        "required": [
          "type",
          "direction",
          "quantity",
          "strikeSelection",
          "daysUntilExpiration"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "equity",
              "equity-option"
            ],
            "description": "The instrument type for this leg: `equity` for shares or `equity-option` for an option contract.",
            "example": "equity-option"
          },
          "direction": {
            "type": "string",
            "enum": [
              "long",
              "short"
            ],
            "description": "The position direction: `long` to buy, `short` to sell.",
            "example": "short"
          },
          "side": {
            "type": "string",
            "enum": [
              "call",
              "put"
            ],
            "description": "The option side, `call` or `put`. Only applicable when `type` is `equity-option`.",
            "example": "put"
          },
          "quantity": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100,
            "description": "Number of contracts or shares (1-10 for options, 1-100 for stocks).",
            "example": 1
          },
          "strikeSelection": {
            "type": "string",
            "enum": [
              "delta",
              "percentageOTM",
              "percentageOTMRelative",
              "currentPriceOffset",
              "currentPriceOffsetRelative",
              "currentPriceExactOffsetRelative",
              "premium"
            ],
            "description": "How the strike is chosen for this leg. The accompanying value field depends on this: for example `delta` reads `delta`, `premium` reads `premium`.",
            "example": "delta"
          },
          "strikeRelativeLeg": {
            "type": "integer",
            "description": "Index of the leg to use as reference when strike selection is relative to another leg.",
            "example": 0
          },
          "delta": {
            "type": "number",
            "minimum": 1,
            "maximum": 100,
            "description": "Delta value used when `strikeSelection` is `delta`, expressed as an integer from 1 to 100.",
            "example": 16
          },
          "percentageOTM": {
            "type": "number",
            "description": "Percentage out-of-the-money when `strikeSelection` is `percentageOTM` or `percentageOTMRelative` (for example 0.1 for 10% OTM, -0.1 for 10% ITM).",
            "example": 0.1
          },
          "currentPriceOffset": {
            "type": "number",
            "maximum": 50000,
            "description": "Offset from current price when `strikeSelection` is a currentPriceOffset variant (max 50000).",
            "example": 5
          },
          "premium": {
            "type": "number",
            "maximum": 50000,
            "description": "Target premium when `strikeSelection` is `premium` (max 50000).",
            "example": 2.5
          },
          "daysUntilExpiration": {
            "type": "integer",
            "description": "Number of days until expiration to target for this leg.",
            "example": 45
          }
        },
        "description": "One position in the strategy. The required fields are `type`, `direction`, `quantity`, `strikeSelection`, and `daysUntilExpiration`; `side` applies only to `equity-option` legs. The strike-related field you supply depends on `strikeSelection`."
      },
      "EntryConditions": {
        "type": "object",
        "properties": {
          "frequency": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "every day",
              "on specific days of the week",
              "on exact days to expiration match"
            ],
            "description": "How often to open new trials: `every day`, `on specific days of the week`, or `on exact days to expiration match`.",
            "example": "every day"
          },
          "specificDays": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "integer"
            },
            "description": "Days of the week on which to enter, used when `frequency` is `on specific days of the week`."
          },
          "maximumActiveTrials": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Maximum number of trials allowed open simultaneously.",
            "example": 5
          },
          "maximumActiveTrialsBehavior": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "don't enter",
              "close oldest"
            ],
            "description": "What to do when the active-trial limit is reached: `don't enter` to skip the entry, or `close oldest` to free a slot.",
            "example": "don't enter"
          },
          "minimumVIX": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Only open trials when the VIX is at or above this value.",
            "example": 15
          },
          "maximumVIX": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Only open trials when the VIX is at or below this value.",
            "example": 30
          }
        },
        "description": "Optional rules controlling when new trials are opened during the backtest, such as entry frequency, concurrency limits, and VIX bounds."
      },
      "ExitConditions": {
        "type": "object",
        "properties": {
          "takeProfitPercentage": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Profit threshold for closing a trial, expressed as a percentage.",
            "example": 50
          },
          "stopLossPercentage": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Loss threshold for closing a trial, expressed as a percentage.",
            "example": 100
          },
          "afterDaysInTrade": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Close a trial after it has been open this many days.",
            "example": 21
          },
          "atDaysToExpiration": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Close a trial when this many days to expiration remain.",
            "example": 7
          },
          "minimumVIX": {
            "type": [
              "integer",
              "null"
            ],
            "description": "VIX threshold used as an exit condition for closing trials.",
            "example": 12
          }
        },
        "description": "Optional rules controlling when trials are closed during the backtest, such as profit/loss targets, time in trade, and days to expiration."
      },
      "AvailableSymbolDates": {
        "type": "object",
        "properties": {
          "symbol": {
            "type": "string",
            "description": "The symbol with available history.",
            "example": "SPY"
          },
          "startDate": {
            "type": "string",
            "description": "Earliest date with available backtest history for this symbol.",
            "example": "2012-01-03"
          },
          "endDate": {
            "type": "string",
            "description": "Latest date with available backtest history for this symbol.",
            "example": "2024-12-31"
          }
        },
        "description": "The inclusive range of historical dates available for backtesting a single symbol."
      }
    },
    "responses": {
      "400": {
        "description": "Request included malformed input"
      },
      "404": {
        "description": "Backtest not found"
      },
      "500": {
        "description": "Failed to process request"
      }
    }
  },
  "x-tastytrade-service": {
    "serviceName": "backtesting-api",
    "group": "backtesting",
    "title": "Backtesting"
  },
  "tags": [
    {
      "name": "backtesting"
    }
  ]
}