Strategy 01
Funding Rate Capture
Market-neutral yield
Strategy 02
Momentum Following
Trend with ATR stops
Strategy 03
Mean Reversion
Bollinger + RSI
Strategy 04
Kelly Criterion
Optimal sizing
Strategy 05
Statistical Arbitrage
Correlated pairs

All examples use the Purple Flea trading API which connects to Hyperliquid — a decentralized perpetual exchange with 275+ markets, sub-second execution, and no-KYC access. All accounts are denominated in USDC. The API accepts REST calls with a Bearer token.

Risk disclosure: All trading involves risk of loss. These strategies are provided for educational purposes. Past algorithmic performance does not guarantee future results. Always test with small capital before scaling.


1

Funding Rate Capture

Perpetual futures maintain price parity with spot markets through a funding rate — a periodic payment exchanged between long and short holders. When the funding rate is positive, longs pay shorts; when negative, shorts pay longs. The rate is typically exchanged every 8 hours and can range from 0.01% to over 0.3% per period during extreme market conditions.

The funding rate capture strategy is market-neutral: go long when the funding rate is negative (you receive payment for holding the position) and short when it is positive (you receive payment for being short while everyone else is bullishly leveraged). The key insight is that during periods of high open interest and one-sided positioning, funding rates can sustain for days or weeks, generating consistent yield without any directional market view.

On Hyperliquid, funding rates are publicly visible via the API. An agent can scan all 275 markets every hour, identify the highest positive or negative rates, and enter positions accordingly. A 0.1% funding rate per 8 hours compounds to approximately 110% APY if it persists — though in practice rates normalize as arbitrageurs pile in.

strategy_funding_rate.py Python
import requests
from typing import Optional

API_KEY = "YOUR_KEY"
BASE = "https://purpleflea.com/api/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}

def get_all_funding_rates() -> list:
    """Fetch current funding rates for all markets."""
    resp = requests.get(f"{BASE}/trading/funding-rates", headers=HEADERS)
    return resp.json()["markets"]

def find_best_funding_opportunity(min_rate: float = 0.05) -> Optional[dict]:
    """Find market with highest absolute funding rate above threshold."""
    markets = get_all_funding_rates()
    candidates = [
        m for m in markets
        if abs(m["funding_rate_8h_pct"]) > min_rate
    ]
    if not candidates:
        return None
    # Return the market with highest absolute rate
    return max(candidates, key=lambda m: abs(m["funding_rate_8h_pct"]))

def enter_funding_trade(market: str, rate: float, size_usd: float = 200):
    # Go short when rate is positive (longs pay us), long when negative
    side = "short" if rate > 0 else "long"
    resp = requests.post(f"{BASE}/trading/open", headers=HEADERS, json={
        "market": market,
        "side": side,
        "size_usd": size_usd,
        "leverage": 1,      # No leverage for funding trades — pure yield
        "order_type": "market",
        "tag": "funding_capture"
    })
    return resp.json()

# Run every hour — find and enter best funding opportunity
opp = find_best_funding_opportunity(min_rate=0.08)
if opp:
    rate = opp["funding_rate_8h_pct"]
    print(f"Entering {opp['market']}: rate={rate:.3f}%/8h")
    result = enter_funding_trade(opp["market"], rate)
    print(result)
else:
    print("No attractive funding opportunities — holding cash")

Set a cron job to run this script every hour. After each 8-hour funding period, check if the rate has normalized (dropped below your threshold) and close the position if so. The target is to hold only during high-rate periods and exit before the rate compresses.


2

Momentum Following with ATR Stops

Momentum strategies exploit the tendency of assets to continue moving in their current direction after a significant price move. In crypto perpetuals, momentum is particularly pronounced due to the leverage effects of futures liquidations — a rising market triggers short liquidations which in turn push prices higher, creating momentum cascades.

The entry signal uses the exponential moving average crossover: when the fast EMA (e.g., 20-period) crosses above the slow EMA (e.g., 50-period), enter long; when it crosses below, enter short or close. The stop-loss is set using the Average True Range (ATR) — a measure of volatility. A 2x ATR stop keeps you in the trade during normal fluctuations while cutting losses if the trend genuinely reverses.

strategy_momentum.py Python
import requests
import numpy as np

def get_ohlcv(market: str, interval: str = "1h", limit: int = 100):
    resp = requests.get(
        f"https://purpleflea.com/api/v1/trading/candles/{market}",
        headers=HEADERS,
        params={"interval": interval, "limit": limit}
    )
    return resp.json()["candles"]  # list of {o, h, l, c, v, ts}

def ema(prices: np.ndarray, period: int) -> np.ndarray:
    alpha = 2 / (period + 1)
    result = np.zeros_like(prices)
    result[0] = prices[0]
    for i in range(1, len(prices)):
        result[i] = alpha * prices[i] + (1 - alpha) * result[i-1]
    return result

def atr(candles: list, period: int = 14) -> float:
    highs = np.array([c["h"] for c in candles])
    lows  = np.array([c["l"] for c in candles])
    closes = np.array([c["c"] for c in candles])
    tr = np.maximum(
        highs - lows,
        np.maximum(
            np.abs(highs - np.roll(closes, 1)),
            np.abs(lows  - np.roll(closes, 1))
        )
    )
    return np.mean(tr[-period:])

def check_momentum_signal(market: str):
    candles = get_ohlcv(market, interval="1h", limit=60)
    closes = np.array([c["c"] for c in candles])
    ema_fast = ema(closes, 20)
    ema_slow = ema(closes, 50)
    current_atr = atr(candles)
    current_price = closes[-1]

    # Crossover signal on last 2 candles
    was_above = ema_fast[-2] > ema_slow[-2]
    is_above  = ema_fast[-1] > ema_slow[-1]

    if not was_above and is_above:
        stop_loss = current_price - (2 * current_atr)
        return {"signal": "long", "stop_loss": stop_loss, "atr": current_atr}
    elif was_above and not is_above:
        stop_loss = current_price + (2 * current_atr)
        return {"signal": "short", "stop_loss": stop_loss, "atr": current_atr}
    return {"signal": "hold"}

# Check BTC for momentum signal
sig = check_momentum_signal("BTC-PERP")
print(sig)

3

Mean Reversion with Bollinger Bands and RSI

Mean reversion is the complement of momentum: it assumes that extreme price deviations from a moving average will revert to the mean. In range-bound markets (common in low-volume periods), mean reversion strategies outperform momentum strategies. The two can be combined by detecting market regime first.

The setup uses Bollinger Bands (price outside the lower band = oversold, upper band = overbought) combined with RSI (below 30 = oversold, above 70 = overbought) as a confirmation filter. This dual-indicator approach reduces false signals significantly — a price at the lower Bollinger Band alone is not enough; the RSI must also confirm oversold conditions.

strategy_mean_reversion.py Python
import numpy as np
import requests

def bollinger_bands(closes: np.ndarray, period: int = 20, std_dev: float = 2.0):
    sma = np.convolve(closes, np.ones(period)/period, mode="valid")
    rolling_std = np.array([np.std(closes[i:i+period]) for i in range(len(closes)-period+1)])
    upper = sma + (std_dev * rolling_std)
    lower = sma - (std_dev * rolling_std)
    return upper, sma, lower

def rsi(closes: np.ndarray, period: int = 14) -> np.ndarray:
    deltas = np.diff(closes)
    gains = np.where(deltas > 0, deltas, 0)
    losses = np.where(deltas < 0, -deltas, 0)
    avg_gain = np.convolve(gains, np.ones(period)/period, mode="valid")
    avg_loss = np.convolve(losses, np.ones(period)/period, mode="valid")
    rs = avg_gain / (avg_loss + 1e-10)
    return 100 - (100 / (1 + rs))

def check_reversion_signal(market: str):
    candles = get_ohlcv(market, interval="1h", limit=80)
    closes = np.array([c["c"] for c in candles], dtype=float)
    upper, mid, lower = bollinger_bands(closes)
    rsi_vals = rsi(closes)
    price = closes[-1]

    # Oversold: price below lower band AND RSI < 30 → go long
    if price < lower[-1] and rsi_vals[-1] < 30:
        target = mid[-1]       # Take profit at middle band
        stop = lower[-1] * 0.985  # Stop 1.5% below lower band
        return {"signal": "long", "target": target, "stop": stop,
                "rsi": rsi_vals[-1], "band": lower[-1]}

    # Overbought: price above upper band AND RSI > 70 → go short
    if price > upper[-1] and rsi_vals[-1] > 70:
        target = mid[-1]
        stop = upper[-1] * 1.015
        return {"signal": "short", "target": target, "stop": stop,
                "rsi": rsi_vals[-1], "band": upper[-1]}

    return {"signal": "neutral"}

Mean reversion works best on less volatile, more correlated assets (e.g., BTC-PERP and ETH-PERP). On highly volatile small-cap perpetuals, the lower Bollinger Band can be hit and then continue falling dramatically — use this strategy on the top 10 markets by open interest only.


4

Kelly Criterion Position Sizing

The Kelly Criterion is not a strategy in itself — it is an optimal position sizing formula that maximizes long-run wealth growth given a known edge. Applied to any of the above strategies, Kelly sizing prevents both under-betting (leaving profit on the table) and over-betting (risking ruin).

The formula: f = (bp - q) / b, where f is the fraction of bankroll to bet, b is the net odds (payout multiple minus 1), p is the probability of winning, and q = 1 - p is the probability of losing. In practice, traders use fractional Kelly (typically 1/4 or 1/2 Kelly) to reduce variance while retaining most of the growth benefit.

For an algorithmic agent, Kelly sizing requires an estimate of the strategy's win rate and average win/loss ratio — derived from backtesting or live trade history. The agent can continuously update its Kelly fraction as more data accumulates.

kelly_sizing.py Python
from dataclasses import dataclass
import requests

@dataclass
class StrategyStats:
    win_rate: float          # e.g. 0.55 for 55% wins
    avg_win_pct: float       # e.g. 0.025 for 2.5% avg win
    avg_loss_pct: float      # e.g. 0.015 for 1.5% avg loss

def kelly_fraction(stats: StrategyStats, kelly_divisor: int = 4) -> float:
    """Calculate fractional Kelly bet size as fraction of bankroll."""
    b = stats.avg_win_pct / stats.avg_loss_pct  # win/loss ratio
    p = stats.win_rate
    q = 1 - p
    full_kelly = (b * p - q) / b
    return max(0.0, full_kelly / kelly_divisor)

def get_trade_history_stats(api_key: str, strategy_tag: str) -> StrategyStats:
    """Compute win rate and avg win/loss from live trade history."""
    headers = {"Authorization": f"Bearer {api_key}"}
    resp = requests.get(
        f"https://purpleflea.com/api/v1/trading/history",
        headers=headers,
        params={"tag": strategy_tag, "limit": 100}
    )
    trades = resp.json()["trades"]
    wins  = [t["pnl_pct"] for t in trades if t["pnl_pct"] > 0]
    losses = [abs(t["pnl_pct"]) for t in trades if t["pnl_pct"] < 0]
    return StrategyStats(
        win_rate=len(wins) / len(trades),
        avg_win_pct=sum(wins) / len(wins) if wins else 0,
        avg_loss_pct=sum(losses) / len(losses) if losses else 0
    )

# Example: compute Kelly size for next trade
stats = get_trade_history_stats("YOUR_KEY", strategy_tag="momentum")
bankroll = 1000.0  # USDC
f = kelly_fraction(stats, kelly_divisor=4)
trade_size = bankroll * f
print(f"Win rate: {stats.win_rate:.1%} | Kelly: {f:.3f} | Size: ${trade_size:.2f}")

With a 55% win rate and a 1.5:1 win/loss ratio, full Kelly suggests allocating about 16% of bankroll per trade. Quarter Kelly therefore sizes at 4% per trade — conservative but mathematically justified. As the trade history grows and the strategy's edge is confirmed, the Kelly fraction can be increased incrementally. See our in-depth guide to Kelly criterion for AI trading.


5

Statistical Arbitrage on Correlated Asset Pairs

Statistical arbitrage (stat arb) exploits temporary pricing divergences between assets that historically move together. In crypto, BTC and ETH have a historically high correlation (~0.85+). When the spread between them deviates significantly from its historical mean, an agent can go long the underperformer and short the outperformer, expecting the spread to revert.

This is market-neutral: the position makes money regardless of whether the overall market rises or falls, as long as the relative spread reverts. The risk is correlation breakdown — if BTC decouples from ETH for structural reasons (a major protocol event, regulatory action), the spread may not revert on the expected timeframe.

On Hyperliquid, both BTC-PERP and ETH-PERP are available with deep liquidity, making them ideal for pairs trading. The approach: compute the Z-score of the price ratio spread over a rolling window, and enter when the Z-score exceeds ±2.

strategy_stat_arb.py Python
import numpy as np
import requests

def get_close_prices(market: str, limit: int = 60) -> np.ndarray:
    candles = get_ohlcv(market, interval="1h", limit=limit)
    return np.array([c["c"] for c in candles], dtype=float)

def zscore(series: np.ndarray, window: int = 30) -> float:
    """Z-score of last value relative to rolling mean/std."""
    recent = series[-window:]
    mean = np.mean(recent)
    std  = np.std(recent)
    return (series[-1] - mean) / (std + 1e-10)

def stat_arb_signal(asset_a: str = "BTC-PERP", asset_b: str = "ETH-PERP",
                     z_entry: float = 2.0, z_exit: float = 0.5):
    prices_a = get_close_prices(asset_a)
    prices_b = get_close_prices(asset_b)

    # Normalize: compute log price ratio
    ratio = np.log(prices_a) - np.log(prices_b)
    z = zscore(ratio)

    print(f"Spread Z-score: {z:.3f}")

    if z > z_entry:
        # A overpriced vs B: short A, long B
        return {"signal": "spread_short",
                "short": asset_a, "long": asset_b, "z": z}
    elif z < -z_entry:
        # A underpriced vs B: long A, short B
        return {"signal": "spread_long",
                "long": asset_a, "short": asset_b, "z": z}
    elif abs(z) < z_exit:
        return {"signal": "exit_pair", "z": z}

    return {"signal": "hold", "z": z}

def execute_pair_trade(api_key, signal: dict, size_usd: float = 300):
    """Execute both legs of a pair trade atomically."""
    headers = {"Authorization": f"Bearer {api_key}"}
    if signal["signal"] not in ("spread_long", "spread_short"):
        return

    # Open long leg
    requests.post(f"{BASE}/trading/open", headers=headers, json={
        "market": signal["long"], "side": "long",
        "size_usd": size_usd, "leverage": 2, "tag": "stat_arb"
    })
    # Open short leg
    requests.post(f"{BASE}/trading/open", headers=headers, json={
        "market": signal["short"], "side": "short",
        "size_usd": size_usd, "leverage": 2, "tag": "stat_arb"
    })
    print(f"Pair trade entered at Z={signal['z']:.2f}")

# Run pair trade check
sig = stat_arb_signal()
if sig["signal"] in ("spread_long", "spread_short"):
    execute_pair_trade("YOUR_KEY", sig, size_usd=250)

Combining Strategies: The Multi-Signal Agent

A production-ready trading agent does not rely on a single strategy. It allocates capital across all five, dynamically weighting each based on current market regime:

With this allocation framework, the agent is never fully exposed to a single market regime. If momentum fails in a ranging market, mean reversion and funding capture generate consistent returns while waiting for trend conditions to return.

For a complete walkthrough of deploying this multi-strategy agent on Purple Flea, see the trading API documentation. For detailed coverage of funding rate arbitrage and no-KYC perpetuals trading, visit the linked pages.

Get started: All strategies above work with a Purple Flea API key and $50+ in USDC. Get your API key here — no KYC required, account created in seconds.