About cheetah-strategy
cheetah-strategy is an automated trading strategy skill from Senpi for operators who screen entries with multiple signals and manage exits through rules. It combines trader consensus, price and volume signals, score-based leverage, and DSL risk controls; it emits trading signals while user-conversation sessions remain read-only.
Key features
- 01Score trading opportunities using multiple signals
- 02Set score-based leverage within asset limits
- 03Manage exits and stops through a DSL ladder
- 04Apply position deduplication, cooldowns, and risk gates
- 05Emit alerts for trader pools and position parsing
Use cases
- 01Screen entry signals using trader consensus and market momentum
- 02Set leverage by score while limiting asset risk
- 03Manage profit-taking, stops, and holding time with the DSL
- 04Review trading state without executing trades from conversation sessions
MCP support
The README mentions in-process MCP through senpi_runtime_helpers; the strategy producer sends signals using SenpiClient.push_signal(). Trading tools are reserved for the producer and DSL engine, while conversation sessions are read-only.
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ senpi-ai/ senpi-skills --skill cheetah-strategyAfter installing, type/cheetah-strategyin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
README.mdruntime.yamlconfig/cheetah-config.jsonreferences/skill-attribution.mdscripts/cheetah_config.pycheetah-producer.pyDetails
From the project README🐆 CHEETAH v7.2.0 — Multi-Signal Confluence Sniper
SM commits. Quality traders commit. Price confirms. Volume commits. All at once. Cheetah pounces once. The runtime DSL ratchets to lock the win.
v7.2.0 (2026-06-01) — DSL exit overhaul: disable time cuts, let winners run
Entries are fine. Exits were the bottleneck. A 100-trade-by-score analysis on the live wallet showed win rate scaling cleanly with score — 40.0% @ score 10, 41.7% @ score 11 (n=84), 50.0% @ score 12, 55.6% @ score 13 — confirming the accuracy filter works. But Avg ROE was negative across EVERY score bracket (−1.90% / −0.35% / −0.20% / −0.12%).
| Score | Trades | Win rate | Avg ROE |
|---|---|---|---|
| 10 | 5 | 40.0% | −1.90% |
| 11 | 84 | 41.7% | −0.35% |
| 12 | 2 | 50.0% | −0.20% |
| 13 | 9 | 55.6% | −0.12% |
Diagnosis: "death by a thousand cuts." The tight Phase 2 locks plus the 60m/90m time-cuts made it mechanically impossible to hold for a big win — Cheetah took the full brunt of −15% stops / −5% chops but was forced to give up every +15% / +30% runner. The two big HYPE longs that actually paid (+13.9%, +8.9%) only landed because they hit the 12h hard timeout — not a trailing stop, not a 60m dead_weight_cut. When forced to hold, the position printed.
Fix (mirrors the live deploy):
weak_peak_cut→ disabled (was 90m @ 3.0 — chopping winners-in-waiting)dead_weight_cut→ disabled (was 60m — forcing exits before trenders accelerate)hard_timeout→ kept (12h fail-safe — the one cut that captured the winners)- Phase 2 ladder already widened to the "let winners run" shape (T0 +10% / lock 0 → +100% / lock 85) on 2026-05-21
Together this removes the glass ceiling so heavy trenders can run. NO change to scoring, MIN_SCORE, leverage tiers, margin, cooldowns, or risk gates — exit-mechanics only. Hypothesis test; re-evaluate after ~50 more trades.
v7.1.1 (2026-05-11) — positions parser fix (patch)
Live verification of v7.1.0 found positions_map was STILL empty even though the widened filter returned the full 25-trader pool. Root cause: leaderboard_get_trader_positions actually returns
{ "data": { "positions": { "trader_id": ..., "rank": ..., "positions": [...] } } }
— the per-trader positions array is nested one level deeper than the schema guide documents. The v7.0/v7.1 parser looked at data.positions expecting a list, got a dict, hit if not isinstance(positions, list): continue and silently skipped every trader.
v7.1.1 parser handles BOTH shapes:
- list-direct (matches schema guide)
- nested-dict
{trader_id, rank, positions: [...]}(actual prod response)
Plus a POSITIONS_SHAPE_WARN log line for any future schema drift.
NO change to scoring components, MIN_SCORE, leverage tiers, margin, cooldowns, or DSL. Same thesis. Now the +3 QUALITY_TRADER bonus can actually fire when the per-asset overlap exists.
v7.1.0 (2026-05-11) — quality-trader filter widened (patch)
Post-migration diagnostic on the live wallet revealed quality-cache.json was always empty (positions_map = {}). Root cause: discovery_get_top_traders(time_frame=WEEKLY, consistency=[ELITE,RELIABLE], open_position_filter=True, limit=8) returned zero traders under the current platform regime. With no quality-trader pool, the +3 QUALITY_TRADER scoring bonus never fires; candidates cap at score 8 (needs 4-of-4 secondary confluence to reach the MIN_SCORE=10 floor without that bonus), and that 4-of-4 only hits in strong-trending regimes.
Filter changes (live-verified):
time_frame: WEEKLY → MONTHLY— broader history, more traders accumulate ELITE/RELIABLE labelsconsistency: [ELITE, RELIABLE] → [ELITE, RELIABLE, STREAKY]— slight loosen at the lower endlimit: 8 → 25— wider pool surfaces more per-asset overlapopen_position_filterdropped — the per-traderleaderboard_get_trader_positionsfetch already filters flat traders implicitly via empty position arrays
NO change to scoring components, weights, MIN_SCORE, leverage tiers, margin, cooldowns, or DSL. Same thesis, wider input.
Telemetry: producer now emits a QT_POOL_WARN log line whenever the trader pool is unhealthily small (< QT_POOL_SIZE / 2) or positions_map ends up empty. Future regressions surface in one grep instead of post-hoc archaeology.
v6.0 thesis (preserved from v5.x)
Refuse to trade unless ALL major signals align simultaneously:
| Component | Signal | Pts |
|---|---|---|
| SM consensus | pct_of_top_traders ≥ 10%, traders ≥ 25 | +4 (hard gate) |
| Velocity | 15m contrib ≥ 1.0 OR 1h contrib ≥ 3.0 | +2 (hard gate) |
| Accelerating | 15m > 1h > 0 (SM building) | +2 |
| Dual price | 4h move ≥ 2% AND 1h aligned same direction | +2 |
| Volume | current ≥ 2x 6h avg | +1 |
| Quality trader | ≥ 1 ELITE/RELIABLE positioned same direction | +3 |
| Rank climb | ≥ 5 positions in last 2 scans, top 15 | +1 |
Max score: 15. v6.0 floor: 10. Universe is the top 100 SM leaderboard (leaderboard_get_markets), refreshed every producer tick. XYZ permanently banned.
Score-scaled leverage:
| Score | Leverage |
|---|---|
| 10 | 3x |
| 11 | 5x |
| 12-13 | 7x |
| 14-15 | 8x |
Leverage is clamped to each asset's Hyperliquid max via strategy_get_asset_trading_limits (preserves v5.1.1 leverage safety fix).
v6.0 architectural changes
v1 scanner → v2 producer
- v5.x:
cheetah-scanner.pyscored signals, calledcreate_positiondirectly, maintained Python-side cooldowns/counters/resting-order guards - v6.0:
cheetah-producer.pyemits signals viaSenpiClient.push_signal()(direct HTTP POST). Runtime LLM gate (decision_mode: llm) is pass-through. Risk gates declarative inruntime.yaml.
MIN_SCORE 11 → 10
v5.2 tightened MIN_SCORE 10 → 11. Result: 8 consecutive days of zero trades. Pre-tightening (v5.0/v5.1 at MIN_SCORE 10) produced +$182 net across 11 trades with 60%+ win rate. v6.0 restores the trade flow.
FEE_OPTIMIZED_LIMIT on EXITS too
v5.x used MARKET (taker) closes. v6.0's DSL fires FEE_OPTIMIZED_LIMIT on every exit — maker-first, 60s ALO timeout, taker fallback. HL maker is 0.015% vs taker 0.045% = 0.020-0.030% saved per close.
Held-asset dedup (3-layer defense)
- Producer pre-filter:
held_assetsfrom clearinghouse, drops candidates already held - LLM gate hard skip:
heldAssetsarray in signal payload, LLM rejects if asset is held - Runtime
per_asset_cooldown_minutes: 240(known to silently not-enforce; layer 1+2 are the real defense)
Post-close cooldown (producer-side backstop)
Diff held_assets across ticks. When asset disappears from held set, record close timestamp. Producer skips emission for 240 min after any close. Backstops the runtime cooldown bug.
Reentrancy guard
producer_daemon owns a per-tick scanner_lock with stale-PID auto-recovery — replaces v6.x's hand-rolled fcntl lockfile. Prevents two ticks racing.
DSL preset (v7.2.0 — "let winners run" ladder, time cuts disabled)
| Phase | Component | Setting |
|---|---|---|
| Phase 1 | max_loss_pct | 15% |
| Phase 1 | retrace_threshold | 10 (v6.x raise from 6) |
| Phase 1 | consecutive_breaches | 1 |
| Phase 2 T0 | trigger 10% / lock 0% | (lets a +10% mover retrace fully before any lock) |
| Phase 2 T1 | trigger 20% / lock 25% | |
| Phase 2 T2 | trigger 30% / lock 40% | |
| Phase 2 T3 | trigger 50% / lock 60% | |
| Phase 2 T4 | trigger 75% / lock 75% | |
| Phase 2 T5 | trigger 100% / lock 85% | (apex) |
| hard_timeout | 720 min (12h) | enabled (the only time cut left) |
| weak_peak_cut | — | disabled (v7.2.0) |
| dead_weight_cut | — | disabled (v7.2.0) |
v7.2.0: weak_peak_cut + dead_weight_cut disabled. The 100-trade analysis showed the time cuts were the bottleneck — they forced exits before trenders could accelerate and capped the right-tail move that pays for everything. The two big HYPE wins (+13.9%, +8.9%) only landed via the 12h hard timeout, so that fail-safe is kept. Median trade gives back a bit; the right tail is now uncapped.
Risk gates (runtime.yaml risk.guard_rails)
| Gate | Setting |
|---|---|
| daily_loss_limit_pct | 25% |
| max_entries_per_day | 8 |
| max_consecutive_losses | 3 |
| cooldown_minutes (post-loss) | 30 |
| drawdown_halt_pct | 25% |
| drawdown_reset_on_day_rollover | false (Roach Sat-Sun bleed lesson) |
| per_asset_cooldown_minutes | 240 (4h) |
Operator install
See README.md for fresh-install + migration commands from v5.2.
Fleet patches incorporated
- ✓ Held-asset dedup (3-layer: producer pre-filter, LLM gate, runtime per_asset_cooldown)
- ✓ Post-close cooldown (producer-side backstop for the runtime cooldown silent-enforcement bug)
- ✓ Fleet-standard T0/T1 ladder
- ✓ Reentrancy guard (v7.0.0: producer_daemon scanner_lock with stale-PID auto-recovery; replaces hand-rolled fcntl)
- ✓ Wallet-from-config (no hardcoding; senpi-skills is public)
- ✓ drawdown_reset_on_day_rollover: false (Roach lesson)
- ✓ senpi_runtime_helpers (v7.0.0: in-process MCP + signal POST; replaces mcporter + openclaw-CLI subprocess)
- ✗ FP-001 quiet hours — deferred until fleet telemetry can validate the time-of-day P&L correlation
Hard rule for user-conversation Claude sessions
User-conversation Claude sessions MUST NOT call any of:
create_position, close_position, edit_position,
ratchet_stop_add, ratchet_stop_edit, ratchet_stop_delete,
cancel_order, strategy_close, strategy_close_positions.
These tools are reserved for the producer daemon (entry path) and the DSL ratchet engine (exit path). User-conversation sessions are read-only.
If the user asks a question that implies action ("anything close to triggering?"), respond by reading the current state — DO NOT execute. The producer daemon will handle real signals on its next tick.
Skill Attribution
When creating a strategy, include skill_name and skill_version in the call. See references/skill-attribution.md for details.
License
MIT — Copyright 2026 Senpi (https://senpi.ai)