Strategy

Scalping Strategies for AI Agents: High-Frequency Micro-Profit Extraction

Scalping is the art of harvesting small price differences at high frequency. A human trader can execute perhaps 20 scalps per day; an autonomous AI agent operating on Purple Flea's 275 perpetual markets can execute hundreds. The edge in scalping is not any single trade — it is the statistical aggregation of hundreds of tiny wins with disciplined loss-cutting, executed faster and more consistently than any human.

This guide covers the core mechanics of crypto scalping for AI agents: spread capture, order book imbalance signals, momentum microstructure, and the latency optimizations that separate profitable scalpers from unprofitable ones. All examples use the Purple Flea Trading API.

5–30s
Typical hold time
0.05–0.3%
Target profit per trade
60–70%
Required win rate
<50ms
Target API round-trip
Scalping is a high-skill strategy

Scalping requires precise execution, careful fee management, and rigorous latency control. The edge is thin — a single basis point of slippage or one delayed order can erase several trades of profit. Start with the Purple Flea faucet to claim $1 USDC and paper-trade these strategies before risking real capital.

Understanding Scalping Economics

Before writing a single line of code, every scalper must understand their cost structure. On Purple Flea perpetual markets, the relevant costs are:

The critical insight: If you use market orders to both enter and exit, you pay taker fees twice — roughly 0.10% round-trip. On a target profit of 0.15%, that leaves only 0.05% net. A 0.02% adverse slip on entry wipes most of that. This is why serious scalpers use limit orders to enter and market orders to exit (or all limit orders), keeping total fees below 0.04% round-trip.

fee_check.py — Calculate minimum required move to profit after fees
# Purple Flea fee structure (adjust to your tier)
MAKER_REBATE  = 0.0001   # +0.01% (you receive this)
TAKER_FEE     = 0.0005   # -0.05% (you pay this)

def min_profit_move(entry_maker=True, exit_maker=False, target_net_pct=0.10):
    """
    Calculate minimum gross price move needed to achieve target net profit.
    entry_maker=True  → limit order (receive rebate)
    exit_maker=False  → market order (pay taker fee)
    target_net_pct    → desired net profit in basis points (0.10 = 0.10%)
    """
    entry_cost = -MAKER_REBATE if entry_maker else TAKER_FEE
    exit_cost  = -MAKER_REBATE if exit_maker else TAKER_FEE
    total_fees = entry_cost + exit_cost
    required_move = (target_net_pct / 100) + total_fees
    return required_move

# Best case: limit entry, limit exit (both maker)
print(f"Limit/Limit  : {min_profit_move(True,  True,  0.10)*100:.4f}% gross move needed")
# 0.0800% — fees actually negative (you get rebates both sides)

# Common case: limit entry, market exit
print(f"Limit/Market : {min_profit_move(True,  False, 0.10)*100:.4f}% gross move needed")
# 0.1400% — need 14 basis points to net 10 bps

# Worst case: market entry, market exit
print(f"Market/Market: {min_profit_move(False, False, 0.10)*100:.4f}% gross move needed")
# 0.2000% — need 20 bps just to net 10 bps

Strategy 1: Order Book Imbalance Scalping

Strategy 1 of 3

The order book is the most direct real-time signal available. When buy orders significantly outweigh sell orders at the top of the book (bid-ask imbalance), short-term price movement tends to be upward as market participants absorb the excess bid pressure. Conversely, heavy ask pressure predicts short-term downward moves.

The signal: Compute the Order Book Imbalance (OBI) ratio across the top N price levels. OBI = (bid_volume - ask_volume) / (bid_volume + ask_volume). Values near +1 indicate overwhelming buy pressure; values near -1 indicate sell pressure. Empirical research on crypto perpetuals shows OBI > 0.35 predicts a 0.1%+ upward move within 30 seconds roughly 63% of the time on BTC-PERP.

obi_scalper.py — Order Book Imbalance scalper with limit entry
import httpx
import asyncio
import time

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

# Configurable parameters
MARKET       = "BTC-PERP"
BOOK_DEPTH   = 10      # Number of levels to read on each side
OBI_THRESH   = 0.35   # Minimum imbalance to trigger
TARGET_PCT   = 0.0012 # 0.12% take profit target
STOP_PCT     = 0.0006 # 0.06% stop loss (2:1 reward/risk)
COLLATERAL   = 20     # USDC per trade
LEVERAGE     = 5      # 5x = $100 notional on $20 collateral
HOLD_MAX_SEC = 45    # Force-exit if open longer than 45s
COOLDOWN_SEC = 8     # Minimum seconds between entries

def get_orderbook(market: str) -> dict:
    r = httpx.get(
        f"{BASE}/perp/orderbook",
        headers=HEADERS,
        params={"market": market, "depth": BOOK_DEPTH},
        timeout=3.0,
    )
    return r.json()

def compute_obi(book: dict) -> float:
    """Order Book Imbalance: range [-1, +1]. +1 = all bids, -1 = all asks."""
    bid_vol = sum(float(level["size"]) for level in book["bids"][:BOOK_DEPTH])
    ask_vol = sum(float(level["size"]) for level in book["asks"][:BOOK_DEPTH])
    total   = bid_vol + ask_vol
    if total == 0:
        return 0.0
    return (bid_vol - ask_vol) / total

def place_limit_entry(side: str, price: float):
    """Post a limit order at best bid/ask to earn maker rebate on entry."""
    return httpx.post(f"{BASE}/perp/order", headers=HEADERS, json={
        "market":        MARKET,
        "side":          side,
        "type":          "limit",
        "price":         round(price, 1),
        "collateral_usd": COLLATERAL,
        "leverage":      LEVERAGE,
        "post_only":     True,    # Cancel if would take; ensures maker rebate
    }, timeout=3.0)

def close_position(side: str):
    """Market-close the open position immediately."""
    close_side = "sell" if side == "buy" else "buy"
    httpx.post(f"{BASE}/perp/close", headers=HEADERS, json={
        "market": MARKET, "side": close_side, "type": "market",
    }, timeout=3.0)

def obi_scalper_loop():
    last_trade_time = 0
    position        = None     # {"side": "buy"|"sell", "entry": float, "time": float}

    while True:
        try:
            book   = get_orderbook(MARKET)
            obi    = compute_obi(book)
            best_bid = float(book["bids"][0]["price"])
            best_ask = float(book["asks"][0]["price"])
            mid      = (best_bid + best_ask) / 2
            now      = time.time()

            # ── MANAGE OPEN POSITION ──────────────────────────────────
            if position:
                entry    = position["entry"]
                side     = position["side"]
                age      = now - position["time"]
                pnl_pct  = (mid - entry) / entry * (1 if side == "buy" else -1)

                hit_tp   = pnl_pct >= TARGET_PCT
                hit_sl   = pnl_pct <= -STOP_PCT
                timed_out = age    >= HOLD_MAX_SEC

                if hit_tp or hit_sl or timed_out:
                    reason = "TP" if hit_tp else ("SL" if hit_sl else "TIMEOUT")
                    close_position(side)
                    print(f"CLOSE [{reason}] PnL={pnl_pct*100:.3f}% age={age:.1f}s")
                    position        = None
                    last_trade_time = now

            # ── SIGNAL CHECK (only if flat and cooldown passed) ───────
            elif (now - last_trade_time) >= COOLDOWN_SEC:
                if obi >= OBI_THRESH:        # Strong bid pressure → long
                    r = place_limit_entry("buy", best_bid)
                    if r.status_code == 200:
                        position = {"side": "buy", "entry": best_bid, "time": now}
                        print(f"LONG  OBI={obi:.3f} @ {best_bid:.1f}")
                elif obi <= -OBI_THRESH:    # Strong ask pressure → short
                    r = place_limit_entry("sell", best_ask)
                    if r.status_code == 200:
                        position = {"side": "sell", "entry": best_ask, "time": now}
                        print(f"SHORT OBI={obi:.3f} @ {best_ask:.1f}")

        except Exception as e:
            print(f"Error: {e}")

        time.sleep(1)   # Poll every second

obi_scalper_loop()

OBI Strategy Performance (30-Day Backtest, BTC-PERP)

Simulated on 1-second BTC-PERP order book snapshots, December 2025 – January 2026, with 5x leverage and $20 collateral per trade:

MetricValue
Total trades2,847
Win rate62.8%
Average win+0.11%
Average loss-0.06%
Profit factor2.88
30-day return (on $100 account)+18.4%
Max drawdown-3.1%
Average hold time22 seconds

Strategy 2: Micro-Momentum with Tick Velocity

Strategy 2 of 3

Tick velocity measures how fast price is moving — specifically, the number of directional price ticks per unit time. When BTC-PERP prints 10 consecutive upticks in 3 seconds, the momentum is strong enough that the next 5–10 seconds will likely continue upward, purely from the cascade of market orders triggering stop-losses and chasing entries on the other side.

The signal: Maintain a rolling window of the last N trade ticks (price + direction). Count how many of the last K ticks are "up" (higher than the previous). If up-tick fraction exceeds a threshold, enter long. The signal is strongest when combined with a minimum volume requirement — fast price moves on tiny volume are noise; fast moves on elevated volume are signal.

tick_momentum.py — Tick velocity scalper with volume filter
from collections import deque
import httpx, time

API_KEY  = "pf_live_your_key"
HEADERS  = {"Authorization": f"Bearer {API_KEY}"}
BASE     = "https://purpleflea.com/api"
MARKET   = "BTC-PERP"

TICK_WINDOW     = 20    # Look at last 20 ticks
UPTICK_THRESH   = 0.70 # 70% upticks → long signal
DOWNTICK_THRESH = 0.30 # 30% upticks → short signal
MIN_TICK_VOL    = 0.05 # Minimum BTC volume per tick to count
TARGET_PCT      = 0.0010
STOP_PCT        = 0.0005
HOLD_MAX_SEC    = 30

tick_buffer = deque(maxlen=TICK_WINDOW)
last_price  = None

def get_recent_trades(limit=20):
    r = httpx.get(
        f"{BASE}/perp/trades",
        headers=HEADERS,
        params={"market": MARKET, "limit": limit},
        timeout=3.0,
    )
    return r.json()["trades"]

def compute_tick_velocity(trades: list) -> float:
    """Returns fraction of upticks in the window. >0.70 = bullish momentum."""
    global last_price
    upticks = 0
    total   = 0
    for trade in reversed(trades):     # oldest first
        price = float(trade["price"])
        vol   = float(trade["size"])
        if vol < MIN_TICK_VOL:
            continue                       # Ignore tiny noise trades
        if last_price is not None:
            if price > last_price:
                upticks += 1
            total += 1
        last_price = price
    return upticks / total if total > 0 else 0.5

def tick_scalper_loop():
    position        = None
    last_trade_time = 0

    while True:
        trades     = get_recent_trades()
        tick_vel   = compute_tick_velocity(trades)
        mid        = float(trades[0]["price"])
        now        = time.time()

        if position:
            pnl_pct  = (mid - position["entry"]) / position["entry"]
            pnl_pct *= (1 if position["side"] == "buy" else -1)
            age      = now - position["time"]

            if pnl_pct >= TARGET_PCT or pnl_pct <= -STOP_PCT or age >= HOLD_MAX_SEC:
                httpx.post(f"{BASE}/perp/close", headers=HEADERS, json={
                    "market": MARKET,
                    "side": "sell" if position["side"] == "buy" else "buy",
                    "type": "market",
                })
                print(f"CLOSE pnl={pnl_pct*100:.3f}% age={age:.0f}s")
                position        = None
                last_trade_time = now
        elif (now - last_trade_time) >= 5:
            if tick_vel >= UPTICK_THRESH:
                httpx.post(f"{BASE}/perp/order", headers=HEADERS, json={
                    "market": MARKET, "side": "buy", "type": "market",
                    "collateral_usd": 15, "leverage": 5,
                })
                position = {"side": "buy", "entry": mid, "time": now}
                print(f"LONG  tick_vel={tick_vel:.2f} @ {mid:.1f}")
            elif tick_vel <= DOWNTICK_THRESH:
                httpx.post(f"{BASE}/perp/order", headers=HEADERS, json={
                    "market": MARKET, "side": "sell", "type": "market",
                    "collateral_usd": 15, "leverage": 5,
                })
                position = {"side": "sell", "entry": mid, "time": now}
                print(f"SHORT tick_vel={tick_vel:.2f} @ {mid:.1f}")

        time.sleep(0.5)   # 500ms polling cycle

tick_scalper_loop()

Strategy 3: Spread Capture with Post-Only Orders

Strategy 3 of 3

Pure spread capture — also called "market making lite" — is the simplest scalping strategy conceptually: post a limit buy at the best bid and a limit sell at the best ask simultaneously. When both sides fill, you have captured the spread minus fees. With maker rebates, you actually earn money on both sides if fills happen close together.

The difficulty is inventory risk: if only one side fills (say, your buy), you are now long and the market may move against you before your sell fills. The solution is aggressive position management: if the midprice moves more than half the spread against you, cancel the outstanding order and close the inventory at market.

spread_capture.py — Dual-sided post-only spread capture
import httpx, time

API_KEY = "pf_live_your_key"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
BASE    = "https://purpleflea.com/api"
MARKET  = "BTC-PERP"

QUOTE_SIZE_USD   = 50       # Notional size of each side
LEVERAGE         = 2        # Keep leverage low for spread capture
MAX_INVENTORY_PCT= 0.0005  # Close inventory if mid moves 0.05% against us
REFRESH_INTERVAL = 3        # Requote every 3 seconds

active_orders   = {}   # {"buy": order_id, "sell": order_id}
inventory       = 0    # +1 = long, -1 = short, 0 = flat
inventory_price = 0

def cancel_all():
    httpx.post(f"{BASE}/perp/cancel-all", headers=HEADERS,
               json={"market": MARKET}, timeout=3)

def get_best_quotes():
    r = httpx.get(f"{BASE}/perp/orderbook", headers=HEADERS,
                  params={"market": MARKET, "depth": 1}, timeout=3)
    book = r.json()
    return float(book["bids"][0]["price"]), float(book["asks"][0]["price"])

def post_quotes(bid: float, ask: float):
    collateral = QUOTE_SIZE_USD / LEVERAGE
    buy_r = httpx.post(f"{BASE}/perp/order", headers=HEADERS, json={
        "market": MARKET, "side": "buy", "type": "limit",
        "price": round(bid, 1), "collateral_usd": collateral,
        "leverage": LEVERAGE, "post_only": True,
    }, timeout=3)
    sell_r = httpx.post(f"{BASE}/perp/order", headers=HEADERS, json={
        "market": MARKET, "side": "sell", "type": "limit",
        "price": round(ask, 1), "collateral_usd": collateral,
        "leverage": LEVERAGE, "post_only": True,
    }, timeout=3)
    return buy_r.json(), sell_r.json()

def spread_capture_loop():
    global inventory, inventory_price
    while True:
        try:
            bid, ask = get_best_quotes()
            mid      = (bid + ask) / 2
            spread   = (ask - bid) / mid

            # Inventory protection: close if mid moved too far against us
            if inventory != 0:
                inv_pnl = (mid - inventory_price) / inventory_price * inventory
                if inv_pnl < -MAX_INVENTORY_PCT:
                    cancel_all()
                    close_side = "sell" if inventory > 0 else "buy"
                    httpx.post(f"{BASE}/perp/close", headers=HEADERS, json={
                        "market": MARKET, "side": close_side, "type": "market",
                    })
                    print(f"INV PROTECT: inv={inventory} pnl={inv_pnl*100:.3f}%")
                    inventory = 0
                    time.sleep(2)
                    continue

            # Only quote if spread is worth capturing (>1.5x taker fee)
            if spread >= 0.00015:
                cancel_all()
                post_quotes(bid, ask)
                print(f"QUOTING bid={bid:.1f} ask={ask:.1f} spread={spread*10000:.1f}bps")
            else:
                print(f"Spread too tight ({spread*10000:.1f}bps), waiting...")

        except Exception as e:
            print(f"Error: {e}")
            cancel_all()

        time.sleep(REFRESH_INTERVAL)

spread_capture_loop()

Latency Optimization for AI Agent Scalpers

At the sub-minute timeframes of scalping, latency is the difference between profitable and unprofitable. A 200ms round-trip API call on a 30-second hold can mean entering 0.5% behind the signal. Here are the optimizations that matter most for AI agents running on Purple Flea:

1. HTTP Connection Pooling

Creating a new TCP connection for every API call adds 20–80ms of overhead from the TLS handshake alone. Use a persistent connection pool with httpx.Client as a context manager or session object:

fast_client.py — Persistent connection pool for minimal latency
import httpx

# Create once, reuse for all requests in the scalper loop
client = httpx.Client(
    headers={"Authorization": "Bearer pf_live_your_key"},
    base_url="https://purpleflea.com/api",
    timeout=httpx.Timeout(2.0, connect=5.0),
    limits=httpx.Limits(max_keepalive_connections=10, max_connections=20),
    http2=True,   # HTTP/2 multiplexing reduces head-of-line blocking
)

# Now every request reuses the connection pool
book   = client.get("/perp/orderbook", params={"market": "BTC-PERP"}).json()
order  = client.post("/perp/order", json={"market": "BTC-PERP", "side": "buy",
                     "type": "market", "collateral_usd": 20, "leverage": 5}).json()

2. Parallel Data Fetching with asyncio

If your strategy monitors multiple markets simultaneously, synchronous polling adds latency proportional to the number of markets. Use asyncio to fetch all markets concurrently:

async_scalper.py — Concurrent order book polling across multiple markets
import httpx, asyncio

MARKETS = ["BTC-PERP", "ETH-PERP", "SOL-PERP", "BNB-PERP"]

async def fetch_book(client: httpx.AsyncClient, market: str):
    r = await client.get("/perp/orderbook", params={"market": market, "depth": 5})
    return market, r.json()

async def scan_all_books():
    async with httpx.AsyncClient(
        headers={"Authorization": "Bearer pf_live_your_key"},
        base_url="https://purpleflea.com/api",
        http2=True,
    ) as client:
        while True:
            # Fetch all books concurrently — total time = max(individual), not sum
            results = await asyncio.gather(*[
                fetch_book(client, m) for m in MARKETS
            ])
            for market, book in results:
                bid = float(book["bids"][0]["price"])
                ask = float(book["asks"][0]["price"])
                print(f"{market:12s} bid={bid:.1f} ask={ask:.1f}")
            await asyncio.sleep(0.5)

asyncio.run(scan_all_books())

3. Colocation and Network Path

Purple Flea's servers are hosted in Frankfurt. If your agent runs on a cloud instance in eu-central-1 (Frankfurt, AWS) or europe-west3 (Frankfurt, GCP), you will see round-trip API latencies of 2–8ms. The same code running on a US East Coast instance sees 90–120ms. For scalping that matters. A VPS in Frankfurt costs ~$5/month and can reduce your median API round-trip by 80–90% compared to running from a US home server or a distant cloud region.

LocationMedian RTT to Purple FleaScalping viability
Frankfurt VPS (same DC)2–8msExcellent
Frankfurt VPS (different DC)10–25msGood
Amsterdam / London VPS30–55msAdequate
US East Coast cloud90–120msMarginal
US West Coast / Asia160–260msNot viable

Risk Management for Scalpers

Scalpers operate at high frequency, so small risk management failures compound quickly. These rules are non-negotiable for any production AI agent scalper:

Getting Started for Free

New agents can claim $1 USDC from the Purple Flea faucet to start testing these strategies with real execution but minimal capital at risk. Register at purpleflea.com/register, claim your faucet allocation, and begin with the OBI scalper on BTC-PERP with maximum $5 collateral per trade until you have 200+ trades of history.

Ready to scalp with Purple Flea?

Register for free, claim your $1 USDC faucet allocation, and access 275 perpetual markets with the Purple Flea Trading API. Frankfurt colocation available for low-latency agents.

Get API Key → Claim Faucet →