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 issenpi-onboard(bootstrap + welcome),senpi-entrypoint(post-onboarding routing + the strategy-intent router + the fleet catalog), andsenpi-trading-runtime(this skill — the build path). If you only seesenpi-trading-runtimeinstalled 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):
▶ 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 completeruntime.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 fromstrategy-creation.mdfor 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.mdthat'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:
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'sproducer.py+runtime.yaml. Path A: clone the closest example. Path B: use it as your structural template.references/python-producer-sdk.md— build (Path B) or tune (Path A) the producer on the bundledsenpi_runtime_helpersSDK (SenpiClient,producer_daemon,scanner_lock,tick_cache). Don't hand-roll MCP calls or the daemon loop.references/yaml-schema.md— configureruntime.yaml(scanners, actions, decision gate).references/risk-gates.md+references/dsl-configuration.md— declare risk guard-rails and tune the two-phase exit.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 callcreate_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_LIMITentries + exits) — fee-optimized limit orders with optional taker fallback for assured fills. - Operator visibility —
senpi-helpersCLI (list / health / stats / stop / restart) for producer daemons; HTTP/stateand/healthendpoints for external monitors. Seesenpi-helpersCLI.
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_TOKENenvironment variableopenclaw.jsonatplugins.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:
- Momentum-guarded quickstart →
references/momentum-guarded-strategy.md - Bare position-tracker (DSL only) →
references/strategy-examples.md, oropenclaw senpi guide examplesfor a minimal template
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:
- Runtime is
runninginruntime list. - Every scanner in the YAML has executed at least once and recently (
state.components.scanners.state.scanners[].lastRunFinishedAt). - 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'srunCount > 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)
- Create the strategy wallet via Senpi MCP (or confirm it already exists).
- Put that wallet address into the runtime YAML (
strategy.wallet/${WALLET_ADDRESS}). - 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:
- Call
strategy_listand collectstrategies[].strategyWalletAddress. - If the provided wallet matches an entry (case-insensitive), validation passes — proceed to runtime install.
- If the list is empty OR the wallet is not in the list: confirm with the user (including the
initialBudgetin USDC), then callstrategy_create_custom_strategy. - Use the returned strategy wallet address as
WALLET_ADDRESSin 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 callstrategy_listto 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:
initialBudgetis 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
statewhenstatusreports 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 inspectfor 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.md— catalog 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.md—SignalItemwire format consumed byPOST /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:
- Is the target runtime in
runtime listwithstatus: running? - For every scanner declared in the YAML, has it executed at least once and recently? Walk
state.components.scanners.state.scanners[]— for interval scanners checklastRunFinishedAtis within2 × intervalSeconds; for external scanners checkrunCount > 0andlastRunFinishedAtis within (producer cadence + buffer). - 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'swallet+scannerfields) against running runtimes' external scanners fromstate. Report orphans on either side. Confirm health withsenpi-helpers health <daemon-name>— exit code 0 meanshealthy; 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
statusis a useful first signal but does not prove liveness for external scanners — push-driven scanners are reportedhealthyeven withrunCount === 0, because the health layer cannot distinguish "waiting for first ingest" from "broken pipe."statusaggregates 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 --jsonfield-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 === 0andlastRunFinishedAtis null — the scheduler hasn't picked it up.lastError:<message>. Recommend recreating the runtime." - "External scanner
external_momentumhasrunCount === 0. No daemon for(wallet=0x…, scanner=external_momentum)appears insenpi-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_pctsets a hard loss cap;retrace_thresholdtrails the high-water mark. - Phase 2 (after first tier reached): locks in profits. Each tier's
lock_hw_pctsets 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 ofphase1/phase2) and evaluated in both phases as outer-bound protections, not Phase-1 patience knobs.hard_timeoutfires whenever elapsed time exceeds its interval, regardless of phase or handoff status (a deliberate test-locked behavior).weak_peak_cutevaluates in both phases too, but itspeakROE < min_valueguard makes it effectively Phase 1 only whenmin_valueis set below the first tier'strigger_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 Schema —
SignalItemwire format consumed byPOST /signals: routing fields vs.data, validation, per-item error codes, batch semantics senpi-helpersCLI — Operator CLI for producer daemons: list / health / stats / stop / restart with exit codes and JSON envelopes