ZERO strategy runners and plugins are paper-first proposal modules. They may
inspect market data and return a StrategySignal; they must not place orders,
manage custody, bypass risk checks, or call live execution endpoints.
The runtime keeps the safety boundary:
- Runner or plugin reads market data.
- Runner or plugin returns
StrategySignalorNone. - ZERO converts the signal to an
OrderIntent. PaperEngine.submitapplies risk limits.- The engine records accepted and rejected decisions.
Declarative runners are the preferred first contribution path because they are small, deterministic, and easy to review.
name: close-strength-yaml
version: 0.1.0
description: Declarative close-above-open paper runner.
paper_only: true
symbol: BTC
side: buy
quantity: 0.01
confidence: 0.8
condition:
type: close_above_open
min_move_pct: 0.01Load and submit through the helper:
runner = load_strategy_runner("examples/strategy-runner/close-strength.yaml")
order = propose_runner_order(runner, market, "BTC")
decision = engine.submit(order, source=f"strategy-runner:{runner.metadata.name}")Run the example:
just strategy-runner-exampleThe current dependency-free YAML subset supports simple key: value pairs and
one nested mapping for condition. Use JSON if the strategy needs a richer
shape.
Use StrategyPluginMetadata and implement propose when the strategy needs
Python code:
from dataclasses import dataclass
from zero_engine import MarketDataAdapter, Side, StrategyPluginMetadata, StrategySignal
@dataclass(frozen=True)
class MyPlugin:
metadata: StrategyPluginMetadata = StrategyPluginMetadata(
name="my-plugin",
version="0.1.0",
description="Paper-only example.",
)
def propose(self, market: MarketDataAdapter, symbol: str) -> StrategySignal | None:
latest = market.latest(symbol)
if latest.close <= latest.open:
return None
return StrategySignal(
symbol=latest.symbol,
side=Side.BUY,
confidence=0.8,
quantity=0.01,
reason="close above open",
)Submit through the helper so metadata validation always runs:
order = propose_plugin_order(plugin, market, "BTC")
decision = engine.submit(order, source=f"strategy-plugin:{plugin.metadata.name}")Public runners and plugins must:
- set
paper_only=True; - produce deterministic output from deterministic fixtures;
- include tests;
- name their decision source as
strategy-runner:<name>orstrategy-plugin:<name>; - keep all exchange and wallet access outside plugin code.
Public runners and plugins must not:
- read private keys or wallet secrets;
- submit orders directly;
- call
POST /executewithX-Zero-Mode: live; - start background processes;
- publish operator data.
See examples/strategy-runner and examples/strategy-plugin. For a slightly larger accepted/no-signal plugin example, see examples/momentum-strategy-plugin.
Run:
just strategy-runner-example
just strategy-plugin-example
just momentum-strategy-plugin-exampleBefore opening a pull request:
just ci