Periscøpe
Docs menu Strategies

Strategies

Strategy files, versioning, publishing, and what changes require a new version.

A strategy is your trading logic: Python code that reacts to market events and returns orders. Periscøpe runs the same code against historical data (backtest), live data with simulated fills (paper), or a real broker (live). Each Publish snapshots the code so every run is reproducible.

What a strategy contains

Every strategy is a small bundle of files.

  • strategy.py (required). A Python module that defines a class named Strategy extending StrategyBase. This is the entry point. See Strategy Protocol for the shape it must have.
  • config_schema.json (required). The per-run settings your strategy exposes: symbol, size, thresholds, time windows, or anything else you define. Periscøpe uses it to build the run config form and validate the values before the strategy starts. The visual editor creates and edits this file for you through the Strategy Parameters panel; open the raw JSON only if you need advanced schema control.
  • Optional helper .py files. Anything you want to factor out of strategy.py: indicators, utilities, data classes. Import them from strategy.py like any Python module.

File constraints

  • At most 50 files per strategy (strategy.py, config_schema.json, plus up to 48 helpers).
  • At most 10 MB per file.
  • At most 50 MB total across the strategy.
  • Helper filenames must match [a-zA-Z0-9_-][a-zA-Z0-9_.-]*\.py. strategy.py and config_schema.json are reserved names.

The strategy editor

The editor has three surfaces.

  • A file tree on the left with your Python files and a Strategy Parameters entry.
  • A Monaco code editor for Python files.
  • A visual editor for your strategy parameters, with preview and import options if you prefer to paste raw JSON.

The sidebar also shows an Available Imports quick reference and an AI Assistant toggle. The full import surface is in the Strategy SDK.

If you want something concrete before reading the full reference, start with Strategy Examples.

Versioning

Strategies are versioned. Every time you click Publish, the backend creates a new strategy version with an auto-incremented number and a full snapshot of the bundle at that moment.

Runs bind to a specific strategy_version_id. Editing the strategy later does not change what a previously created run executed. This is the reproducibility guarantee of the platform.

Strategy parameters

Strategy parameters are the settings for a strategy. You define them when the trading logic stays the same but the run conditions change: symbol, position size, threshold, lookback window, or anything else your strategy reads. Instead of editing Python for each variation, you define those knobs once on the strategy and Periscøpe turns them into a validated run form. Changing values on a run does not require a new publish; changing the parameter definitions does.

Add parameters from the Strategy Parameters panel in the editor sidebar. Click +, give the parameter a snake_case key, pick a Type, and (optionally) add a description and a default. The description you enter appears as help text under the matching field in the run form. Drag rows to reorder; operators see parameters in that order when they create a run.

Parameter types

The Type picker has two groups. Generic types cover the primitives you would expect from any config form. System types are Periscøpe-specific and give the run form extra affordances: inline ticker validation, asset-type badges, and frequency pickers.

Generic types

  • Text. A short string. The run form renders a text input. In code, read with require_str / optional_str.
  • Integer. A whole number. The run form renders a number input. In code, read with require_int / optional_int.
  • Decimal. A real number (prices, quantities, thresholds). The run form renders a number input that accepts decimal digits. In code, read with require_decimal / optional_decimal; the extractor normalises to Decimal.
  • Boolean. A true/false flag. The run form renders a checkbox. In code, read with require_bool / optional_bool.
  • Choice. A string constrained to a fixed list of allowed values. The editor lets you manage the list inline; the run form renders a dropdown. In code, read with require_str / optional_str and treat the result as one of the listed options.

System types

  • Symbol. Any instrument reference (ticker, root symbol, leg of a pair). When you create the parameter, pick its Asset Type. Supported asset types today are EQUITY and FUTURE. The run form shows the asset type as a badge next to the field, validates the operator's ticker against the security master, offers suggestions for typos, and provides a search popover. In code, read with require_symbol / optional_symbol; the extractor returns a Symbol with .symbol and .asset_type. For multi-symbol strategies, declare separate Symbol parameters per role (long_leg, short_leg, hedge). There is no array form today.
  • Bar Frequency. A bar aggregation period (second, minute, hour, day). The visual editor pins one value, and the run form shows it as a read-only display. In code, read with require_bar_time_span / optional_bar_time_span; the extractor returns a BarTimeSpan.

Advanced: editing the raw config_schema.json

The Strategy Parameters panel creates and updates config_schema.json for you. You only need this section if you are importing a schema from elsewhere, hand-editing the raw JSON, or trying to use a JSON Schema feature the visual editor does not expose.

config_schema.json is a flat JSON Schema (Draft 7) document. Each entry under properties describes one parameter; entries in the top-level required array are required. System-type fields carry an x-periscope-type annotation.

Symbol shape

{
  "long_leg": {
    "x-periscope-type": "symbol",
    "x-periscope-asset-type": "EQUITY",
    "type": "string",
    "description": "Equity to go long on."
  }
}

The operator submits a bare ticker; require_symbol parses it into a Symbol. Only EQUITY and FUTURE are accepted in x-periscope-asset-type today.

Bar Frequency shape

// Fixed by the strategy
{
  "bar_freq": {
    "x-periscope-type": "bar_time_span",
    "type": "string",
    "const": "minute"
  }
}

// Operator chooses from a list
{
  "bar_freq": {
    "x-periscope-type": "bar_time_span",
    "type": "string",
    "enum": ["minute", "hour"]
  }
}

Values must be a subset of second, minute, hour, day. The visual editor only emits the const form; the enum form is valid in raw schemas but is not yet authored from the editor.

Schema and code consistency

When a field is listed in required, read it in Python with require_*. Otherwise read it with optional_* and supply a default. Publishing runs a consistency check between the schema and the code; mismatches surface as validation errors.

What requires a new publish

Any change to the bundle. That means:

  • Any change to strategy.py.
  • Any change to config_schema.json.
  • Adding, removing, or editing any helper file.

What does not require a publish is changing the values the config form asks for. Those live on the run, not the strategy. Create a new run or Copy an existing run and edit the config.

Publishing

Publishing is how your edits become a new runnable version. When you click it, the backend validates the bundle (file layout, schema shape, and a consistency check between the schema and the code) and then stores an immutable snapshot. Validation errors surface as toasts; fix them and click Publish again.

On success you get a toast with a Create run action that takes you straight to a new run preloaded with the new version.