name: bison-strategy description: >- BISON v3.0.1 — Conviction Holder. Patient multi-asset (BTC/ETH/SOL) conviction trader. MIN_SCORE 11 across 9 components (4H/1H structure, momentum, SM alignment, funding, volume, OI proxy, RSI). Conviction- scaled margin tiers (25%/31%/37%) and 7-10× leverage. DSL is wide by design — time-cuts DISABLED — so a real directional thesis can run multiple days into the move. Few trades. Long holds. Big moves. v3.0.1 ships a race-window dedup cache that eliminates the ENGINE_FAILURE retry noise on already-held assets, plus a doubled DSL exit interval to throttle REDUCE_ONLY spam when runtime position-state lags HL. license: Apache-2.0 metadata: author: jason-goldberg version: "3.0.1" platform: senpi exchange: hyperliquid requires: - senpi-trading-runtime>=1.1.0 - senpi_runtime_helpers
🦬 BISON v3.0.1 — Conviction Holder
Asset whitelist (BTC/ETH/SOL). Conviction floor (minScore 11). No time-cuts — Phase 1 + Phase 2 ratchet ladder own all exits.
v3.0.1 (2026-05-18) — held-asset dedup race-fix + DSL throttle
Operational reliability patch. NO thesis change. NO scoring change.
Bug observed. Audit on Bison12 (M192943) 2026-05-13 to 2026-05-17
showed 16 create_position ENGINE_FAILUREs carrying
BISON_CONVICTION reasoning text, all for assets the wallet already
held. Each failure was a runtime LLM-gate fire on a producer-emitted
signal that should have been deduped — the producer's on-chain
heldAssets check leaked during the race window between
push_signal() returning OK and the resulting position appearing
in the next-tick clearinghouseState pull.
Fix 1 — recent-signals cache (scripts/bison_config.py).
record_signal(coin) writes {coin: epoch_seconds} to
state/recent-signals.json after every successful push. main()
calls was_recently_signaled(coin) BEFORE build_thesis() and
skips any coin seen within RECENT_SIGNAL_TTL_SEC (default 180s
≈ 3× the typical ALO open-fill window). Skipped coins are echoed
in recently_signaled_skipped in the per-tick output for audit.
Fix 2 — DSL exit interval 30s → 60s (runtime.yaml).
Audit also showed clusters of CLOSE_FEE_OPTIMIZED_FAILED "Reduce only order would increase position" + CLOSE_NO_POSITION errors
firing 1-2 ticks after a successful close. The runtime's view of
the closed position takes longer to refresh than the 30s DSL
interval, so DSL re-fires close on a flat HL position. Doubling
the interval gives the runtime position-state pull room to catch
up and halves the wasted close attempts. Root cause (runtime-
side position-state sync) is escalated to the runtime team —
this YAML change is downstream mitigation only.
What this does NOT fix. Phantom orphan positions where DSL state and HL state are durably out of sync (i.e. runtime thinks a position is open after a complete external close, or vice versa). That class of bug needs runtime-side reconciliation; the producer has no authority over DSL state.
v3.0.0 (2026-05-12) — plumbing-only migration
NO thesis change. v2.1 asset whitelist, minScore 11, conviction-scaled margin tiers, direction waterfall, 9-component scoring, and DSL preset all preserved verbatim. Six-layer plumbing flip:
- MCP transport:
mcportersubprocess (2.5-5s cold-start per call) →senpi_runtime_helpers.SenpiClient.mcp_call()in-process HTTPS (~280ms). - Signal emit: scanner called
create_positiondirectly (Wolverine pattern); producer now emits viapush_signal()to runtime/signals. Runtime LLM-gatedbison_entryaction opens via FEE_OPTIMIZED_LIMIT, passingmarginUsd+leveragethrough from signal data for tier-based sizing. - Reentrancy: no v2 lockfile to replace (Bison v2.x ran on openclaw cron with no producer-side lock);
producer_daemonowns the per-tickscanner_lockwith stale-PID auto-recovery. - Scheduler: openclaw cron (5 min) →
producer_daemon(interval_seconds=300). Long-lived process; zero per-tick cold-start cost. - Risk gates: Python
MAX_DAILY_LOSS_PCT, dynamic-slots PnL-based cap, per-asset cooldown state files → declarativerisk.guard_railsblock.state/trade-counter.jsonandstate/asset-cooldowns.jsonare vestigial in v3.0 and can be deleted. - Exit fee: DSL exits switched from MARKET (taker, 0.045%) to FEE_OPTIMIZED_LIMIT (maker-first, 0.015%, 60s ALO timeout, taker fallback). Entry exits keep
ensure_execution_as_taker: false(v2.1 ALO patience rule).
CRITICAL RULES
RULE 1: Producer enters. DSL exits. Producer NEVER exits positions.
v1.x had thesis re-evaluation that chopped winners. v2.0 removed it. v3.0 makes it structurally impossible — the producer has no close_position call site.
RULE 2: Producer emits signals via push_signal(); runtime opens
The producer scores conviction theses and emits via client.push_signal(). The runtime's LLM-gated bison_entry action opens the position via FEE_OPTIMIZED_LIMIT with ensure_execution_as_taker: false (v2.1 patience rule preserved — entry ALO waits 30s for maker fill).
RULE 3: All signals are score contributors, not hard gates
4H trend, 1H trend, 1H momentum, SM direction, funding — all add/subtract points. The minScore threshold (v2.1+: 11) is the ONLY gate. Nothing kills a signal before scoring.
RULE 4: Asset whitelist enforced (v2.1+)
Only BTC/ETH/SOL by default (configurable via allowedAssets in bison-config.json). Pre-v2.1 fetched top 10 by 24h notional volume — let small-caps that volume-spiked into the universe (e.g. ZEC on May 4) consume the single daily slot.
RULE 5: ensureExecutionAsTaker = false on ENTRIES
Entries use FEE_OPTIMIZED_LIMIT with ensure_execution_as_taker: false (v2.1 patience rule). Exits use ensure_execution_as_taker: true (closes MUST happen — taker fallback is the safety floor).
RULE 6: Use the DSL preset in runtime.yaml — not a custom DSL state file
v2.x shipped a hardcoded DSL state template in scanner output. v3.0 uses the runtime YAML's dsl_preset block as the single source of truth. No scanner-side DSL state.
How BISON v3.0 trades
Entry (all score contributors — preserved from v2.1)
| Signal | Points | Type |
|---|---|---|
| 4H trend aligned | +3 | Score |
| 4H trend opposing | -1 | Score |
| 1H trend agrees | +2 | Score |
| 1H trend opposing | -1 | Score |
| 1H strong momentum (≥1%) | +2 | Score |
| 1H moderate momentum (≥0.5%) | +1 | Score |
| 1H counter momentum | -1 | Score |
| SM aligned | +2 | Score |
| SM opposing | -2 | Score |
| Funding aligned | +2 | Score |
| Funding crowded | -1 | Score |
| Volume rising | +1 | Score |
| OI growing | +1 | Score |
| RSI room | +1 | Score |
| RSI extreme | -1 | Score |
| 4H momentum (>1.5%) | +1 | Score |
Min score: 11. Max possible: ~16.
Direction determination (waterfall)
- 4H trend structure → LONG if BULLISH, SHORT if BEARISH
- If 4H NEUTRAL → follow SM direction
- If SM NEUTRAL → follow 1H momentum (>0.5%)
- If all neutral → no signal
Conviction-scaled margin
| Score | Margin |
|---|---|
| 8-9 | 25% of account |
| 10-11 | 31% |
| 12+ | 37% |
Producer computes marginUsd from tier; signal data carries it; LLM gate passes through to runtime payload. Same pattern for leverage (default 10x, MIN 7x, MAX 10x).
Exit — RatchetStop only
DSL preset wide-by-design for conviction breathing:
- +10% ROE: no lock (confirms working)
- +20% / +25% lock
- +30% / +40% lock
- +50% / +60% lock
- +75% / +75% lock
- +100% ROE: lock 85% — infinite trail
Phase 1 max_loss: 30%. Time-cuts: hard_timeout + dead_weight_cut DISABLED. weak_peak_cut KEPT (self-limiting at 60min/3.0% peak).
Risk gates (runtime.yaml risk.guard_rails)
| Gate | Setting | Replaces |
|---|---|---|
| max_entries_per_day | 3 | v2.1 base daily cap |
| per_asset_cooldown_minutes | 120 | v2.1 cooldownMinutes |
| daily_loss_limit_pct | 10 | v2.1 MAX_DAILY_LOSS_PCT |
| max_consecutive_losses | 3 | v2.1 (implicit) |
| cooldown_minutes (post-loss) | 30 | new (fleet-standard) |
| drawdown_halt_pct | 25 | new (fleet-standard, Roach lesson) |
| drawdown_reset_on_day_rollover | false | fleet-standard |
v2.1's PnL-aware dynamic daily cap (3 base / 6 hard cap, tiered by realized PnL) is dropped in favor of static max_entries_per_day=3 + drawdown_halt_pct=25. The conservative ceiling and -25% hard stop are preserved; the "ride the hot hand" tier is sacrificed for state-file-free reliability.
Hardcoded constants (not configurable)
- MAX_LEVERAGE: 10
- MIN_LEVERAGE: 7
- XYZ_BANNED: true (banned at producer scan level)
Operator deployment topology
Bison's runtime + producer pair runs per strategy wallet, not per
operator. Operators wanting more capital exposure deploy multiple
strategy wallets, each running its own runtime instance and its own
bison-producer daemon. Each instance:
- Reads its own
${WALLET_ADDRESS}(typically via env var) — the runtime YAML's${WALLET_ADDRESS}placeholder is resolved atruntime createtime, and the producer falls back tobison-config.json'swalletfield. - Computes
held_assetsfrom its own wallet's on-chainclearinghouseState— instances do NOT see each other's positions. - Writes its own
state/recent-signals.json(per-instance cache). - Reuses the same
SENPI_AUTH_TOKEN(operator-scoped) — a single operator can run N strategy wallets under one auth token.
Implication: two-wallet deployment doubles capital exposure and parallelizes asset selection. Wallet A may take SOL while Wallet B simultaneously takes BTC, capturing uncorrelated entries. Each wallet's DSL ratchet is independent.
This topology is the production reality on Bison's live deployment
(see Predator MCP bison-v1-0 slug, strategyWalletCount: 2).
It is NOT a Bison-specific feature — it's how the senpi-trading-
runtime plugin natively scales: one runtime per wallet, multiple
wallets per operator.
Signal cadence (what to expect)
- Producer tick: every 300s (5 min). Long-lived daemon — no per-tick cold-start cost.
- Universe scanned per tick: ≤ 10 assets, filtered to the whitelist (default BTC/ETH/SOL = 3 assets).
- MCP cost per tick: ~4-7 calls (
market_list_instruments,leaderboard_get_markets,market_get_asset_dataper candidate,strategy_get_clearinghouse_statefor held check). - Emission rate: 0-5 signals per day in typical regimes. The
MIN_SCORE 11 floor + 2h
per_asset_cooldownkeeps emission selective. - Quiet day signature: every tick output ends with
"note": "WAITING — no conviction thesis (min score 11)". This is the most common state — the producer is alive, the thesis is just not firing. - Suspected silent producer: check
audit_query({tool_name: "market_get_asset_data", user_ids: [<MID>]})— if entries fall to zero for >15 min during market hours, the daemon has died and needsdisownre-launch (fleet pattern from 2026-05-13).
Reading the decision log
Bison emits per-trade decision telemetry via the runtime LLM gate. To inspect the reasoning chain for a specific window:
mcp__senpi-prod__audit_query({
user_ids: ["<your Senpi MID>"], # e.g. "M192943" for Bison12
tool_name: "create_position",
action_type: "create",
limit: 50
})
Each entry's ai_reasoning field is the LLM gate's reasoning
output — typically "BISON_CONVICTION <ASSET> <DIRECTION> - <top_score_reason>". Successful entries have success: true;
failures carry an error_code and error_message.
Pair with close_position audit queries to see the DSL exit
reasons (weak_peak_cut, dsl_breach, etc).
The audit-query path is the canonical post-hoc validation tool for Bison — the Predator MCP shows aggregates (PnL, ROI, volume) but does not expose per-trade reasoning. For "why did this trade fire" questions, always go to audit_query.
v3.0.0 (2026-05-12) — plumbing-only migration
Hard rule for user-conversation Claude sessions
User-conversation Claude sessions MUST NOT call:
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.
Operator install
See README.md for fresh-install + migration commands from v2.1.
Changelog
v3.0.0 (2026-05-12) — plumbing-only migration
Migrated to helpers-native (senpi_runtime_helpers). Thesis preserved verbatim. See six-layer flip above.
v2.1 (2026-05-07)
- Asset whitelist (BTC/ETH/SOL); pre-v2.1 fetched top-10-by-volume which let small-caps like ZEC consume the daily slot
- minScore 8 → 11; was firing on first mediocre setup post-midnight, now demands real conviction
- DSL time-cuts disabled in runtime.yaml per single-asset pattern
v2.0 (2026-04-06)
- ALL hard gates converted to score contributors
- Thesis exit (evaluate_held_position) REMOVED
- Scanner calls create_position internally (Wolverine pattern; superseded in v3.0 by helpers-native push_signal)
- Direction waterfall: 4H → SM → 1H momentum
License
Apache-2.0 — attribution required for derivative works. Copyright 2026 Senpi (https://senpi.ai).
Skill Attribution
When creating a strategy, include skill_name and skill_version in the call. See references/skill-attribution.md for details.