Skip to main content
Contents
5 min read

Strategies

Strategy SDK

Author, test, version, and sync a local Python Strategy plugin.

App path

  • Local Strategy Library -> plugin.py
  • Strategies -> Strategy Library -> Sync

Build a Strategy with Python

The Strategy SDK is Arizmic's Python code-authoring path for Strategies. Use it when you want source control, reusable builder code, focused tests, or a rule shape that the Visual Composer cannot express through its supported controls.

The plugin's Python constructs a typed Strategy definition; it is not the code that runs directly in a Study or deployment. Arizmic validates and compiles the saved Strategy before later workflows use it.

Decide whether code is needed

Prefer Visual Composer for supported no-code rule design. Use the SDK when you need to:

  • maintain the Strategy in a reviewed Python project;
  • reuse helpers across a related group of Strategies;
  • generate a bounded family of typed rules; or
  • write direct unit tests for rule construction.

Do not use a plugin as a place for network requests, credentials, hidden local files, arbitrary execution, or future-looking data access. Put reusable market calculations in Signals and bind their reviewed outputs to the Strategy.

What the plugin owns

A Strategy plugin has a stable plugin ID and version, declared parameters, and a build_ir() function that returns the Strategy definition. That definition names its inputs, parameters, entries, exits, position/risk behavior, and requirements.

Keep the Python code small and explicit. A useful test checks that the intended inputs, parameter bounds, rule structure, and requirements are present—not just that the module imports.

Worked example: Session VWAP True Z-Score Mean Reversion

The bundled Session VWAP True Z-Score Mean Reversion plugin is a complete Strategy SDK example with no Signal-to-Signal dependency. It binds two outputs from the one direct Session VWAP True Z-Score Signal: session_vwap_true_zscore:seed1:zscore for its trading rules and session_vwap_true_zscore:seed1:weighted_sd to translate chosen z-score distances into price distances. That Signal calculates its values directly from session_id, close, and volume.

It is a Strategy definition, not a claim that the idea will perform well. The plugin declares separate long and short entry thresholds, their stop extensions, and minimum and maximum reward-to-risk settings. The compact walkthrough below follows the pre-shipped plugin's plugin.py logic:

CODE
from collections.abc import Mapping

from strategy_library.examples.execution_requirements import BARS_VECTOR_REQUIREMENTS

from arizmic.strategy_sdk import (
    ParameterDeclaration,
    ParameterValue,
    StrategyBuilder,
    StrategyIR,
    ValueDType,
    sqrt,
)


class SessionVwapTrueZscoreMeanReversionPlugin:
    plugin_id = "examples.session_vwap_true_zscore_mean_reversion"
    plugin_version = "1"
    parameters = (
        ParameterDeclaration(
            parameter_id="long_entry_zscore",
            dtype=ValueDType.FLOAT,
            default=-2.0,
            min=-5.0,
            max=-0.25,
        ),
        ParameterDeclaration(
            parameter_id="long_stop_extension_zscore",
            dtype=ValueDType.FLOAT,
            default=0.25,
            min=0.05,
            max=2.0,
        ),
        ParameterDeclaration(
            parameter_id="short_entry_zscore",
            dtype=ValueDType.FLOAT,
            default=2.0,
            min=0.25,
            max=5.0,
        ),
        ParameterDeclaration(
            parameter_id="short_stop_extension_zscore",
            dtype=ValueDType.FLOAT,
            default=0.25,
            min=0.05,
            max=2.0,
        ),
        ParameterDeclaration(
            parameter_id="minimum_rr",
            dtype=ValueDType.FLOAT,
            default=1.5,
            min=0.1,
            max=10.0,
        ),
        ParameterDeclaration(
            parameter_id="maximum_rr",
            dtype=ValueDType.FLOAT,
            default=4.0,
            min=0.25,
            max=20.0,
        ),
    )

    def build_ir(
        self,
        overrides: Mapping[str, ParameterValue] | None = None,
    ) -> StrategyIR:
        values = {item.parameter_id: item.default for item in self.parameters}
        values.update(overrides or {})
        builder = StrategyBuilder("session_vwap_true_zscore_mean_reversion")
        zscore = builder.input(
            "session_vwap_true_zscore",
            source="signal_output",
            ref="session_vwap_true_zscore:seed1:zscore",
            dtype="float",
            description="True session VWAP z-score SignalOutput.",
        )
        weighted_sd = builder.input(
            "session_vwap_weighted_sd",
            source="signal_output",
            ref="session_vwap_true_zscore:seed1:weighted_sd",
            dtype="float",
            description="Cumulative session VWAP weighted SD used for stop-distance sizing.",
        )
        long_entry = builder.param("long_entry_zscore", "float", float(values["long_entry_zscore"]), bounds=(-5.0, -0.25))
        long_extension = builder.param("long_stop_extension_zscore", "float", float(values["long_stop_extension_zscore"]), bounds=(0.05, 2.0))
        short_entry = builder.param("short_entry_zscore", "float", float(values["short_entry_zscore"]), bounds=(0.25, 5.0))
        short_extension = builder.param("short_stop_extension_zscore", "float", float(values["short_stop_extension_zscore"]), bounds=(0.05, 2.0))
        minimum_rr = builder.param("minimum_rr", "float", float(values["minimum_rr"]), bounds=(0.1, 10.0))
        maximum_rr = builder.param("maximum_rr", "float", float(values["maximum_rr"]), bounds=(0.25, 20.0))

        long_static_rr = (0.0 - long_entry) / long_extension
        short_static_rr = short_entry / short_extension
        long_stop = long_entry - long_extension
        short_stop = short_entry + short_extension
        long_target = (
            (0.0 - zscore) + (long_extension * maximum_rr)
            - sqrt(((0.0 - zscore) - (long_extension * maximum_rr)) ** 2.0)
        ) / 2.0
        short_target = (
            zscore + (short_extension * maximum_rr)
            - sqrt((zscore - (short_extension * maximum_rr)) ** 2.0)
        ) / 2.0
        long_rr_target = long_entry + (long_extension * maximum_rr)
        short_rr_target = short_entry - (short_extension * maximum_rr)

        builder.long_entry((zscore < long_entry) & (long_static_rr >= minimum_rr))
        builder.short_entry((zscore > short_entry) & (short_static_rr >= minimum_rr))
        builder.exit_when(
            "long", zscore <= long_stop,
            rule_id="long_zscore_stop",
            display_label="Long true z-score stop",
            display_purpose="stop_loss",
        )
        builder.exit_when(
            "short", zscore >= short_stop,
            rule_id="short_zscore_stop",
            display_label="Short true z-score stop",
            display_purpose="stop_loss",
        )
        builder.exit_when(
            "long", (zscore >= 0.0) | (zscore >= long_rr_target),
            rule_id="long_vwap_or_max_rr",
            display_label="Long VWAP or max RR target",
            display_purpose="take_profit",
        )
        builder.exit_when(
            "short", (zscore <= 0.0) | (zscore <= short_rr_target),
            rule_id="short_vwap_or_max_rr",
            display_label="Short VWAP or max RR target",
            display_purpose="take_profit",
        )
        builder.risk(
            long_stop_price_distance=long_extension * weighted_sd,
            short_stop_price_distance=short_extension * weighted_sd,
            long_target_price_distance=long_target * weighted_sd,
            short_target_price_distance=short_target * weighted_sd,
        )
        builder.position_rule(
            allow_long=True,
            allow_short=True,
            signal_conflict_policy="enter_neither",
        )
        return builder.build(
            name="Session VWAP True Z-Score Mean Reversion",
            description="Mean-reversion Strategy using true session VWAP z-score.",
            execution_requirements=BARS_VECTOR_REQUIREMENTS,
        )


strategy_plugin = SessionVwapTrueZscoreMeanReversionPlugin()

Read it from top to bottom:

  • The builder.input() calls name two exact, versioned outputs from one Signal. That Signal calculates Session VWAP, weighted standard deviation, and z-score directly from Dataset fields; there is no upstream Signal to resolve.
  • builder.param() exposes typed, bounded settings. A Study can vary values inside those bounds; it cannot silently add a new setting.
  • Long entry needs a sufficiently negative z-score and short entry a sufficiently positive one. Both also require that the selected entry and stop extension meet minimum_rr before an entry is permitted.
  • Stops extend beyond the entry threshold. Targets return toward zero (Session VWAP) but are limited by maximum_rr; the selected z-score distances are multiplied by the current weighted standard deviation for price-distance risk fields.
  • enter_neither prevents an entry when long and short conditions conflict.
  • BARS_VECTOR_REQUIREMENTS declares bar data and Vector execution as the baseline. It does not make every Dataset, Study request, or later execution mode valid automatically.
  • The final strategy_plugin binding is what local Strategy Library discovery loads from the plugin's plugin.py entry point.

The bundled test builds this definition, compiles it for Vector and Fast paths, and verifies matching program hashes. It also checks the two Signal inputs, risk distances, parameter set, and stop/target exit purposes. For your own plugin, test the actual rule outcomes as well as the definition shape.

Local authoring loop

  1. Create a dedicated folder in the configured local Strategy Library with a plugin.py entry point.
  2. Define the plugin identity, parameters, and typed Strategy builder.
  3. Test normal rules, parameter edges, missing inputs, and invalid structures.
  4. Run the SDK validation and compile checks available in your development environment.
  5. Open Strategies -> Strategy Library and use Sync for that plugin, or Sync Plugins for a deliberate library-wide rescan.
  6. Inspect the imported Strategy, then validate it with a real Dataset and time window in Studies.

The Strategy Library is the confirmation point: verify the source, exact version, inputs, parameters, requirements, and rules it displays after sync.

Version deliberately

Publish a new version when you change what the Strategy means: rule logic, input semantics, parameter interpretation, entries, exits, position behavior, risk behavior, or requirements. Keep previous versions when you need to understand earlier results or compare a change.

Changing a display label or local Library group does not change the Strategy's meaning and does not need a semantic version change.

Important boundaries

Syncing a Python Strategy does not prove that it is profitable, ready for every data source, or approved for execution. It only makes a reviewed local Strategy definition available for inspection and later validation.

For a no-code way to brainstorm the rules before you write Python, use Author with AI Companion.