--- name: dsl-dynamic-stop-loss description: >- Automated trailing stop loss for leveraged perpetual positions on Hyperliquid. Monitors price via cron, ratchets profit floors through configurable tiers, and auto-closes positions on breach via mcporter โ€” no agent intervention for the critical path. Works for LONG and SHORT. ROE-based (return on margin) tier triggers that automatically account for leverage. Use when protecting an open Hyperliquid perp position, setting up trailing stops, managing profit tiers, or automating position exits on breach. license: Apache-2.0 compatibility: >- Requires python3, mcporter (configured with Senpi auth), and cron. Hyperliquid perp positions only. metadata: author: jason-goldberg version: "4.0" platform: senpi exchange: hyperliquid --- # Dynamic Stop Loss (DSL) v4 Automated trailing stop loss for leveraged perp positions on Hyperliquid. Monitors price via cron, ratchets profit floors upward through configurable tiers, and **auto-closes positions on breach** โ€” no agent intervention required for the critical path. ## Self-Contained Design ``` Script handles: Agent handles: โœ… Price monitoring ๐Ÿ“ข Telegram alerts โœ… High water tracking ๐Ÿงน Cron cleanup (disable after close) โœ… Tier upgrades ๐Ÿ“Š Portfolio reporting โœ… Breach detection ๐Ÿ”„ Retry awareness (pendingClose alerts) โœ… Position closing (via mcporter, with retry) โœ… State deactivation โœ… Error handling (fetch failures) ``` The script closes positions directly via `mcporter`. If the agent is slow, busy, or restarting, the position still gets closed on the next cron tick. ## How It Works ### Phase 1: "Let It Breathe" (uPnL < first tier) - **Wide retrace**: 3% from high water mark - **Patient**: requires 3 consecutive breach checks below floor - **Absolute floor**: hard price floor to cap max loss - **Goal**: Don't get shaken out before the trade develops ### Phase 2: "Lock the Bag" (uPnL โ‰ฅ first tier) - **Tight retrace**: 1.5% from high water mark (or per-tier retrace) - **Quick exit**: 1โ€“2 consecutive breaches to close - **Tier floors**: ratchet up as profit grows โ€” never go back down - **Effective floor**: best of tier floor and trailing floor ### ROE-Based Tier Ratcheting All tier triggers use ROE (Return on Equity): `PnL / margin ร— 100`. This means a `triggerPct: 10` fires at 10% return on margin, not 10% price move. Leverage is accounted for automatically. Tiers are defined as `{triggerPct, lockPct}` pairs. Each tier can optionally specify its own `retrace` value to tighten stops as profit grows: ```json "tiers": [ {"triggerPct": 10, "lockPct": 5}, {"triggerPct": 20, "lockPct": 14}, {"triggerPct": 30, "lockPct": 22, "retrace": 0.012}, {"triggerPct": 50, "lockPct": 40, "retrace": 0.010}, {"triggerPct": 75, "lockPct": 60, "retrace": 0.008}, {"triggerPct": 100, "lockPct": 80, "retrace": 0.006} ] ``` The gap between trigger and lock (e.g., 10% trigger โ†’ 5% lock) gives breathing room so a minor pullback after hitting a tier doesn't immediately close. **Ratchets never go down** โ€” once you hit Tier 2, Tier 1's floor is permanently superseded. See [references/tier-examples.md](references/tier-examples.md) for LONG and SHORT worked examples with exact price calculations. ### Direction Matters > โš ๏ธ **CRITICAL โ€” Getting direction backwards causes immediate false breaches or no protection at all.** The script handles this automatically via the `direction` field, but double-check when initializing state files manually. | | LONG | SHORT | |---|---|---| | **Tier floor** | `entry ร— (1 + lockPct / 100 / leverage)` | `entry ร— (1 - lockPct / 100 / leverage)` | | **Absolute floor** | Below entry (e.g., entry ร— 0.97) | Above entry (e.g., entry ร— 1.03) | | **High water** | Highest price seen | Lowest price seen | | **Trailing floor** | `hw ร— (1 - retrace)` | `hw ร— (1 + retrace)` | | **Breach** | `price โ‰ค floor` | `price โ‰ฅ floor` | | **uPnL** | `(price - entry) ร— size` | `(entry - price) ร— size` | ### Breach Decay When price recovers above the floor: - `"hard"` (default): breach count resets to 0 - `"soft"`: breach count decays by 1 per check Soft mode is useful for volatile assets where price rapidly oscillates around the floor. ### Floor Resolution At each check, the effective floor is the **best** of: 1. **Tier floor** โ€” locked profit level (Phase 2 only) 2. **Trailing floor** โ€” from high water mark and retrace % 3. **Absolute floor** โ€” hard minimum (Phase 1 only) For LONGs, "best" = maximum. For SHORTs, "best" = minimum. ## Architecture ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Cron: every 3-5 min (per position) โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ scripts/dsl-v4.py โ”‚ โ”‚ โ€ข Reads state from JSON file โ”‚ โ”‚ โ€ข Fetches price from allMids API โ”‚ โ”‚ โ€ข Direction-aware (LONG + SHORT) โ”‚ โ”‚ โ€ข Updates high water mark โ”‚ โ”‚ โ€ข Checks tier upgrades (ROE-based) โ”‚ โ”‚ โ€ข Per-tier retrace override โ”‚ โ”‚ โ€ข Calculates effective floor โ”‚ โ”‚ โ€ข Detects breaches (with decay modes) โ”‚ โ”‚ โ€ข ON BREACH: closes via mcporter w/retry โ”‚ โ”‚ โ€ข pendingClose if close fails โ”‚ โ”‚ โ€ข Outputs enriched JSON status โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ Agent reads JSON output: โ”‚ โ”‚ โ€ข closed=true โ†’ alert user, disable cron โ”‚ โ”‚ โ€ข pending_close=true โ†’ alert, will retry โ”‚ โ”‚ โ€ข tier_changed=true โ†’ notify user โ”‚ โ”‚ โ€ข status=error โ†’ log, check failures โ”‚ โ”‚ โ€ข Otherwise โ†’ silent โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` ## Files | File | Purpose | |------|---------| | `scripts/dsl-v4.py` | Core DSL engine โ€” monitors, closes, outputs JSON | | State file (JSON) | Per-position config + runtime state | **Multiple positions:** Set `DSL_STATE_FILE=/path/to/state.json` to run separate instances per position. Each gets its own state file and cron job. ## State File Schema See [references/state-schema.md](references/state-schema.md) for the complete schema with all fields documented. Minimal required fields to create a new state file: ```json { "active": true, "asset": "HYPE", "direction": "LONG", "leverage": 10, "entryPrice": 28.87, "size": 1890.28, "wallet": "0xYourStrategyWalletAddress", "strategyId": "uuid-of-strategy", "phase": 1, "phase1": { "retraceThreshold": 0.03, "consecutiveBreachesRequired": 3, "absoluteFloor": 28.00 }, "phase2TriggerTier": 1, "phase2": { "retraceThreshold": 0.015, "consecutiveBreachesRequired": 2 }, "tiers": [ {"triggerPct": 10, "lockPct": 5}, {"triggerPct": 20, "lockPct": 14}, {"triggerPct": 30, "lockPct": 22, "retrace": 0.012}, {"triggerPct": 50, "lockPct": 40, "retrace": 0.010}, {"triggerPct": 75, "lockPct": 60, "retrace": 0.008}, {"triggerPct": 100, "lockPct": 80, "retrace": 0.006} ], "currentTierIndex": -1, "tierFloorPrice": null, "highWaterPrice": 28.87, "floorPrice": 28.00, "currentBreachCount": 0, "createdAt": "2026-02-20T15:22:00.000Z" } ``` **`wallet` is required** โ€” the script uses it to call `close_position` on breach. ### Absolute Floor Calculation - **LONG:** `entry ร— (1 - maxLoss% / leverage)` โ€” e.g., 10x with 3% โ†’ `28.87 ร— (1 - 0.03/10)` = $28.78 - **SHORT:** `entry ร— (1 + maxLoss% / leverage)` โ€” e.g., 7x with 3% โ†’ `1955 ร— (1 + 0.03/7)` = $1,963.38 ## Output JSON The script prints a single JSON line per run. See [references/output-schema.md](references/output-schema.md) for the complete schema. Key fields for agent decision-making: | Field | Agent action | |-------|-------------| | `closed: true` | Alert user, disable cron | | `pending_close: true` | Alert โ€” close failed, retrying next tick | | `tier_changed: true` | Notify user with tier details | | `status: "error"` | Log; alert if `consecutive_failures >= 3` | | `breached: true` | Alert "โš ๏ธ BREACH X/X" | | `distance_to_next_tier_pct < 2` | Optionally notify approaching next tier | ## Cron Setup Per-position cron (every 3-5 min): ``` DSL_STATE_FILE=/data/workspace/dsl-state-BTC.json python3 scripts/dsl-v4.py ``` Stagger multiple positions by offsetting start times (:00, :01, :02). ## How to Set Up a New Position 1. Open position via Senpi API (`create_position`) 2. Create a state file with position details (see schema above) - **Double-check `direction`** โ€” controls all LONG/SHORT math - **Calculate `absoluteFloor` correctly** for the direction 3. Create a cron job (every 3-5 min) 4. DSL handles everything from there ### When a Position Closes 1. โœ… Script closes position via `mcporter call senpi close_position` (with retry) 2. โœ… Script sets `active: false` (or `pendingClose: true` if close fails) 3. ๐Ÿค– Agent disables the cron (reads `closed=true`) 4. ๐Ÿค– Agent sends alert to user If close fails, script sets `pendingClose: true` and retries next cron tick. ## Customization See [references/customization.md](references/customization.md) for conservative/moderate/aggressive presets and per-tier retrace tuning guidelines. ## API Dependencies - **Price**: Hyperliquid `allMids` API (direct HTTP, no auth) - **Close position**: Senpi `close_position` via mcporter > โš ๏ธ **Do NOT use `strategy_close_strategy`** to close individual positions. That closes the **entire strategy** (irreversible). Use `close_position`. ## Setup Checklist 1. Extract `scripts/dsl-v4.py` and `chmod +x` 2. Ensure `mcporter` is configured with Senpi auth 3. Create state file(s) per position 4. Set up cron: `DSL_STATE_FILE=/path/to/state.json python3 scripts/dsl-v4.py` 5. Agent reads output for alerts and cron cleanup 6. If `pending_close=true`, script auto-retries on next tick