senpi-trading-runtime

star 90

The Senpi Trading Runtime OpenClaw plugin runs automated trading strategies on Hyperliquid end-to-end: external producers push signals over POST /signals, rule-based or LLM-gated actions decide whether to open positions, declarative risk guard rails enforce daily caps and drawdown halts, FEE_OPTIMIZED_LIMIT orders execute maker-first with optional taker fallback, the position_tracker scanner detects on-chain position changes (including positions opened manually or by other tools), and the DSL exit engine applies two-phase trailing stop-loss protection. A bundled stdlib-only Python Producer SDK (senpi_runtime_helpers — SenpiClient, producer_daemon, scanner_lock, tick_cache, parallel) is the canonical way to author push producers; a senpi-helpers operator CLI manages running daemons. Use when a user needs to create/install/list/delete runtime YAMLs, configure DSL phases/tiers/time-cuts, set up an external_scanner with an LLM decision gate, declare risk guard_rails, inspect DSL-tracked positions, check runtime +

Senpi-ai By Senpi-ai schedule Updated 6/9/2026

name: senpi-trading-runtime description: "The Senpi Trading Runtime OpenClaw plugin runs automated trading strategies on Hyperliquid end-to-end: external producers push signals over POST /signals, rule-based or LLM-gated actions decide whether to open positions, declarative risk guard rails enforce daily caps and drawdown halts, FEE_OPTIMIZED_LIMIT orders execute maker-first with optional taker fallback, the position_tracker scanner detects on-chain position changes (including positions opened manually or by other tools), and the DSL exit engine applies two-phase trailing stop-loss protection. A bundled stdlib-only Python Producer SDK (senpi_runtime_helpers — SenpiClient, producer_daemon, scanner_lock, tick_cache, parallel) is the canonical way to author push producers; a senpi-helpers operator CLI manages running daemons. Use when a user needs to create/install/list/delete runtime YAMLs, configure DSL phases/tiers/time-cuts, set up an external_scanner with an LLM decision gate, declare risk guard_rails, inspect DSL-tracked positions, check runtime + daemon health, or write a Python producer that ingests external signals. ALSO load this skill when the user asks ANY of: 'build a trading strategy', 'build a new strategy', 'create a strategy', 'what trading strategies can we create', 'what types of strategies', 'what templates exist', 'show me the templates', 'help me pick a strategy', 'recommend a strategy', 'what should I trade', 'autonomous trading strategies', 'autonomous agents', 'write my own scanner', 'write a producer', or any open-ended 'strategy' query that is NOT an explicit specific-position request (those go to MCP strategy_create_custom_strategy) and NOT an explicit copy-trade request (those go to MCP strategy_create). Triggers on mentions of senpi, trading runtime, DSL exit, stop-loss tiers, position tracker, trailing stop, openclaw senpi, dsl_preset, decision_mode llm, risk guard_rails, FEE_OPTIMIZED_LIMIT, strategy YAML configuration, runtime status, runtime health, system state, scanner health, external_scanner producer, push_signal, signal POST, scanner_lock, tick_cache, producer_daemon, senpi_runtime_helpers, senpi-helpers CLI, build a strategy, autonomous strategy, custom strategy authoring, strategy templates, producer patterns, picker flow, install_skill, catalog.json, strategy-creation, strategy-intent-routing, or any other senpi strategy authoring / template selection query. The 'classify the user's intent FIRST' table at the top of this skill is the canonical disambiguation — read it before responding." license: Apache-2.0 metadata: author: Senpi version: "2.3" platform: senpi exchange: hyperliquid

Senpi Trading Runtime — OpenClaw Plugin

On-chain position tracker with automated DSL (Dynamic Stop-Loss) exit engine. Monitors a wallet's positions on Hyperliquid for lifecycle events (open, close, edit, flip) and applies two-phase trailing stop-loss protection to all positions.

Install location (OpenClaw-managed hosts): Senpi skills live at /data/.openclaw/skills/. The Senpi skill family is senpi-onboard (bootstrap + welcome), senpi-entrypoint (post-onboarding routing + the strategy-intent router + the fleet catalog), and senpi-trading-runtime (this skill — the build path). If you only see senpi-trading-runtime installed and not the other two, follow the raw-URL pointers in this doc — they work regardless of install state.

▶ Before you read this: classify the user's intent

The word "strategy" is overloaded — and defaulting to the build-from-scratch path when the user just wanted a recommendation is the most common silent failure. This skill is only for the build-a-new-autonomous-agent path (author code + runtime.yaml + DSL). Before you read further, do this:

If the user's intent is anything other than an explicit, specific build-from-scratch request, ASK them to disambiguate:

"Do you want to (a) open a specific position you already have in mind, (b) copy a specific trader, or (c) get help picking or building a strategy from our catalog?"

Then route accordingly:

User intent Path
(a) "go long HYPE 10x", "open a short on BTC" — specific named position MCP strategy_create_custom_strategy — execute directly. Stop reading this doc.
(b) "copy 0x...", "mirror this OG" — explicit copy of a named trader MCP strategy_create — mirror them. Stop reading this doc.
(c) "help me pick a strategy", "what should I trade", "recommend a strategy", "what templates exist" — open-ended, wants help deciding Template-first picker: walk the Decision Tree in references/producer-patterns.md — the canonical routing source (Layers 0–3). Cross-reference catalog.json for install metadata (name, tagline, install command, min_budget). Recommend 2–3 templates, install via install_skill. Build-from-scratch is the fallback, not the default. Stop reading this doc unless the user explicitly picks "build new".
The user explicitly chose "build new", or no fleet template fits their thesis Keep reading.

Never default to strategy_create_custom_strategy for an ambiguous "what should I trade?" or "help me pick a strategy" query. That tool is for positions the user named. Using it for open-ended queries surfaces a manual-position basket where the user wanted strategy recommendations — the exact failure mode this section exists to prevent.

The fleet install metadata (use for case (c), after the Decision Tree has narrowed to a candidate set):

curl -s https://raw.githubusercontent.com/Senpi-ai/senpi-skills/refs/heads/main/catalog.json

catalog.json is install/display metadata only — names, emojis, taglines, min_budget, version. All recommendation logic lives in the Decision Tree in producer-patterns.md.

Full router with the picker flow and the what-never-to-do list (raw URL — works whether or not senpi-entrypoint is installed):

https://raw.githubusercontent.com/Senpi-ai/senpi-skills/main/senpi-entrypoint/references/strategy-intent-routing.md

▶ Building a new autonomous strategy? Read the fast path first

Start here: references/strategy-creation.md. It's the self-contained fast path — the 5-step flow, the producer-only-emits-signals invariant, an archetype→example→DSL-preset table, an inline producer skeleton, a complete runtime.yaml, the DSL presets, and the gotchas, all in one fetch. You should not need to read anything else to ship a working strategy. The sections below are the deep reference, linked from strategy-creation.md for edge cases.

This skill is the canonical way to build a Senpi strategy. Every strategy is built the same way — on this runtime, with the Producer SDK, DSL exit engine, and risk guard-rails. There are two on-ramps to that one structure:

  • Path A — Start from a template. Something close to your idea already exists. Pick the closest archetype in producer-patterns.md, clone the linked example agent, and swap in your asset / thresholds. Fastest path when a near-match exists.
  • Path B — Bring your own scanner/producer. Your signal logic is novel and no template fits. Pick the archetype in producer-patterns.md that's structurally closest to your approach, then write a new producer on the SDK. Your signal logic is yours; the surrounding structure is standard.

Both paths follow the same read-order:

  1. references/producer-patterns.md — pick the archetype (universe trend-follower, single-asset alpha hunter, trader-follower, funding fade, etc.). Each links to a working example agent's producer.py + runtime.yaml. Path A: clone the closest example. Path B: use it as your structural template.
  2. references/python-producer-sdk.md — build (Path B) or tune (Path A) the producer on the bundled senpi_runtime_helpers SDK (SenpiClient, producer_daemon, scanner_lock, tick_cache). Don't hand-roll MCP calls or the daemon loop.
  3. references/yaml-schema.md — configure runtime.yaml (scanners, actions, decision gate).
  4. references/risk-gates.md + references/dsl-configuration.md — declare risk guard-rails and tune the two-phase exit.
  5. references/senpi-helpers-cli.md — verify the daemon is alive and ticking after deploy.

The invariant (both paths)

Whether you cloned a template or wrote your own scanner, the contract is identical:

  • The producer ONLY emits signals via push_signal / POST /signals. It does not call create_position, does not write its own stop-loss / exit logic, and does not hand-roll a daemon loop or risk checks.
  • The runtime owns execution, DSL exits, and risk guard-rails for every strategy — always. That's what keeps a bring-your-own scanner on the same rails as a proven template.

A "custom strategy" means custom signal logic inside a standard producer, not a custom harness. If you find yourself calling create_position or writing exit logic in the producer, stop — that belongs to the runtime.

Recommended for every strategy: attach DSL

Recommend DSL (Dynamic Stop-Loss) on every strategy, by default — both paths, every time. It's the runtime's two-phase trailing exit (Phase 1 caps the loss, Phase 2 ratchets profit as the position runs) and it's the layer that keeps a position from running unprotected if the producer goes quiet, the host restarts, or an entry fills late. A strategy with no DSL carries naked-position risk: when anything interrupts the producer or the fill, nothing is managing the exit.

The user and their agent decide for themselves — DSL can be tuned or turned off — but the recommended default is DSL on, for every strategy. When proposing a strategy, default to attaching it and only drop it if the user explicitly opts out.

Pick a preset — don't hand-author from zero. Four named DSL presets cover the strategy spectrum (machine-readable in references/dsl-presets.yaml, copy-paste blocks + tuning in references/dsl-configuration.md):

Preset Use for
balanced ⭐ default General-purpose / unsure — breathes early, runner tier to +100%, smart time-cuts
let_winners_run Trend / breakout / momentum / trader-follower — widest, no time-cuts
mean_reversion Faders / contrarian / range unwinds — banks the snapback fast
scalp High-frequency, fee-sensitive — tightest, fast locks + short timeouts

When creating a strategy, prompt the user to pick a preset (or hand-author a custom one — fully supported). If the user has given the agent full autonomy to design the strategy, the agent picks the preset itself by matching the producer archetype: trend-follower / single-asset / striker / trader-follower → let_winners_run; funding-fade / contrarian → mean_reversion; volume-engine / high-frequency → scalp; anything else or unsure → balanced.

Then tune from there — see references/dsl-configuration.md for the full field reference, and references/risk-gates.md for the declarative risk guard-rails that pair with it.

Full worked examples: references/strategy-examples.md. The rest of this doc is the reference for each piece.

Core Concepts

Python Producer SDK ships with this skill at senpi_runtime_helpers/. When wiring up an external_scanner (a Python producer that pushes signals into this runtime), build it on this SDK — it wraps the /signals endpoint, the SignalItem schema, the per-tick lock, and the long-running daemon scheduler in one stdlib-only Python package. Recipes and rules: see Python Producer SDK below.

SignalItem shape (per runtime-api/routes/signals.schema.ts): top-level address, scanner, asset, direction, score (0..1), signal_type. The per-scanner data block is validated against the config.fields declared on the external_scanner. Keep asset and direction out of data — they're top-level routing fields. Putting them in data makes the runtime store two copies (signal.asset vs signal.meta.asset) and downstream consumers read inconsistently; the runtime rejects this with INVALID_REQUEST.

Flow:

  • Self-tracking (no external scanner): position_tracker scanner detects on-chain position changes → DSL exit engine manages trailing stops. Protects positions opened manually or by any tool on Hyperliquid.
  • Signal-driven (with external_scanner): external producer pushes signals via POST /signals → action decides (rule-based or LLM-gated) → opens position → position_tracker picks it up → DSL manages exits.

In both flows, strategy = wallet address. The wallet address is the strategy identifier everywhere.

Key insight: The position tracker enables DSL protection for ALL positions of a strategy address — including those opened manually on the exchange or by other tools.

Wallet uniqueness: Only one running runtime per wallet address is allowed. Installing a second runtime for the same wallet is rejected. Delete the existing runtime first, then install the new one.

Capabilities at a glance

  • On-chain position tracker + DSL trailing stop-loss — two-phase trailing exits with optional time-based cuts; protects positions opened manually or by any tool on Hyperliquid. See DSL Exit Engine.
  • External-scanner push ingest — runtime exposes POST /signals; any out-of-process producer can push signals. Non-Python producers implement the wire format directly. See Signal Schema.
  • Python Producer SDK — bundled with this skill; daemon-based; auto-self-terminates when the runtime is deleted or the scanner is renamed. See Python Producer SDK.
  • LLM-gated actions (decision_mode: llm, optional pass-through) — let an LLM evaluate each signal against current account + market state before opening. See Runtime YAML.
  • Declarative risk guard rails — daily loss cap, max entries per day, drawdown halt, consecutive-losses cooldown, per-asset cooldown. All in YAML, runtime-enforced. See Strategy YAML Reference.
  • Maker-first execution (FEE_OPTIMIZED_LIMIT entries + exits) — fee-optimized limit orders with optional taker fallback for assured fills.
  • Operator visibilitysenpi-helpers CLI (list / health / stats / stop / restart) for producer daemons; HTTP /state and /health endpoints for external monitors. See senpi-helpers CLI.

Deploy Checklist

Follow these steps in order for any strategy. Step 5 only applies when the YAML declares an external_scanner.

1. Pre-flight environment probe

Before proposing any command, probe your own environment so you know whether to execute directly or hand commands to the user:

openclaw --version                      # confirms the CLI is reachable
openclaw senpi status                   # confirms the senpi plugin is running on this host
printenv | grep -E 'RAILWAY|OPENCLAW'   # detects a Railway / OpenClaw-hosted environment

If those succeed, you can exec here — run the deployment commands yourself instead of handing the user copy/paste blocks. Only fall back to "please run this on your host" if the probe fails.

Where secrets live on an owned host. On the user's own OpenClaw host (self-hosted, Railway, etc.) the Senpi API key is already on disk — reading it from the expected locations is expected behavior, not a breach. Common locations:

  • $SENPI_AUTH_TOKEN environment variable
  • openclaw.json at plugins.entries.runtime.config.apiKey

Pass the key through env vars on the producer daemon's launch command (e.g. SENPI_AUTH_TOKEN=… nohup python3 -u <producer>.py …). Do not echo it into chat or log files.

2. Validate the strategy wallet

Call strategy_list via MCP (see the Strategy Wallet Validation section below). If the wallet is not in the list, confirm with the user before calling strategy_create_custom_strategy.

3. Write the YAML to disk

Never handwrite or re-stream a full strategy YAML into chat. Chat transports (Telegram, etc) frequently corrupt indentation and code fences in long YAML blocks — don't spend turns re-sending it. Instead, read the matching reference file in this skill and write it to disk in one command:

cat > <name>.yaml <<'YAML'
<contents from the matching reference file, with ${VAR} placeholders as-is>
YAML

Sources of truth by strategy type:

Only handwrite YAML when no shipped reference fits the user's ask — and still write it straight to disk via heredoc, never paste it into chat for the user to copy.

4. Create the runtime

openclaw senpi runtime create --path ./<name>.yaml

5. Wire producers (only if the YAML declares an external_scanner)

External scanners are push-driven — without a producer, the scanner stays silent. Build the producer using the Python Producer SDK (senpi_runtime_helpers) bundled with this skill and launch it as a long-running daemon via producer_daemon. See Python Producer SDK below for rules, the import shim, and a new-producer skeleton. For a catalog of producer/scanner archetypes (universe trend-follower, single-asset alpha hunter, trader-follower, funding-regime fade, etc.) with inline code snippets and links to working example producers, see references/producer-patterns.md. Strategy-specific env vars are documented in the consuming strategy skill.

After first launch, the daemon is managed with the bundled senpi-helpers operator CLI (list / health / stats / stop / restart).

6. Verify the strategy is live

Do not declare success on runtime list: running alone — that field is process status, not functional liveness. Run the full liveness verification (see Liveness Verification below and references/liveness-verification.md for the field-level decision tree).

At minimum, confirm all three self-questions pass:

  1. Runtime is running in runtime list.
  2. Every scanner in the YAML has executed at least once and recently (state.components.scanners.state.scanners[].lastRunFinishedAt).
  3. If the YAML declares an external_scanner: the producer daemon is running with a recent successful tick (senpi-helpers health <name>healthy), and the scanner's runCount > 0.

Quick commands:

openclaw senpi runtime list                # runtime shows status: running
openclaw senpi state --json                # field-level liveness data — walk per the reference
openclaw senpi status                      # health summary (do not rely on this alone for external scanners)
openclaw senpi dsl positions               # positions under DSL tracking (once opened)
senpi-helpers list                         # producer daemons running on this host
senpi-helpers health <daemon-name>         # one daemon's health; non-zero exit if degraded
openclaw senpi action history <action>     # LLM/rule decisions, if the strategy has actions

Strategy Wallet Validation (MCP-first)

The runtime does not create wallets — it links to an existing strategy wallet that you create separately via Senpi MCP. Installing a runtime is a linking operation, never a wallet-creation operation.

Wallet lifecycle (required order)

  1. Create the strategy wallet via Senpi MCP (or confirm it already exists).
  2. Put that wallet address into the runtime YAML (strategy.wallet / ${WALLET_ADDRESS}).
  3. Install/create the runtime — this links the runtime to the existing wallet for monitoring and exits.

Never treat openclaw senpi runtime create as wallet creation.

Validation guardrail (mandatory before runtime create/install)

Validation is agent-side, not user-side. Call strategy_list via MCP yourself — do not ask the user to check the Senpi UI or run the call on their host. If the wallet appears in strategies[].strategyWalletAddress (case-insensitive), validation passes and you proceed directly to runtime install. If it does not appear, confirm with the user before creating a new one.

MCP-first flow:

  1. Call strategy_list and collect strategies[].strategyWalletAddress.
  2. If the provided wallet matches an entry (case-insensitive), validation passes — proceed to runtime install.
  3. If the list is empty OR the wallet is not in the list: confirm with the user (including the initialBudget in USDC), then call strategy_create_custom_strategy.
  4. Use the returned strategy wallet address as WALLET_ADDRESS in the YAML, then install the runtime.

Hard rules:

  • Never use an embedded wallet / injected wallet for runtime linking.
  • Never treat a wallet as a strategy wallet unless it appears in strategy_list — a user saying "it's my strategy wallet" is not a substitute; still call strategy_list to confirm.
  • Runtime install is blocked until strategy wallet validation passes.
  • Always confirm with the user before creating a new strategy wallet, and explicitly confirm the budget (initialBudget) that will be used.

Example MCP flow:

strategy_list({})
if provided_wallet not in strategies[].strategyWalletAddress:
  # confirm with user first: create new strategy wallet + initialBudget
  strategy_create_custom_strategy({
    initialBudget: <budget_usdc>,
    positions: [],
    skillName: <runtime_name>,
    skillVersion: "1.0.0"
  })

Notes:

  • initialBudget is required when creating the strategy wallet.
  • Use positions: [] when you only need the strategy wallet created now and will trade later.

CLI Commands

All commands require the OpenClaw gateway running (openclaw gateway run). Print a full commands cheatsheet at any time with openclaw senpi --cheatsheet.

Runtime management

# Create a runtime from YAML file
openclaw senpi runtime create --path ./my-strategy.yaml

# Create with inline YAML content
openclaw senpi runtime create --content "<yaml>"

# Create with custom ID
openclaw senpi runtime create --path ./my-strategy.yaml --runtime-id my-name

# List installed runtimes (id, wallet, source, status)
openclaw senpi runtime list

# Delete a runtime by id or wallet address
openclaw senpi runtime delete --id <runtime_id>
openclaw senpi runtime delete --address <wallet>
openclaw senpi runtime delete <runtime_id>   # positional also works

DSL position inspection

# All active DSL-tracked positions
openclaw senpi dsl positions
openclaw senpi dsl positions --runtime <id>
openclaw senpi dsl positions --address <0x...>
openclaw senpi dsl positions --json

# Inspect one position (full DslState)
openclaw senpi dsl inspect <ASSET>
openclaw senpi dsl inspect SOL --runtime <id>
openclaw senpi dsl inspect BTC --json

# Archived (closed) positions
openclaw senpi dsl closes
openclaw senpi dsl closes --limit 20 --json

Health and system state

Use these commands to check whether the runtime is operating correctly and to diagnose issues.

status — lightweight health check. Use as a first step when something seems wrong (e.g., positions not being tracked, stop-losses not firing, scanners not running). Shows overall health, scanner summary, and DSL summary per runtime.

openclaw senpi status                  # Health for all running runtimes
openclaw senpi status --runtime <id>   # Health for a specific runtime
openclaw senpi status --json           # Raw JSON output

state — full runtime snapshot. Use when status shows a problem and you need to dig deeper — it includes scanner registration details, DSL monitor telemetry (tick counts, errors, timing), active positions, and state directory location.

openclaw senpi state                   # Full state for all running runtimes
openclaw senpi state --runtime <id>    # Full state for a specific runtime
openclaw senpi state --json            # Raw JSON output

When to use which:

  • Start with status — if everything shows healthy, the runtime is operating normally.
  • Move to state when status reports degraded/unhealthy and you need to understand why (e.g., which scanner is erroring, whether the DSL monitor is stuck, tick error counts).
  • Use dsl positions / dsl inspect for position-level detail (trailing stop floors, current tier, breach counts) — those are about individual positions, not runtime health.

In-shell reference

openclaw senpi guide              # Overview and quick command list
openclaw senpi guide scanners     # Scanner types and config fields
openclaw senpi guide actions      # Action types and decision modes
openclaw senpi guide dsl          # DSL exit engine reference
openclaw senpi guide examples     # Print minimal strategy YAML
openclaw senpi guide schema       # Full YAML schema
openclaw senpi guide version      # Plugin version and changelog URL

Diagnostic action commands

Use these when a trade didn't fire as expected, to audit decision-engine runs and execution history.

openclaw senpi action list                          # All registered actions with counters
openclaw senpi action inspect <action-name>         # Persisted latest state for one action
openclaw senpi action history [action-name]         # Rolling execution history
openclaw senpi action decisions [action-name]       # Rows where decision engine ran (LLM reasoning)

All accept --runtime <id>, --address <wallet>, --limit <n>, and --json.

Configuration

openclaw senpi config set-chat-id <chatId>           # Telegram notifications
openclaw senpi config set-senpi-jwt-token <token>     # Senpi MCP auth
openclaw senpi config set-state-dir <dir>             # State directory
openclaw senpi config get <key>
openclaw senpi config list
openclaw senpi config unset <key>
openclaw senpi config reset

Telegram notifications — two layers

Two independent mechanisms exist by design; they deliver different event classes, not duplicates.

Layer Set via Scope Events delivered
Runtime-wide infra channel openclaw senpi config set-chat-id <id> Per host (all runtimes) Plugin lifecycle, startup, infra / runtime-level errors
Per-strategy channel notifications.telegram_chat_id in YAML (usually via ${TELEGRAM_CHAT_ID}) Per strategy Position opens, closes, DSL exits, action decisions

Typical setup: set both to the same chat id. They are not redundant — they cover different event classes, so setting only one leaves a class unnotified.

Python Producer SDK

The runtime ships with senpi_runtime_helpers, a stdlib-only Python SDK for writing external-scanner producers. It is the canonical client for the runtime's /signals endpoint and replaces the legacy mcporter subprocess and openclaw senpi external-scanner ingest CLI patterns. Do not use openclaw cron add senpi-producer-… — that is the legacy fork-storm path the daemon was written to replace.

Rules — read before writing a producer

asset and direction are top-level, never inside data. The wire schema is additionalProperties: false. Routing fields live at the top level; scanner-specific fields go in data (validated against the scanner's config.fields).

# RIGHT
client.push_signal(
    address=wallet, scanner="my_signals",
    asset="BTC", direction="LONG",
    score=0.85,
    signal_type="MOMENTUM",
    data={"rsi": 75},
)

# WRONG — runtime rejects with INVALID_REQUEST
client.push_signal(
    address=wallet, scanner="my_signals",
    data={"asset": "BTC", "direction": "LONG"},
)

Pass signal_type= explicitly on every emission. The fallback is the scanner's defaultSignalType from runtime.yaml; most skills don't declare one, so omission lands empty type tags in audit logs and LLM decision context.

Depth lives in references

  • references/python-producer-sdk.md — import shim, full new-producer skeleton, batch + parallel recipes, error → fix table, operator CLI subcommands, logging format, SDK-tuning env vars.
  • references/producer-patterns.mdcatalog of the ~11 producer/scanner archetypes implemented by the active fleet (universe trend-follower, single-asset Kodiak family, trader-follower, funding-regime fade, cross-asset lag detector, etc.). Each pattern includes inline code snippets, distinctive MCP-call signature, typical tick interval + risk envelope, and direct GitHub URLs to a working example agent's producer.py + runtime.yaml. Start here when building a new strategy — pick a pattern, fetch the example, tune the thresholds.
  • references/signal-schema.mdSignalItem wire format consumed by POST /signals: routing fields vs. data, validation, per-item error codes, batch semantics.
  • references/senpi-helpers-cli.md — operator CLI deep dive: subcommand reference, exit codes, JSON envelopes.

JSON-line logs go to stderr prefixed [senpi_helpers]. Stdout stays clean.

Liveness Verification

openclaw senpi runtime list: running reports process status, not functional liveness. A runtime can be running while every component inside it is silent — scanners not ticking, DSL not evaluating positions, external scanners declared but no producer pushing data, actions wired but never invoked. Never declare the runtime live based on runtime list alone.

This is an agent-side check. Run the commands yourself (the pre-flight probe in Deploy Step 1 confirms you can exec here); do not ask the user to confirm "is it working?"

Self-questions checklist

Before claiming the runtime is operating, the agent must be able to answer yes to all three — with field values as evidence, not assertions:

  1. Is the target runtime in runtime list with status: running?
  2. For every scanner declared in the YAML, has it executed at least once and recently? Walk state.components.scanners.state.scanners[] — for interval scanners check lastRunFinishedAt is within 2 × intervalSeconds; for external scanners check runCount > 0 and lastRunFinishedAt is within (producer cadence + buffer).
  3. For every external scanner in a running runtime, is the paired producer daemon registered on this host and healthy? And vice versa? Reconcile senpi-helpers list --json (each entry's wallet + scanner fields) against running runtimes' external scanners from state. Report orphans on either side. Confirm health with senpi-helpers health <daemon-name> — exit code 0 means healthy; anything else is degraded.

DSL counters (tickSuccessCount, lastTickFinishedAt) are intentionally not in this checklist — they prove the runtime's internal tick loop moved, not that the protective path actually works (price providers, MCP, exchange connectivity are outside the runtime's visibility). Use openclaw senpi dsl inspect <ASSET> for per-position triage instead.

What you can and can't trust from senpi status

  • status is a useful first signal but does not prove liveness for external scanners — push-driven scanners are reported healthy even with runCount === 0, because the health layer cannot distinguish "waiting for first ingest" from "broken pipe."
  • status aggregates across components; a hung DSL monitor can be masked by healthy scanners. Drill into the DSL component specifically.
  • For anything more than a sanity check, walk state --json field-by-field per the decision tree.

Decision tree and reconciliation

For the per-component field rules (interval scanner, external scanner, action) and the daemon ↔ runtime reconciliation algorithm, see references/liveness-verification.md. It is the authoritative source on which fields prove operation versus silence.

Reporting back to the user

When liveness verification fails, surface the specific failing field and the remediation — never a generic "looks fine" or "still starting up." Examples:

  • "Position tracker scanner is registered but runCount === 0 and lastRunFinishedAt is null — the scheduler hasn't picked it up. lastError: <message>. Recommend recreating the runtime."
  • "External scanner external_momentum has runCount === 0. No daemon for (wallet=0x…, scanner=external_momentum) appears in senpi-helpers list — the producer was never launched. Run Deploy Checklist Step 5."

Runtime YAML

The runtime YAML drives all behavior. Top-level keys: name, strategy, scanners, actions, exit, risk, notifications.

name: my-tracker
version: 1.0.0
description: >
  On-chain position tracker with DSL trailing stop-loss.

strategy:
  wallet: "${WALLET_ADDRESS}"
  slots: 2
  margin_pct: 15
  trading_risk: conservative    # conservative | moderate | aggressive
  enabled: true

scanners:
  - name: position_tracker
    type: position_tracker
    interval: 10s

actions:
  - name: position_tracker_action
    action_type: POSITION_TRACKER
    decision_mode: rule
    scanners: [position_tracker]

exit:
  engine: dsl
  interval_seconds: 30            # how often the price monitor runs (5-3600)
  order_type: FEE_OPTIMIZED_LIMIT # MARKET (default) or FEE_OPTIMIZED_LIMIT
  fee_optimized_limit_options:
    ensure_execution_as_taker: true       # fall back to market if maker doesn't fill
    execution_timeout_seconds: 15         # seconds to wait for maker fill (1-300, default 45)
  dsl_preset:                     # single preset (no named map needed)
    hard_timeout:
      enabled: true
      interval_in_minutes: 360
    weak_peak_cut:
      enabled: true
      interval_in_minutes: 120
      min_value: 5
    dead_weight_cut:
      enabled: true
      interval_in_minutes: 60
    phase1:
      enabled: true
      max_loss_pct: 4.0
      retrace_threshold: 7
      consecutive_breaches_required: 1
    phase2:
      enabled: true
      tiers:
        - { trigger_pct: 7,  lock_hw_pct: 40 }
        - { trigger_pct: 12, lock_hw_pct: 55 }
        - { trigger_pct: 15, lock_hw_pct: 75 }
        - { trigger_pct: 20, lock_hw_pct: 85 }

notifications:
  telegram_chat_id: "${TELEGRAM_CHAT_ID}"

Environment variable substitution: ${VAR} and ${VAR:-default} resolved at load time.

The risk: block is optional — include it when the strategy needs guard rails (daily loss cap, max entries per day, drawdown halt, per-asset cooldown, etc.). See the momentum-guarded reference for a worked example and the YAML schema reference for the full field list.

For full field details: YAML Schema Reference

DSL Exit Engine — Key Concepts

Two-phase trailing stop-loss protecting open positions:

  • Phase 1 (entry → first tier): limits downside. max_loss_pct sets a hard loss cap; retrace_threshold trails the high-water mark.
  • Phase 2 (after first tier reached): locks in profits. Each tier's lock_hw_pct sets a floor as a % of the high-water ROE — the floor trails upward and never loosens.
  • Time-based cuts (hard_timeout, weak_peak_cut, dead_weight_cut) — declared at the preset level (siblings of phase1/phase2) and evaluated in both phases as outer-bound protections, not Phase-1 patience knobs. hard_timeout fires whenever elapsed time exceeds its interval, regardless of phase or handoff status (a deliberate test-locked behavior). weak_peak_cut evaluates in both phases too, but its peakROE < min_value guard makes it effectively Phase 1 only when min_value is set below the first tier's trigger_pct.

Tiers are profit milestones. trigger_pct = ROE % that activates the tier, lock_hw_pct = % of high-water ROE to lock as floor. Tiers must have strictly increasing trigger_pct.

Tuning guidance:

  • Higher max_loss_pct = more room before hard stop (conservative: 5-6%, aggressive: 2-3%)
  • Higher retrace_threshold = more tolerance for pullbacks from peak
  • Higher consecutive_breaches_required = more tolerant of temporary dips (1 = instant, 3 = sustained)
  • Longer time-cut intervals = more patience before cutting underperformers

For full DSL mechanics (retrace math, breach logic, close reasons, events): DSL Configuration Reference

Environment Variables

Variable Required Purpose
WALLET_ADDRESS Yes Strategy wallet address (used in YAML via ${WALLET_ADDRESS}).
SENPI_API_KEY For live MCP Senpi MCP authentication.
TELEGRAM_CHAT_ID No Telegram chat ID for notifications.
DSL_STATE_DIR No Override DSL state file directory.
OPENCLAW_WORKSPACE No Workspace root for skills (default /data/workspace). Used by the Python Producer SDK import shim.
SENPI_AUTH_TOKEN For Python producers MCP bearer token used by SenpiClient.
SENPI_MCP_URL No MCP endpoint (default https://mcp.prod.senpi.ai/mcp).
SENPI_RUNTIME_API_HOST No Runtime signals host (default 127.0.0.1).
SENPI_RUNTIME_API_PORT No Runtime signals port (default 8787).

SDK-tuning env vars (SENPI_HELPERS_STATE_DIR, _MCP_TIMEOUT, _SIGNAL_TIMEOUT, _MAX_CONCURRENT, _TICK_CACHE_TTL) are documented in references/python-producer-sdk.md. Defaults work for every host shipped today; tune only when you have a measured reason.

References

  • Momentum-Guarded Strategy — End-to-end quick-start strategy exercising external scanners, LLM decisions, risk gates, and DSL exits
  • Runtime Concepts — Conceptual explanation of scanners, actions, DSL phases, and what every field controls in trading terms
  • Strategy YAML Reference — Schema reference: top-level sections, building blocks, template variables, wiring rules, validation errors
  • DSL Configuration Reference — DSL exit engine: phases, tiers, time cuts, tuning guidance, close reasons, events
  • Strategy Examples — Position-tracker runtimes with different DSL tuning profiles
  • External Producers — Operations-level reference: how producers work end-to-end, common env vars, launch + restart
  • Python Producer SDK — Full SDK depth: import shim, new-producer skeleton, batch + parallel recipes, error → fix table, operator CLI, SDK-tuning env vars
  • Producer Patterns — Catalog of the ~11 producer/scanner archetypes implemented by the active fleet, with inline code snippets and GitHub URLs to working example producers. Start here when building a new strategy.
  • Liveness Verification — Field-level decision tree and daemon ↔ runtime reconciliation for confirming the runtime is actually operating, not just registered
  • Signal SchemaSignalItem wire format consumed by POST /signals: routing fields vs. data, validation, per-item error codes, batch semantics
  • senpi-helpers CLI — Operator CLI for producer daemons: list / health / stats / stop / restart with exit codes and JSON envelopes
Install via CLI
npx skills add https://github.com/Senpi-ai/senpi-skills --skill senpi-trading-runtime
Repository Details
star Stars 90
call_split Forks 29
navigation Branch main
article Path SKILL.md
More from Creator