diff --git a/skills/otto-alpha-sniper/.claude-plugin/plugin.json b/skills/otto-alpha-sniper/.claude-plugin/plugin.json new file mode 100644 index 000000000..64fe6bc50 --- /dev/null +++ b/skills/otto-alpha-sniper/.claude-plugin/plugin.json @@ -0,0 +1,21 @@ +{ + "name": "otto-alpha-sniper", + "description": "Otto Alpha Sniper \u2014 Hyperliquid perp execution driven by live crypto-alpha signals (trending altcoins, funding-rate skews, KOL-sentiment). Natural-language intent in, one-click perp with TP/SL out. Dry-run by default.", + "version": "0.1.3", + "author": { + "name": "Otto AI" + }, + "homepage": "https://useotto.xyz", + "repository": "https://github.com/useOttoAI/plugin-store", + "license": "MIT", + "keywords": [ + "perpetuals", + "hyperliquid", + "onchainos", + "trading-strategy", + "alpha-signals", + "kol-sentiment", + "funding-rates", + "trending" + ] +} diff --git a/skills/otto-alpha-sniper/LICENSE b/skills/otto-alpha-sniper/LICENSE new file mode 100644 index 000000000..0208813a9 --- /dev/null +++ b/skills/otto-alpha-sniper/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Otto AI (@useOttoAI) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/skills/otto-alpha-sniper/README.md b/skills/otto-alpha-sniper/README.md new file mode 100644 index 000000000..1a1f1292c --- /dev/null +++ b/skills/otto-alpha-sniper/README.md @@ -0,0 +1,48 @@ +# Otto Alpha Sniper + +A Strategy Skill for the [OKX Onchain OS Plugin Store](https://github.com/okx/plugin-store). Natural-language intent → live Hyperliquid perpetual trade, driven by Otto AI's multi-source alpha signals. + +## What it does + +User tells their AI agent something like *"open a perp on the strongest altcoin"* or *"follow the KOLs on ETH"*. The Skill: + +1. Checks the user's Hyperliquid account via the Hyperliquid Basic Skill (funding + signing ready?). +2. Pulls a live signal from Otto AI's public signals feed (trending momentum, KOL sentiment, or funding-rate extremes). +3. Picks the highest-conviction coin + direction. +4. Confirms with the user (explicit prompt — no silent orders). +5. Places the perp via `hyperliquid-plugin order` with auto TP/SL bracket via `hyperliquid-plugin tpsl`. +6. Returns a compact trade card. + +Dry-run is the default. Live orders require `--confirm`. + +## Install + +```bash +npx skills add okx/plugin-store --skill hyperliquid-plugin --yes --global +npx skills add okx/plugin-store --skill otto-alpha-sniper --yes --global +``` + +## Requirements + +- onchainos CLI ≥ 2.0.0 +- USDC on Arbitrum (for deposit into Hyperliquid) +- ETH on Arbitrum (gas for the deposit) +- A registered Hyperliquid signing address (`hyperliquid-plugin register`) + +## Files + +| File | Purpose | +|---|---| +| [SKILL.md](SKILL.md) | Agent protocol — what the AI reads to orchestrate a trade | +| [SUMMARY.md](SUMMARY.md) | User-facing overview | +| `plugin.yaml` | Plugin Store manifest | +| `scripts/config.py` | Hot-reload parameters (thresholds, caps, risk limits) | +| `scripts/bot.py` | Optional autonomous poller mode | + +## Status + +Submission target: OKX Plugin Store Developer Challenge Season 1 (snapshot 2026-05-07). + +## License + +MIT diff --git a/skills/otto-alpha-sniper/SKILL.md b/skills/otto-alpha-sniper/SKILL.md new file mode 100644 index 000000000..5627c3380 --- /dev/null +++ b/skills/otto-alpha-sniper/SKILL.md @@ -0,0 +1,342 @@ +--- +name: otto-alpha-sniper +description: > + Otto Alpha Sniper v0.1 — Natural-language intent → live Hyperliquid perp execution, driven + by Otto AI's multi-source alpha signals (trending altcoins, funding-rate skews, KOL sentiment, + filtered crypto news). Opens perps with automatic TP/SL brackets inside Onchain OS Agentic + Wallet. Dry-run is the default; `--confirm` required for live orders. + Trigger when the user mentions Otto alpha, alpha sniper, data-driven perp, momentum trade, + trending altcoin scalp, KOL follow, funding-rate fade, smart-money perp, Otto AI signals, + or wants to automatically open a Hyperliquid position based on aggregated crypto intel. +version: "0.1.3" +author: "Otto AI" +updated: 2026-04-24 +tags: + - perpetuals + - hyperliquid + - onchainos + - trading-strategy + - alpha-signals + - kol-sentiment + - funding-rates + - trending +--- + +# Otto Alpha Sniper — Skill Protocol + +> Real perpetual-futures trading with leverage on Hyperliquid. Use paper mode (`DRY_RUN = True`) until you understand the strategy. Live loss of capital is possible. + +--- + +## Overview + +Otto Alpha Sniper turns a user's plain-language trading intent into a single Hyperliquid perpetual trade with automatic take-profit and stop-loss brackets, driven by Otto AI's production alpha signals. + +Three signal modes, selectable per trade: + +- **Trending** — live altcoin momentum from Otto's 24h trending-altcoins feed (sentiment-weighted). +- **KOL-Follow** — aggregated sentiment across the top 50 crypto KOLs on Twitter/X. +- **Funding-Fade** — captures extreme funding-rate skews (long the most-shorted, short the most-longed). + +Every Hyperliquid action flows through the Hyperliquid Basic Skill (`hyperliquid-plugin`). No raw EIP-712 signing, no private keys, no bypass. The Skill defaults to `DRY_RUN = True`; a user `--confirm` is required before anything hits the exchange. + +**Entry mode.** Reactive — the AI agent fires one trade per user intent. An optional `scripts/bot.py` autonomous poller is provided for advanced users. + +--- + +## Pre-flight Checks + +### 1. Install onchainos CLI (≥ 2.0.0-beta) + +```bash +onchainos --version 2>/dev/null || curl -fsSL https://raw.githubusercontent.com/okx/onchainos-skills/main/install.sh | sh +npx skills add okx/onchainos-skills --yes --global +npx skills add okx/plugin-store --skill plugin-store --yes --global +``` + +### 2. Install the Hyperliquid Basic Skill (required) + +```bash +npx skills add okx/plugin-store --skill hyperliquid-plugin --yes --global +``` + +### 3. Fund Hyperliquid + register signing address + +```bash +hyperliquid-plugin quickstart # tells you what to do next +hyperliquid-plugin register # one-time — binds the signing key +hyperliquid-plugin deposit --amount 50 --confirm # bridges USDC from Arbitrum +``` + +### 4. Verify the Otto signal feed is reachable + +```bash +curl -fsS "https://signals.useotto.xyz/v1/trending?limit=1" | jq .updated_at +``` + +If this fails, abort and report "Otto signal feed unreachable" — the Skill will not fabricate signals. + +--- + +## Commands + +When the user fires an Otto Alpha Sniper intent, execute this 7-step protocol in order. Each command lists **when to use**, **output**, and a concrete example. + +### Step 1 — Readiness check + +```bash +hyperliquid-plugin quickstart +``` + +- **When to use**: Always, before any trade. +- **Output**: JSON with `status: ready | needs_register | no_funds | low_balance`. +- **If not ready**: run `hyperliquid-plugin register` or `hyperliquid-plugin deposit` as indicated. Do NOT proceed to Step 2. + +### Step 2 — Fetch the signal for the requested mode + +Pick ONE mode based on user intent. If ambiguous, ask the user. + +#### Mode: `trending` +```bash +curl -fsS "https://signals.useotto.xyz/v1/trending?limit=5" | jq . +``` +- **When to use**: user says "what's trending", "momentum", "strongest altcoin", "scalp". +- **Output**: + ```json + { + "updated_at": "2026-04-24T10:00:00Z", + "signals": [ + {"coin": "SOL", "score": 0.87, "direction": "long", "reason": "24h momentum + bullish KOL divergence", "confidence": 0.72}, + {"coin": "AVAX", "score": 0.81, "direction": "short", "reason": "breakdown + funding flipped positive", "confidence": 0.64} + ] + } + ``` +- **Selection rule**: pick the entry with the highest `score × confidence` product. If the top score < `MIN_SCORE` (config), abort with "no signal strong enough right now". + +#### Mode: `kol-follow` +```bash +curl -fsS "https://signals.useotto.xyz/v1/kol-sentiment?coin=" | jq . +``` +- **When to use**: user says "KOLs", "follow the crowd", "what are KOLs saying about {coin}". +- **Output**: + ```json + { + "coin": "ETH", + "bullish_pct": 0.84, + "bearish_pct": 0.06, + "neutral_pct": 0.10, + "kol_count": 50, + "updated_at": "2026-04-24T10:00:00Z", + "direction": "long", + "confidence": 0.78 + } + ``` +- **Selection rule**: use `direction` + `confidence`. Abort if `confidence < MIN_CONFIDENCE_KOL`. + +#### Mode: `funding-fade` +```bash +curl -fsS "https://signals.useotto.xyz/v1/funding-extremes?limit=3" | jq . +``` +- **When to use**: user says "fade funding", "most longed", "most shorted", "funding skew". +- **Output**: + ```json + { + "most_longed": {"coin": "DOGE", "funding_8h": 0.0012, "action": "short"}, + "most_shorted": {"coin": "LINK", "funding_8h": -0.0009, "action": "long"} + } + ``` +- **Selection rule**: pick the `action` on whichever side has the more extreme funding magnitude, or follow user's side if specified. + +### Step 3 — Confirm parameters with the user + +Before placing anything, summarize explicitly: + +> I'm about to open a **{direction}** **{coin}** perp on Hyperliquid. +> • Size: **${size_usd}** at **{leverage}x** leverage (≈ {notional_usd} notional) +> • Stop-loss: **{sl_pct}%** below entry (${sl_price}) +> • Take-profit: **{tp_pct}%** above entry (${tp_price}) +> • Mode: **{mode}** (Otto signal score: {score}, confidence: {confidence}) +> Dry run? {DRY_RUN} +> +> Reply "confirm" to execute live, "paper" to run dry-only, or "cancel" to abort. + +**Do NOT proceed to Step 4 without explicit user confirmation.** + +### Step 4 — Read current price + +```bash +hyperliquid-plugin prices --coin {COIN} +``` + +- **When to use**: once per trade, for TP/SL anchoring. +- **Output**: JSON with `mark_px` (current mark price). +- **Usage**: use `mark_px` as the entry reference for TP/SL bracket calculation. + +### Step 5 — Place the order + +```bash +hyperliquid-plugin order --coin {COIN} --side {buy|sell} --size {size} --leverage {leverage} --strategy-id otto-alpha-sniper --confirm +``` + +- **When to use**: after Step 3 confirmation and Step 4 price read. +- **Output**: JSON with `order_id`, `filled_px`, `tx_hash`. +- **Size conversion**: `size_tokens = size_usd / mark_px`, rounded down to the coin's lot size. `--size` is in token units. `size_usd` is the position **notional** value; `--leverage` controls margin posted (margin = notional / leverage). +- **Leverage**: always pass `--leverage {leverage}` explicitly — never rely on the exchange default. +- **Dry-run**: if `DRY_RUN = True`, omit `--confirm`. Plugin will echo the intended order without submitting. +- **Strategy attribution**: every live order must include `--strategy-id otto-alpha-sniper` for leaderboard attribution on the OKX Plugin Store Developer Challenge. + +### Step 6 — Attach TP/SL bracket + +```bash +hyperliquid-plugin tpsl --coin {COIN} --sl-px {sl_price} --tp-px {tp_price} --confirm +``` + +- **When to use**: immediately after Step 5 (same turn). +- **Output**: JSON with `bracket_id`, `tx_hash`. +- **Calculation**: `sl_price = mark_px × (1 - SL_PCT)` for longs, `mark_px × (1 + SL_PCT)` for shorts. Mirror for `tp_price` with `TP_PCT`. +- **Atomic**: pass both `--sl-px` and `--tp-px` in the same call so the bracket lands atomically. + +### Step 7 — Report back to the user + +Return a compact trade card: + +``` +✓ Otto Alpha Sniper — {mode} + {side} {size} {coin} @ ~${mark_px} ({leverage}x, ${notional_usd} notional) + SL ${sl_price} ({sl_pct}%) TP ${tp_price} ({tp_pct}%) + Signal score: {score} Confidence: {confidence} + Reason: {reason} + Entry tx: {tx_hash} + Bracket tx: {bracket_tx_hash} +``` + +### Configuration commands + +Tunable parameters live in `scripts/config.py` — hot-reloaded on every trade. Common tweaks: + +```bash +# View current values +grep -E "^[A-Z_]+ " scripts/config.py + +# Switch to live mode (still requires --confirm per trade) +# edit scripts/config.py: DRY_RUN = False + +# Reduce position size cap +# edit scripts/config.py: MAX_POSITION_PCT_EQUITY = 0.05 +``` + +### Optional autonomous poller + +For advanced users only: + +```bash +python3 scripts/bot.py --mode trending --interval 300 # dry-run by default +python3 scripts/bot.py --mode funding-fade --interval 900 --live +``` + +- **When to use**: user explicitly opts into hands-free operation. +- **Output**: JSONL log of every signal fetch + trade decision to `otto_sniper_trades.jsonl`. +- **Precondition**: run at least 10 `--once` dry cycles and review logs before `--live`. + +--- + +## Error Handling + +| Error | Cause | Resolution | +|---|---|---| +| `hyperliquid-plugin: command not found` | Basic Skill not installed | Run Pre-flight step 2. | +| `status: needs_register` | Signing address not bound to HL account | `hyperliquid-plugin register` and have the user approve. | +| `status: no_funds` / `low_balance` | Insufficient USDC on Hyperliquid | `hyperliquid-plugin deposit --amount N --confirm`. Do NOT retry automatically. | +| Signal feed HTTP 5xx | Otto backend transient failure | Retry ONCE after 3s. If still failing, abort with "Otto signal feed unreachable". | +| Signal feed HTTP 429 | Rate limit from same IP | Back off 30s, retry ONCE. Tell user to space out calls. | +| Signal feed returns empty `signals` array | No asset met threshold this cycle | Abort: "No signal strong enough right now — try again later or a different mode." | +| `score < MIN_SCORE` after selection | Top signal below config threshold | Abort without placing. Do NOT lower threshold dynamically. | +| `hyperliquid-plugin order` non-zero exit | Order rejected (margin, liquidity, or exchange error) | Return the plugin's error verbatim. Do NOT attempt to simulate via raw API. | +| `hyperliquid-plugin tpsl` fails after `order` succeeds | Partial bracket — position open without SL/TP | Immediately warn user + suggest manual bracket or `hyperliquid-plugin close`. | +| User explicitly aborts at Step 3 | User declined the trade summary | Do not place. Do not retry. Do not ask a second time. | +| `SESSION_MAX_DRAWDOWN_PCT` breached | Cumulative P&L below threshold | Refuse all new trades until next session (config reset). Tell user their session hit the drawdown halt. | + +--- + +## Security Notices + +**Risk level: `advanced`** (per OKX Plugin Store risk-level taxonomy). This Skill places leveraged perpetual trades on a user's behalf; loss of capital is possible. + +### Safeguards enforced by this Skill + +- **Dry-run default.** `DRY_RUN = True` in `scripts/config.py`. `--confirm` is required for every live order. +- **Hard position cap.** `MAX_POSITION_PCT_EQUITY = 0.10` — a single trade may never exceed 10% of the user's Hyperliquid equity. +- **Stop-loss.** Every order attaches a bracket with `SL_PCT = 0.02` (2% default) in the same agent turn. +- **Leverage caps per mode.** Trending 5x, KOL 3x, funding-fade 10x. Never exceeds `MAX_LEVERAGE_ABSOLUTE = 20`. Always min()'d against the coin's exchange-level cap. +- **Liquidity filter.** `MIN_VOLUME_USD = 10_000_000` — illiquid coins are skipped. +- **Session drawdown halt.** New trades refused after cumulative `-15%` session P&L. +- **No key handling.** All signing flows through `hyperliquid-plugin`'s TEE-backed Agentic Wallet. This Skill never touches private keys, seed phrases, or raw EIP-712 signatures. +- **No credentials in source.** No API keys, tokens, or secrets committed anywhere in this repo. +- **Declared network surface.** Only `signals.useotto.xyz` and `api.hyperliquid.xyz` (via `hyperliquid-plugin`) — listed in `api_calls`. No other outbound calls. + +### Things this Skill will NOT do + +- **Never** place a live order without an explicit user "confirm" at Step 3. +- **Never** bypass the Hyperliquid Basic Skill. All Hyperliquid actions MUST flow through `hyperliquid-plugin` — this is a submission-eligibility requirement of the OKX Plugin Store Developer Challenge. +- **Never** fabricate a signal. If the Otto signal feed is unreachable, the Skill aborts. +- **Never** exceed configured leverage caps without a user-override flag, and never above the per-coin protocol max. +- **Never** size above `MAX_POSITION_PCT_EQUITY` of Hyperliquid account equity. +- **Never** reconstruct EIP-712 / L1 action signatures by hand. +- **Never** stack a second position on the same coin silently — ask the user. +- **Never** trade illiquid coins below `MIN_VOLUME_USD`. + +### Risk disclaimer + +**This Skill, its parameters, and all related documentation are provided solely for educational research and technical reference purposes. They do not constitute investment advice, trading guidance, or financial recommendations.** + +1. **High risk.** Perpetual futures are leveraged. Liquidation is possible on rapid price moves. You may lose 100% of posted margin. +2. **Signals are not certainties.** Otto AI's alpha feed (trending, KOL sentiment, funding rates, filtered news) reflects aggregated real-time behavior. It has delays, noise, and blind spots, and cannot predict price direction. +3. **Parameters are reference-only.** Defaults in `scripts/config.py` are sized for a general user and are not tuned for your risk tolerance. Adjust before going live. +4. **Market conditions change.** Backtested or in-sample signal edges may degrade. Monitor your outcomes. +5. **Hyperliquid-specific risks.** Insurance fund haircuts, funding-rate regime shifts, keeper failures, L1 outages. Hyperliquid is geofenced — users must confirm eligibility in their jurisdiction. +6. **No profit guarantee.** Past performance ≠ future results. +7. **Regulatory risk.** Leveraged crypto derivatives are restricted or prohibited in many jurisdictions. User is solely responsible for compliance, taxes, and KYC where required. +8. **Assumption of responsibility.** Strategy is provided AS-IS. Authors, Otto AI, OKX, and affiliates are not liable for trading losses. + +**Recommendation.** Start with `DRY_RUN = True` (default). Place at least 10 paper trades and review the logs before switching to live. Use the smallest allowed `SIZE_USD` for your first live trade. + +### No claim of OKX endorsement + +This Skill is authored by Otto AI, a community developer submitting to the OKX Plugin Store Developer Challenge. It is not an OKX-endorsed product. "Otto" and "Otto AI" are the author's branding; "Hyperliquid" and "OKX Onchain OS" are referenced as the execution venues, not as affiliated entities. + +--- + +## Config reference + +See `scripts/config.py`. Key defaults (tune before going live): + +- `DRY_RUN = True` +- `DEFAULT_SIZE_USD = 25` +- `MAX_POSITION_PCT_EQUITY = 0.10` +- `MAX_LEVERAGE_TRENDING = 5` +- `MAX_LEVERAGE_KOL = 3` +- `MAX_LEVERAGE_FUNDING = 10` +- `MAX_LEVERAGE_ABSOLUTE = 20` +- `SL_PCT = 0.02` (2%) +- `TP_PCT = 0.04` (4%, 2:1 RR) +- `MIN_SCORE = 0.65` +- `MIN_CONFIDENCE_KOL = 0.70` +- `MIN_VOLUME_USD = 10_000_000` +- `SESSION_MAX_DRAWDOWN_PCT = 0.15` + +--- + +## Onchain OS Integration + +This Skill runs inside Onchain OS Agentic Wallet. All Hyperliquid interactions go through `hyperliquid-plugin`, which uses the TEE-backed signing context of the user's connected wallet. No private keys leave Onchain OS. + +Otto Alpha Sniper does not provision its own wallet. It orchestrates. + +--- + +## Links + +- Otto AI: https://useotto.xyz +- Otto X (the x402 API layer this Skill shares a data moat with): https://xlayer.ottoai.services +- Docs: https://docs.useotto.xyz +- Source: https://github.com/useOttoAI/plugin-store (fork of okx/plugin-store with Otto Skills under `skills/otto-*`) diff --git a/skills/otto-alpha-sniper/SUMMARY.md b/skills/otto-alpha-sniper/SUMMARY.md new file mode 100644 index 000000000..bfa7fb69d --- /dev/null +++ b/skills/otto-alpha-sniper/SUMMARY.md @@ -0,0 +1,94 @@ +# otto-alpha-sniper + +## 1. Overview + +Otto Alpha Sniper is a Strategy Skill for [OKX Onchain OS](https://web3.okx.com/onchainos) Agentic Wallet that turns a user's plain-language trading intent into a single Hyperliquid perpetual trade with automatic take-profit and stop-loss brackets, driven by Otto AI's production alpha signals. + +Core operations: + +- Fetch live alpha from Otto's signal feed (trending altcoins, KOL sentiment, extreme funding-rates) +- Select the highest-conviction coin + direction for the requested mode +- Place a Hyperliquid perpetual via the Hyperliquid Basic Skill with explicit user confirmation +- Attach an atomic TP/SL bracket on the same trade +- Enforce per-trade, per-session, and per-coin risk caps before anything hits the exchange + +Three signal modes (selectable per trade, or auto-blend): + +- **Trending** — live altcoin momentum from Otto's trending-altcoins feed (sentiment-weighted, 24h window). +- **KOL-Follow** — aggregated sentiment from the top 50 crypto KOLs on Twitter/X. Mirrors consensus conviction into a short-term perp. +- **Funding-Fade** — captures extreme funding-rate skews. Long the most-shorted asset (negative funding), short the most-longed (positive funding). + +Tags: `perpetuals` `hyperliquid` `onchainos` `trading-strategy` `alpha-signals` `kol-sentiment` `funding-rates` + +## 2. Prerequisites + +- US / geofenced users must verify Hyperliquid eligibility in their jurisdiction +- onchainos CLI ≥ 2.0.0 installed and authenticated (`onchainos wallet status`) +- Hyperliquid Basic Skill installed (`npx skills add okx/plugin-store --skill hyperliquid-plugin`) +- USDC on Arbitrum (chain 42161) to deposit into Hyperliquid +- A small amount of ETH on Arbitrum for gas +- Signing address registered via `hyperliquid-plugin register` +- Python 3.8+ for optional `scripts/bot.py` (stdlib only, no pip dependencies) + +## 3. Quick Start + +1. **Dry-run first.** Defaults to paper mode — no real orders. + + ``` + otto-alpha-sniper quickstart + ``` + +2. **Fund your Hyperliquid account.** From inside Onchain OS: + + ``` + hyperliquid-plugin quickstart + hyperliquid-plugin deposit --amount 50 --confirm + ``` + +3. **Test a signal pick (dry-run).** + + ``` + otto-alpha-sniper trade --mode trending --size-usd 25 + ``` + +4. **Go live** with explicit `--confirm` (after reviewing at least 10 paper trades): + + ``` + otto-alpha-sniper trade --mode trending --size-usd 25 --confirm + ``` + +5. **Review open positions.** + + ``` + hyperliquid-plugin positions --show-orders + ``` + +## Safety defaults + +- Dry-run is the default. `--confirm` is required to place a live order. +- Position cap: 10% of Hyperliquid account equity per trade (tunable in `scripts/config.py`). +- Auto stop-loss: 2% on market orders. +- Auto take-profit: 4% (2:1 reward:risk by default). +- Leverage cap: 5x for trending mode, 3x for KOL mode, 10x for funding-fade (explicit user override required to exceed). +- Per-session risk budget: strategy halts after -15% drawdown cumulatively. + +## Trigger phrases + +The AI agent will route to Otto Alpha Sniper on intents like: + +> "Otto, what's trending right now and size me a perp trade" +> "Scalp the strongest altcoin" +> "Follow the KOLs on ETH" +> "Fade the funding on the most-longed asset" +> "Open a perp using Otto's alpha signals" +> "Give me a data-driven Hyperliquid trade" + +## Data moat + +Signals come from Otto AI's production Market Alpha stack: aggregated KOL sentiment across the top 50 crypto Twitter/X accounts, filtered news from 7+ sources, live funding-rate feeds across major CEXs, and real-time trending-altcoin momentum. The same data powers Otto's autonomous trading agents on the Virtuals Agent Commerce Protocol. + +## Risk + +This is real on-chain trading of perpetual futures with leverage. Capital loss is possible. Dry-run extensively before committing real USDC. Users are responsible for tax reporting, jurisdictional compliance (Hyperliquid geofences some regions), and custody of their Hyperliquid signing key. + +See [SKILL.md](SKILL.md) for the full agent protocol and safety notices. diff --git a/skills/otto-alpha-sniper/plugin.yaml b/skills/otto-alpha-sniper/plugin.yaml new file mode 100644 index 000000000..2add1f1e7 --- /dev/null +++ b/skills/otto-alpha-sniper/plugin.yaml @@ -0,0 +1,41 @@ +schema_version: 1 +name: otto-alpha-sniper +version: "0.1.3" +description: "Otto Alpha Sniper — Hyperliquid perp execution driven by live crypto-alpha signals (trending altcoins, funding-rate skews, KOL-sentiment). Natural-language intent in, one-click perp with TP/SL out. Dry-run by default." +author: + name: "Otto AI" + github: "useOttoAI" +license: MIT +category: trading-strategy +tags: + - perpetuals + - hyperliquid + - onchainos + - trading-strategy + - alpha-signals + - kol-sentiment + - funding-rates + - trending + - auto-research + +# Strategy plugin metadata — declares the trading plugin this strategy calls, +# the risk tier, and the venues it operates on. Per FOR-DEVELOPERS.md +# "Submitting Strategy Plugins" section. +dependent_plugin: + - name: hyperliquid-plugin + version: "^0.3.0" + +risk_level: advanced + +supported_venues: + - hyperliquid + +components: + skill: + dir: . + +api_calls: + - "signals.useotto.xyz" + - "api.hyperliquid.xyz" + +type: community-developer diff --git a/skills/otto-alpha-sniper/scripts/bot.py b/skills/otto-alpha-sniper/scripts/bot.py new file mode 100644 index 000000000..ac54a3996 --- /dev/null +++ b/skills/otto-alpha-sniper/scripts/bot.py @@ -0,0 +1,272 @@ +#!/usr/bin/env python3 +""" +Otto Alpha Sniper v0.1 — Optional autonomous poller. + +The reactive flow documented in SKILL.md (AI agent fires trades on user intent) +is the primary mode. This bot.py is for advanced users who want a hands-free +background poller that checks the Otto signal feed every N seconds and fires +trades when thresholds are met. + +Run: + python3 bot.py --mode trending --interval 300 + + # Dry-run by default. Pass --live to submit real orders. + python3 bot.py --mode funding-fade --interval 900 --live + +See config.py for every tunable. + +⚠️ Start in DRY_RUN = True. Monitor for 10+ cycles before --live. +""" +from __future__ import annotations + +import argparse +import json +import subprocess +import sys +import time +import urllib.request +import urllib.error +from datetime import datetime, timezone +from pathlib import Path + +try: + import config # type: ignore[import-not-found] +except Exception: + sys.path.insert(0, str(Path(__file__).parent)) + import config # type: ignore[import-not-found] + +# Per-coin cooldown: last successful fire timestamp keyed by coin symbol. +# In-memory only — resets on bot restart, acceptable for v0.1 since the +# typical session is a single long-running daemon. +_LAST_FIRE_TS: dict[str, datetime] = {} + + +def log(record: dict) -> None: + record["ts"] = datetime.now(timezone.utc).isoformat() + line = json.dumps(record) + print(line, flush=True) + if config.LOG_TRADES_TO_FILE: + with open(config.LOG_FILE, "a") as f: + f.write(line + "\n") + + +def fresh_enough(payload: dict) -> bool: + """True if `updated_at` is within MAX_SIGNAL_AGE_SEC (default 4500s = 75min).""" + max_age = getattr(config, "MAX_SIGNAL_AGE_SEC", 4500) + try: + ts_str = payload["updated_at"] + if ts_str.endswith("Z"): + ts_str = ts_str.replace("Z", "+00:00") + ts = datetime.fromisoformat(ts_str) + return (datetime.now(timezone.utc) - ts).total_seconds() <= max_age + except Exception: + return False + + +def within_cooldown(coin: str) -> bool: + """True if this coin was fired within COOLDOWN_HOURS (default 4).""" + cooldown_h = getattr(config, "COOLDOWN_HOURS", 4) + last = _LAST_FIRE_TS.get(coin) + if last is None: + return False + return (datetime.now(timezone.utc) - last).total_seconds() < cooldown_h * 3600 + + +def fetch_signal(mode: str, coin: str | None = None) -> dict | None: + if mode == "trending": + url = f"{config.SIGNAL_FEED_BASE}/v1/trending?limit=5" + elif mode == "kol-follow": + url = f"{config.SIGNAL_FEED_BASE}/v1/kol-sentiment" + if coin: + url += f"?coin={coin}" + elif mode == "funding-fade": + url = f"{config.SIGNAL_FEED_BASE}/v1/funding-extremes?limit=3" + else: + raise ValueError(f"unknown mode: {mode}") + + for attempt in range(config.SIGNAL_FEED_RETRIES + 1): + try: + req = urllib.request.Request(url, headers={"Accept": "application/json"}) + with urllib.request.urlopen(req, timeout=config.SIGNAL_FEED_TIMEOUT_SEC) as resp: + return json.loads(resp.read()) + except (urllib.error.URLError, urllib.error.HTTPError, TimeoutError) as e: + log({"event": "signal_feed_error", "mode": mode, "attempt": attempt, "err": str(e)}) + time.sleep(3) + return None + + +def pick_trade(mode: str, payload: dict) -> dict | None: + if mode == "trending": + signals = payload.get("signals", []) + if not signals: + return None + best = max(signals, key=lambda s: s.get("score", 0) * s.get("confidence", 0)) + if best.get("score", 0) < config.MIN_SCORE: + return None + return { + "coin": best["coin"], + "direction": best["direction"], + "score": best.get("score"), + "confidence": best.get("confidence"), + "reason": best.get("reason", ""), + } + if mode == "kol-follow": + if payload.get("kol_count", 0) < config.MIN_KOL_COUNT: + return None + if payload.get("confidence", 0) < config.MIN_CONFIDENCE_KOL: + return None + return { + "coin": payload["coin"], + "direction": payload["direction"], + "score": payload.get("bullish_pct") if payload["direction"] == "long" else payload.get("bearish_pct"), + "confidence": payload["confidence"], + "reason": f"KOL consensus {payload.get('bullish_pct', 0):.0%} bullish", + } + if mode == "funding-fade": + longed = payload.get("most_longed") or {} + shorted = payload.get("most_shorted") or {} + # Pick whichever extreme is more severe + longed_abs = abs(longed.get("funding_8h", 0)) + shorted_abs = abs(shorted.get("funding_8h", 0)) + if max(longed_abs, shorted_abs) < config.FUNDING_EXTREME_ABS: + return None + target = longed if longed_abs >= shorted_abs else shorted + return { + "coin": target["coin"], + "direction": target["action"], + "score": longed_abs if target is longed else shorted_abs, + "confidence": 0.6, + "reason": f"funding_8h={target['funding_8h']:+.4f}", + } + return None + + +def hl_quickstart_status() -> str: + try: + out = subprocess.check_output(["hyperliquid-plugin", "quickstart"], text=True, timeout=15) + data = json.loads(out) + return data.get("status", "unknown") + except Exception as e: + log({"event": "hl_quickstart_failed", "err": str(e)}) + return "unknown" + + +def mark_price(coin: str) -> float | None: + try: + out = subprocess.check_output(["hyperliquid-plugin", "prices", "--coin", coin], text=True, timeout=15) + data = json.loads(out) + return float(data.get("mark_px")) + except Exception: + return None + + +def leverage_cap(mode: str) -> int: + return { + "trending": config.MAX_LEVERAGE_TRENDING, + "kol-follow": config.MAX_LEVERAGE_KOL, + "funding-fade": config.MAX_LEVERAGE_FUNDING, + }.get(mode, 3) + + +def compute_bracket(side: str, mark: float) -> tuple[float, float]: + if side == "buy": + sl = mark * (1 - config.SL_PCT) + tp = mark * (1 + config.TP_PCT) + else: + sl = mark * (1 + config.SL_PCT) + tp = mark * (1 - config.TP_PCT) + return sl, tp + + +def fire_trade(trade: dict, mode: str, size_usd: float, live: bool) -> None: + if config.PAUSED: + log({"event": "paused", "mode": mode}) + return + side = "buy" if trade["direction"] == "long" else "sell" + lev = min(leverage_cap(mode), config.MAX_LEVERAGE_ABSOLUTE) + mark = mark_price(trade["coin"]) + if mark is None: + log({"event": "mark_price_unavailable", "coin": trade["coin"]}) + return + # size_usd is the position NOTIONAL (margin used = notional / leverage, + # enforced by hyperliquid-plugin via --leverage). Token count = notional / mark. + size_tokens = round(size_usd / mark, 6) + sl, tp = compute_bracket(side, mark) + log({"event": "preparing_trade", "mode": mode, "trade": trade, "side": side, "leverage": lev, + "size_usd": size_usd, "size_tokens": size_tokens, "mark": mark, "sl": sl, "tp": tp, + "live": live and not config.DRY_RUN}) + confirm = ["--confirm"] if (live and not config.DRY_RUN) else [] + order_cmd = [ + "hyperliquid-plugin", "order", + "--coin", trade["coin"], + "--side", side, + "--size", str(size_tokens), + "--leverage", str(lev), + "--strategy-id", "otto-alpha-sniper", + ] + confirm + try: + subprocess.run(order_cmd, check=True, timeout=45) + except subprocess.CalledProcessError as e: + log({"event": "order_failed", "err": str(e)}) + return + # Record fire timestamp for cooldown gating + _LAST_FIRE_TS[trade["coin"]] = datetime.now(timezone.utc) + if live and not config.DRY_RUN: + # tpsl does NOT accept --strategy-id (leaderboard attribution is on + # the parent order via the order command; tpsl is per-position). + bracket_cmd = [ + "hyperliquid-plugin", "tpsl", + "--coin", trade["coin"], + "--sl-px", f"{sl:.6f}", + "--tp-px", f"{tp:.6f}", + "--confirm", + ] + try: + subprocess.run(bracket_cmd, check=True, timeout=30) + except subprocess.CalledProcessError as e: + log({"event": "bracket_failed", "err": str(e), "coin": trade["coin"], + "warning": "position is OPEN without TP/SL — manual intervention required"}) + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--mode", choices=["trending", "kol-follow", "funding-fade"], required=True) + parser.add_argument("--interval", type=int, default=300, help="seconds between cycles") + parser.add_argument("--size-usd", type=float, default=config.DEFAULT_SIZE_USD) + parser.add_argument("--coin", type=str, default=None, help="override for kol-follow mode") + parser.add_argument("--live", action="store_true", help="submit real orders (still gated by config.DRY_RUN)") + parser.add_argument("--once", action="store_true", help="run one cycle and exit") + args = parser.parse_args() + + log({"event": "bot_start", "mode": args.mode, "interval": args.interval, "live": args.live, + "dry_run": config.DRY_RUN, "paused": config.PAUSED}) + + while True: + status = hl_quickstart_status() + if status != "ready": + log({"event": "hl_not_ready", "status": status}) + else: + payload = fetch_signal(args.mode, args.coin) + if payload is None: + log({"event": "no_signal_feed"}) + elif not fresh_enough(payload): + log({"event": "signal_stale", "updated_at": payload.get("updated_at")}) + else: + trade = pick_trade(args.mode, payload) + if trade is None: + log({"event": "no_trade_this_cycle", "mode": args.mode}) + elif within_cooldown(trade["coin"]): + log({"event": "cooldown_active", "coin": trade["coin"], "mode": args.mode}) + else: + fire_trade(trade, args.mode, args.size_usd, args.live) + + if args.once: + break + time.sleep(args.interval) + + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + log({"event": "bot_stop", "reason": "keyboard_interrupt"}) diff --git a/skills/otto-alpha-sniper/scripts/config.py b/skills/otto-alpha-sniper/scripts/config.py new file mode 100644 index 000000000..a5cc7644b --- /dev/null +++ b/skills/otto-alpha-sniper/scripts/config.py @@ -0,0 +1,65 @@ +""" +Otto Alpha Sniper v0.1 — Hot-reloadable configuration. + +Every parameter below is tunable without changing bot.py or the SKILL protocol. +The AI agent reads these values through bot.py or via grep when orchestrating +a trade in reactive mode. + +⚠️ Disclaimer: values are reference defaults sized for a generalist user on +Hyperliquid. They are NOT investment advice and may not fit your risk tolerance, +experience, or jurisdiction. Adjust before going live. +""" + +# ── Run mode ───────────────────────────────────────────────────────────────── +DRY_RUN = True # True = paper mode (no --confirm passed to hyperliquid-plugin) +PAUSED = False # True = refuse all new trades regardless of DRY_RUN + +# ── Sizing ─────────────────────────────────────────────────────────────────── +# DEFAULT_SIZE_USD is the position NOTIONAL value (size × mark), NOT margin. +# Margin used = notional / leverage. With $25 notional at 5x leverage, the +# trade locks ~$5 of margin. +DEFAULT_SIZE_USD = 25 # per-trade notional in USD; agent may override per call +MIN_SIZE_USD = 10 +MAX_SIZE_USD = 500 +MAX_POSITION_PCT_EQUITY = 0.10 # no single trade can exceed 10% of HL equity + +# ── Leverage caps per mode ─────────────────────────────────────────────────── +# Protocol max is per-coin (e.g. BTC 50x, memecoins 3x). Always min() these +# against the per-coin cap before submitting. +MAX_LEVERAGE_TRENDING = 5 +MAX_LEVERAGE_KOL = 3 +MAX_LEVERAGE_FUNDING = 10 +MAX_LEVERAGE_ABSOLUTE = 20 + +# ── Risk controls ──────────────────────────────────────────────────────────── +SL_PCT = 0.02 # 2% stop-loss +TP_PCT = 0.04 # 4% take-profit (2:1 RR) +SESSION_MAX_DRAWDOWN_PCT = 0.15 # halt all new trades after cumulative -15% +MAX_CONCURRENT_POSITIONS = 3 +COOLDOWN_HOURS = 4 # min hours between fires on the SAME coin (any mode) + +# ── Signal thresholds ──────────────────────────────────────────────────────── +# Calibrated against live signals.useotto.xyz data 2026-04-30: +# - trending: score = token-alpha confidence/100. Real values 0.4-0.85; 0.50 +# floor keeps quality high while catching mid-cap trending picks. +# - kol: sentiment_score 25-90 → confidence 0.0-0.80; 0.50 = score ≥75 or ≤25 +# - kol_count: typical 30-40 distinct authors per 24h window (list size 50) +# - funding: producer not yet shipped (Tier 2 deferred) +MIN_SCORE = 0.50 # trending mode floor +MIN_CONFIDENCE_KOL = 0.50 # genuine consensus gate +MIN_KOL_COUNT = 25 # cohort sample-size floor +FUNDING_EXTREME_ABS = 0.0008 # 8h funding skew threshold (Mode 3 / Tier 2) + +# ── Market filters ─────────────────────────────────────────────────────────── +MIN_VOLUME_USD = 10_000_000 # daily HL volume floor — avoid illiquid coins +ASSET_BLOCKLIST = [] # coins to never trade + +# ── Signal feed ────────────────────────────────────────────────────────────── +SIGNAL_FEED_BASE = "https://signals.useotto.xyz" +SIGNAL_FEED_TIMEOUT_SEC = 4 +SIGNAL_FEED_RETRIES = 1 +MAX_SIGNAL_AGE_SEC = 4500 # 75 min — covers full hourly producer cycle + buffer + +# ── Logging ────────────────────────────────────────────────────────────────── +LOG_TRADES_TO_FILE = True +LOG_FILE = "otto_sniper_trades.jsonl"