Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ Exploit pricing dislocations across venues, instruments, or time horizons.

| Strategy | Description | Key Parameters | When to Use |
|----------|-------------|----------------|-------------|
| `funding_arb` | Cross-venue funding rate arbitrage — captures funding divergence between HL and external venues. Quoting-engine powered with bias from funding delta. *Requires `quoting_engine` module.* | `divergence_threshold_bps`, `max_bias_bps` | When funding rates diverge between venues. Works well on high-funding instruments. |
| `funding_arb` | Funding-biased MM on HL funding rate — cross-venue feeds not wired; HL-only bias from funding delta. *Requires `quoting_engine` module.* | `divergence_threshold_bps`, `max_bias_bps` | When HL funding is elevated and you want quote bias toward collecting premium. |
| `basis_arb` | Trades implied basis from funding rate — enters when annualized basis (contango/backwardation) exceeds threshold. | `basis_threshold_bps`, `size` | Capturing contango/backwardation dislocations. Pairs well with funding_arb. |

### Signal / Directional
Expand All @@ -188,7 +188,7 @@ Supporting strategies for portfolio management, block liquidity, and autonomous

| Strategy | Description | Key Parameters | When to Use |
|----------|-------------|----------------|-------------|
| `hedge_agent` | Reduces excess exposure per deterministic mandate. Fires when net notional exceeds threshold. | `notional_threshold` | Always-on risk overlay. Pairs with any MM or signal strategy. |
| `hedge_agent` | Reduces excess inventory per deterministic mandate. Fires when absolute position qty exceeds threshold. | `inventory_threshold` | Always-on risk overlay. Pairs with any MM or signal strategy. |
| `rfq_agent` | Block-size dark RFQ liquidity — quotes for large orders with wider spreads. | `min_size`, `spread_bps` | Institutional/block flow. Provides hidden liquidity for large counterparties. |
| `claude_agent` | Multi-model LLM trading agent. Sends market snapshot to an LLM (Gemini, Claude, or OpenAI), receives structured trade decisions. | `model`, `base_size` | **Experimental.** Autonomous decision-making using LLM reasoning. |

Expand Down
32 changes: 24 additions & 8 deletions cli/commands/hedge.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,13 @@ def _position_to_summary(raw: dict, coin_override: Optional[str] = None):
)


def _build_proposal(hl, coin: str):
def _build_proposal(
hl,
coin: str,
*,
mainnet: bool = False,
hedge_instrument: Optional[str] = None,
):
"""Build a hedge proposal for the user's current `coin` perp position.

Returns (proposal, snapshot) or raises typer.Exit if no position open.
Expand All @@ -115,7 +121,11 @@ def _build_proposal(hl, coin: str):
fetch_hl_current_funding_hr,
)

profile = get_cfi_profile(coin)
profile = get_cfi_profile(
coin,
mainnet=mainnet,
hedge_instrument=hedge_instrument,
)
if profile is None:
typer.echo(f"Error: no deployed CFI v2 profile for coin '{coin}'", err=True)
raise typer.Exit(2)
Expand Down Expand Up @@ -146,6 +156,8 @@ def _build_proposal(hl, coin: str):
position=position,
current_funding_hr=current_funding,
k_fixed_hr=snapshot.k_fixed_hr,
mainnet=mainnet,
hedge_instrument=hedge_instrument,
)
return proposal, snapshot

Expand All @@ -171,7 +183,7 @@ def propose_cmd(
raw_hl = HLProxy(private_key=private_key, testnet=not mainnet)
hl = DirectHLProxy(raw_hl)

proposal, snapshot = _build_proposal(hl, coin)
proposal, snapshot = _build_proposal(hl, coin, mainnet=mainnet)
typer.echo(hedge_proposal_block(proposal, snapshot, mainnet=mainnet))


Expand Down Expand Up @@ -202,7 +214,7 @@ def execute_cmd(
raw_hl = HLProxy(private_key=private_key, testnet=not mainnet)
hl = DirectHLProxy(raw_hl)

proposal, snapshot = _build_proposal(hl, coin)
proposal, snapshot = _build_proposal(hl, coin, mainnet=mainnet)
typer.echo(hedge_proposal_block(proposal, snapshot, mainnet=mainnet))

# Size the order in CFI v2 (BTCSWP) units. SDK rounds to szDecimals.
Expand Down Expand Up @@ -306,7 +318,11 @@ def _refresh():
if h.get("status") != "active":
live.append({"job": h, "snapshot": None, "drift_apy": 0.0, "savings_usd": h.get("cumulative_savings_usd", 0.0)})
continue
profile = get_cfi_profile(h.get("coin", "BTC"))
profile = get_cfi_profile(
h.get("coin", "BTC"),
mainnet=mainnet,
hedge_instrument=h.get("instrument"),
)
if profile is None:
live.append({"job": h, "snapshot": None, "drift_apy": 0.0, "savings_usd": h.get("cumulative_savings_usd", 0.0)})
continue
Expand Down Expand Up @@ -502,7 +518,7 @@ def auto_cmd(

# Parse + validate the coin list.
coin_tuple = tuple(c.strip().upper() for c in coins.split(",") if c.strip())
unsupported = [c for c in coin_tuple if get_cfi_profile(c) is None]
unsupported = [c for c in coin_tuple if get_cfi_profile(c, mainnet=mainnet) is None]
if unsupported:
typer.echo(
f"{RED}Error: no CFI v2 profile deployed for: {', '.join(unsupported)}{RESET}",
Expand Down Expand Up @@ -595,7 +611,7 @@ def auto_cmd(
coin=coin,
perp_notional_usd=notional,
active_hedge_coins=active_coins,
profile_vol_mult_l=get_cfi_profile(coin).vol_mult_l,
profile_vol_mult_l=get_cfi_profile(coin, mainnet=mainnet).vol_mult_l,
policy=policy,
daily=daily,
now_ms=now_ms,
Expand All @@ -616,7 +632,7 @@ def auto_cmd(

# Real fire — build proposal + place order via existing helper.
try:
proposal, snapshot = _build_proposal(hl, coin)
proposal, snapshot = _build_proposal(hl, coin, mainnet=mainnet)
except typer.Exit:
msg = f"build-proposal-failed for {coin}; skipping"
append_audit_log(msg)
Expand Down
4 changes: 4 additions & 0 deletions cli/commands/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ def run_cmd(
params = dict(cfg.strategy_params)
if model:
params["model"] = model
if cfg.strategy == "cfi_hedge":
params["mainnet"] = cfg.mainnet
if cfg.instrument.endswith(("-USDYP", "-PARA", "-OSRS")) or ":" in cfg.instrument:
params["hedge_instrument"] = cfg.instrument

# Set up anomaly protection for YEX markets
anomaly_thread = None
Expand Down
2 changes: 1 addition & 1 deletion cli/skill.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ Tools: `strategies`, `builder_status`, `wallet_list`, `wallet_auto`, `setup_chec
| regime_mm | MM | Vol-regime adaptive — switches behavior by volatility regime (calm/normal/volatile/extreme) |
| grid_mm | MM | Fixed-interval grid levels above and below mid |
| liquidation_mm | MM | Provides liquidity during cascade/liquidation events |
| funding_arb | Arb | Cross-venue funding rate arbitrage |
| funding_arb | Arb | HL funding-biased MM (cross-venue feeds not wired) |
| basis_arb | Arb | Trades implied basis from funding rate (contango/backwardation) |
| mean_reversion | Signal | Trades when price deviates from SMA |
| momentum_breakout | Signal | Enters on volume + price breakout above/below N-period range |
Expand Down
4 changes: 2 additions & 2 deletions cli/strategy_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"hedge_agent": {
"path": "strategies.hedge_agent:HedgeAgent",
"description": "inventory reducer (delta control)",
"params": {"notional_threshold": 15000.0},
"params": {"inventory_threshold": 3.0},
},
"cfi_hedge": {
"path": "strategies.cfi_hedge_agent:CfiHedgeAgent",
Expand Down Expand Up @@ -56,7 +56,7 @@
},
"funding_arb": {
"path": "strategies.funding_arb:FundingArbStrategy",
"description": "Cross-venue funding rate arbitrage — captures funding dislocations",
"description": "HL funding-rate bias MM — cross-venue arb not wired; HL-only",
"params": {"divergence_threshold_bps": 2.0, "max_bias_bps": 5.0},
},
"regime_mm": {
Expand Down
69 changes: 51 additions & 18 deletions strategies/cfi_hedge.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@
"""
from __future__ import annotations

from dataclasses import dataclass, field
from dataclasses import dataclass, field, replace
from typing import Iterable, Optional

from common.models import BTCSWP_ASSET, asset_to_coin, asset_to_instrument, is_mainnet

HOURS_PER_YEAR = 8_760


Expand Down Expand Up @@ -58,18 +60,29 @@ class CFIAssetProfile:
"""YEX-local asset index. -1 = not deployed (sentinel)."""


BTCSWP_PROFILE = CFIAssetProfile(
name="BTC",
vol_mult_l=15,
fixed_leg_initial=0.0000029,
k2_beta=0.080042,
baseline_b0=75_000,
scale_s=1_000_000,
hl_coin="BTC",
cfi_asset_name="yex:BTCSWP",
cfi_instrument="BTCSWP-USDYP",
cfi_asset_index=2,
)
def btcswp_profile(mainnet: Optional[bool] = None) -> CFIAssetProfile:
"""Network-aware BTCSWP CFI v2 profile.

Testnet: yex:BTCSWP / BTCSWP-USDYP (YEX yield perp).
Mainnet: para:BTCSWP / BTCSWP-PARA (Paragon swap perp).
"""
on_mainnet = is_mainnet(mainnet)
return CFIAssetProfile(
name="BTC",
vol_mult_l=15,
fixed_leg_initial=0.0000029,
k2_beta=0.080042,
baseline_b0=75_000,
scale_s=1_000_000,
hl_coin="BTC",
cfi_asset_name=asset_to_coin(BTCSWP_ASSET, mainnet=mainnet),
cfi_instrument=asset_to_instrument(BTCSWP_ASSET, mainnet=mainnet),
cfi_asset_index=2 if not on_mainnet else -1,
)


# Back-compat alias — testnet profile (YEX).
BTCSWP_PROFILE = btcswp_profile(mainnet=False)

# ETHSWP placeholder — not yet deployed. Filled-in `vol_mult_l` etc. match
# ~/hyperliquid-funding-rate-perps/tools/hedge_calculator.py.
Expand All @@ -87,14 +100,28 @@ class CFIAssetProfile:
)

CFI_PROFILES = {
"BTC": BTCSWP_PROFILE,
"ETH": ETHSWP_PROFILE,
}


def get_cfi_profile(coin: str) -> Optional[CFIAssetProfile]:
def get_cfi_profile(
coin: str,
mainnet: Optional[bool] = None,
hedge_instrument: Optional[str] = None,
) -> Optional[CFIAssetProfile]:
"""Look up a deployed profile by coin ticker. Returns None for unknowns."""
return CFI_PROFILES.get(coin.upper())
upper = coin.upper()
if upper in ("BTC", BTCSWP_ASSET):
base = btcswp_profile(mainnet=mainnet)
if hedge_instrument is None:
return base
from cli.strategy_registry import resolve_instrument
from common.models import instrument_to_coin

resolved = resolve_instrument(hedge_instrument, mainnet=mainnet)
cfi_coin = instrument_to_coin(resolved, mainnet=mainnet)
return replace(base, cfi_asset_name=cfi_coin, cfi_instrument=resolved)
return CFI_PROFILES.get(upper)


# ─── Rate conversions ───────────────────────────────────────────────────────
Expand Down Expand Up @@ -229,12 +256,18 @@ def build_cfi_hedge_proposal(
k_fixed_hr: float,
horizons: Optional[Iterable[tuple]] = None,
now_ms: Optional[int] = None,
mainnet: Optional[bool] = None,
hedge_instrument: Optional[str] = None,
) -> Optional[CFIHedgeProposal]:
"""Build a CFI v2 hedge proposal for an existing HL perp position.

Returns None if no CFI profile is deployed for the position's coin.
"""
profile = get_cfi_profile(position.coin)
profile = get_cfi_profile(
position.coin,
mainnet=mainnet,
hedge_instrument=hedge_instrument,
)
if profile is None:
return None

Expand All @@ -256,7 +289,7 @@ def build_cfi_hedge_proposal(
role="existing",
)
hedge_leg = CFIHedgeLeg(
venue="YEX-HIP3",
venue="PARA-HIP3" if is_mainnet(mainnet) else "YEX-HIP3",
# CFI v2 long pays you (funding − K2) per unit time. Same-side hedge
# by the identity derivation: existing long → CFI long; existing
# short → CFI short.
Expand Down
20 changes: 15 additions & 5 deletions strategies/cfi_hedge_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
strategy loop alongside the inventory-control `HedgeAgent`.

This is the canonical "hedge" strategy: it neutralises funding-rate cost
on an existing perp position by opening a 1/L CFI v2 (yex:{COIN}SWP) leg.
on an existing perp position by opening a 1/L CFI v2 ({yex|para}:{COIN}SWP) leg.
Contrast with `strategies/hedge_agent.py`, which is a delta/inventory
reducer.

Expand All @@ -23,7 +23,7 @@
import time
from typing import List, Optional

from common.models import MarketSnapshot, StrategyDecision, instrument_to_coin
from common.models import MarketSnapshot, StrategyDecision, instrument_to_coin, is_mainnet
from sdk.strategy_sdk.base import BaseStrategy, StrategyContext

from strategies.cfi_hedge import (
Expand All @@ -48,7 +48,7 @@ class CfiHedgeAgent(BaseStrategy):
3. Run the pure `compute_hedge_open_action` gate (trigger + caps + interval).
4. If it fires, size the CFI v2 leg via `build_cfi_hedge_proposal`
(1/L ratio) and emit a `place_order` StrategyDecision on the
yex:{COIN}SWP instrument.
network-appropriate CFI v2 instrument (yex:BTCSWP testnet, para:BTCSWP mainnet).

State (daily action counter / last-action timestamp) is held in-memory
for the life of the strategy instance — the standalone `hl hedge auto`
Expand All @@ -62,12 +62,16 @@ def __init__(
max_hedge_notional: float = 50_000.0,
max_per_day: int = 5,
min_interval_seconds: int = 300,
mainnet: Optional[bool] = None,
hedge_instrument: Optional[str] = None,
):
super().__init__(strategy_id=strategy_id)
self.notional_trigger = notional_trigger
self.max_hedge_notional = max_hedge_notional
self.max_per_day = max_per_day
self.min_interval_seconds = min_interval_seconds
self.mainnet = is_mainnet(mainnet)
self.hedge_instrument = hedge_instrument
# In-memory daily counters (CLI verb owns the disk-backed copy).
self._daily = DailyHedgeState.fresh(today_utc_iso())
# Coins already hedged in this strategy session.
Expand All @@ -83,11 +87,15 @@ def on_tick(

# Normalise the snapshot instrument to a bare ticker (BTC-PERP → BTC,
# yex:BTCSWP → BTCSWP) so it keys into the deployed CFI v2 profiles.
coin = instrument_to_coin(snapshot.instrument)
coin = instrument_to_coin(snapshot.instrument, mainnet=self.mainnet)
if ":" in coin:
coin = coin.split(":", 1)[1]
coin = coin.upper()
profile = get_cfi_profile(coin)
profile = get_cfi_profile(
coin,
mainnet=self.mainnet,
hedge_instrument=self.hedge_instrument,
)
if profile is None:
return []

Expand Down Expand Up @@ -170,6 +178,8 @@ def _action_to_decision(
position=position,
current_funding_hr=snapshot.funding_rate,
k_fixed_hr=profile.fixed_leg_initial,
mainnet=self.mainnet,
hedge_instrument=self.hedge_instrument,
)
if proposal is None:
return None
Expand Down
3 changes: 3 additions & 0 deletions strategies/funding_arb.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
When HL funding diverges from the cross-venue median, biases quotes
to collect the premium. Especially valuable for YEX yield perps
where funding IS the product.

Note: external venue feeds (CrossVenueFundingRate) are not wired in
production — today this strategy biases quotes from HL funding only.
"""
from __future__ import annotations

Expand Down
2 changes: 1 addition & 1 deletion tests/test_engine_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ def test_all_engine_strategies_registered(self):

def test_total_strategies(self):
from cli.strategy_registry import STRATEGY_REGISTRY
assert len(STRATEGY_REGISTRY) == 18 # 14 original + 4 directional
assert len(STRATEGY_REGISTRY) == 19 # 14 original + 4 directional + cfi_hedge

def test_resolve_engine_strategies(self):
from cli.strategy_registry import resolve_strategy_path
Expand Down
Loading