dsl-tight
DSL-Tight — Opinionated Stop-Loss Preset (v5)
A tighter, more opinionated variant of DSL for aggressive profit protection. Uses the same script and architecture as DSL v5 (dsl-v5.py) — strategy-scoped cron, MCP clearinghouse, state under {DSL_STATE_DIR}/{strategyId}/{asset}.json. All tier triggers are ROE-based (PnL / margin × 100); leverage is accounted for automatically.
Key difference from default DSL v5: DSL v5 is the configurable engine with maximum flexibility. DSL-Tight is the "just works" preset — fewer knobs, tighter defaults, per-tier breach counts, stagnation exits, and auto-calculated floors.
Core Concept
All thresholds defined in ROE. The script auto-converts to price levels:
price_floor = entry × (1 ± lockPct / 100 / leverage)
How It Works
Phase 1 — Absolute Floor (Stop-Loss)
- 5% ROE trailing floor
- 3 consecutive breaches required
- Auto-calculated absolute floor from entry/leverage/retrace
Phase 2 — Tiered Profit Lock
4 tiers that lock an increasing percentage of the high-water move:
| Tier | Trigger ROE | Lock % of HW Move | Breaches to Close |
|---|---|---|---|
| 1 | 10% | 50% | 3 |
| 2 | 20% | 65% | 2 |
| 3 | 40% | 75% | 2 |
| 4 | 75% | 85% | 1 |
Per-tier breach counts tighten as profit grows — at Tier 4 (75% ROE), a single breach closes immediately.
Stagnation Take-Profit
Auto-closes if:
- ROE ≥ 8% AND
- High-water mark hasn't improved for 1 hour
Catches winners that stall — takes the profit rather than waiting for a reversal.
Breach Mechanics
- Hard decay only (breach count resets to 0 on recovery)
- Per-tier breach requirements (3→2→2→1) replace the global Phase 1/Phase 2 split
- Floor is always:
max(tier_floor, trailing_floor)for LONG,min()for SHORT
State File Schema
State files live in the DSL v5 strategy directory: {DSL_STATE_DIR}/{strategyId}/{asset}.json (main dex) or {DSL_STATE_DIR}/{strategyId}/xyz--SYMBOL.json (xyz dex). See dsl-dynamic-stop-loss references/state-schema.md for path conventions.
{
"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.05,
"consecutiveBreachesRequired": 3
},
"phase2TriggerTier": 0,
"phase2": {
"retraceThreshold": 0.015,
"consecutiveBreachesRequired": 2
},
"tiers": [
{"triggerPct": 10, "lockPct": 50, "retrace": 0.015},
{"triggerPct": 20, "lockPct": 65, "retrace": 0.012},
{"triggerPct": 40, "lockPct": 75, "retrace": 0.010},
{"triggerPct": 75, "lockPct": 85, "retrace": 0.006}
],
"breachDecay": "hard",
"currentTierIndex": -1,
"tierFloorPrice": null,
"highWaterPrice": 28.87,
"floorPrice": 28.78,
"currentBreachCount": 0,
"createdAt": "2026-02-23T10:00:00.000Z"
}
Omit phase1.absoluteFloor — dsl-v5 fills it from entry and leverage. Set highWaterPrice to entry price, floorPrice to entry (or the computed absolute floor) for Phase 1 start.
Field Reference
| Field | Purpose |
|---|---|
phase1.absoluteFloor |
Not needed — auto-calculated from entry, leverage, retrace |
tiers[].breachesRequired |
Per-tier breach count (replaces global phase2 setting) |
tiers[].retrace |
Per-tier trailing stop tightness |
stagnation.enabled |
Enable stagnation take-profit |
stagnation.minRoePct |
Minimum ROE to trigger stagnation check |
stagnation.maxStaleSec |
Max seconds HW can be stale before auto-close |
Cron Setup
Same as DSL v5: one cron per strategy (not per position), every 3–5 minutes. The agent creates it when setting up DSL for a strategy.
Resolving the script path: When setting up the cron, locate the dsl-dynamic-stop-loss skill on disk (e.g. search the workspace, skills directory, or ClawHub install path for a folder named dsl-dynamic-stop-loss containing scripts/dsl-v5.py). Use that resolved path in the cron command — do not assume a fixed relative path. Example once resolved:
DSL_STATE_DIR=/data/workspace/dsl DSL_STRATEGY_ID=strategy-uuid python3 /path/to/dsl-dynamic-stop-loss/scripts/dsl-v5.py
No DSL_ASSET — the script discovers positions from MCP clearinghouse and state files in the strategy dir.
Clock-aligned schedule (OpenClaw): For runs at fixed 3-minute boundaries (e.g. :00, :03, :06…), create the job with a cron expression: "schedule": { "kind": "cron", "expr": "*/3 * * * *", "tz": "UTC" }. See dsl-dynamic-stop-loss SKILL.md Cron Setup and references/explained.md §0.
Agent responsibilities (same as DSL v5): on strategy_inactive remove cron and run strategy cleanup; on closed=true alert user; on pending_close=true alert and retry next tick.
Key Safety Features
- Same as DSL v5: strategy active check, reconcile state vs clearinghouse, delete state on close
- Auto-calculated floors eliminate manual math errors
- Per-tier breach tightening means large profits get maximum protection
- Stagnation TP prevents stalled winners from reversing
Example Walkthrough
10x LONG entry at $28.87:
- Phase 1: Floor auto-calculated ~$28.73. Price rises, HW tracks.
- Tier 1 at 10% ROE ($29.16): Floor locks at ~$29.01, 3 breaches to close.
- Tier 2 at 20% ROE ($29.45): Floor locks at ~$29.24, now only 2 breaches.
- Stagnation: Price stalls at 12% ROE for 65 min → auto-closed with profit.
- Tier 4 at 75% ROE ($31.04): Floor at ~$30.71. Single breach = instant close.
Script and Dependencies
Uses dsl-v5.py and dsl-cli.py from the dsl-dynamic-stop-loss skill. Install that skill first. Preferred setup: call dsl-cli.py add-dsl with --configuration @dsl-tight/dsl-profile.json (see "Setup via DSL CLI" above). When setting up cron or running the script, locate the dsl-dynamic-stop-loss skill on disk (search workspace/skills/install path for the folder containing scripts/dsl-v5.py and scripts/dsl-cli.py) and use that path. State files go in {DSL_STATE_DIR}/{strategyId}/{asset}.json (main) or xyz--SYMBOL.json (xyz). For cron setup, path conventions, cleanup on strategy inactive, and output schema, follow the dsl-dynamic-stop-loss skill.