Skip to main content

saturday slippage deep dive - where your edge goes to die

woke up at 2am couldn’t sleep.

decided to run a full slippage analysis on last quarter’s trades.

what i found is annoying but fixable.

the invisible tax
#

every algo trader knows slippage exists.

few actually measure it properly.

slippage is the difference between your backtest price and your actual fill.

it’s where your edge goes to die.

my Q4 2025 numbers:

  • total trades: 847
  • theoretical P&L: +$42,300
  • actual P&L: +$31,850
  • slippage cost: $10,450 (24.7% of gross)

that’s insane. almost a quarter of my edge getting eaten by execution.

order size matters more than you think
#

pulled every ES fill from october through december.

Slippage by Order Size

slippage by contract size. anything over 25 contracts and you’re getting murdered. error bars show standard deviation.

the breakpoints:

  • 1-10 contracts: 0.02-0.12% slippage (acceptable)
  • 25-50 contracts: 0.28-0.52% slippage (getting painful)
  • 100+ contracts: 0.89%+ slippage (edge destroyer)

my bigger trades (50+ lots) were killing me.

the algo was right on direction. execution was shit.

timing is everything
#

spent 3 hours analyzing fill quality by time of day.

Execution Timing

blue line is fill quality (% filled at mid or better). red bars show average slippage in basis points. opening and closing hours suck.

when to trade:

  • 9:30-10:30 EST: best fills, lowest slippage (makes sense - most liquidity)
  • 12:00-14:00 EST: lunch doldrums - still decent
  • 6:00-9:00 EST: pre-market garbage - avoid if possible
  • 15:30-16:00 EST: closing auction chaos - slippage spikes

been trading too many overnight positions that fill during pre-market.

bad habit. fixing it.

the fix
#

wrote a simple execution optimizer.

from dataclasses import dataclass
from typing import List, Tuple
from enum import Enum
import numpy as np

class OrderUrgency(Enum):
    LOW = "low"      # can wait for good fill
    MEDIUM = "medium"  # prefer good fill, but time sensitive
    HIGH = "high"    # need to fill now, accept slippage

@dataclass
class ExecutionConfig:
    max_single_order: int = 25  # contracts
    slippage_threshold: float = 0.25  # max acceptable %
    optimal_hours: Tuple[int, int] = (9, 14)  # EST
    twap_duration_minutes: int = 15

class ExecutionOptimizer:
    def __init__(self, config: ExecutionConfig):
        self.config = config

    def split_order(self, total_contracts: int) -> List[int]:
        """Break large order into digestible chunks"""
        if total_contracts <= self.config.max_single_order:
            return [total_contracts]

        chunks = []
        remaining = total_contracts

        while remaining > 0:
            # vary chunk sizes to avoid detection
            chunk = min(
                remaining,
                np.random.randint(
                    int(self.config.max_single_order * 0.6),
                    self.config.max_single_order + 1
                )
            )
            chunks.append(chunk)
            remaining -= chunk

        return chunks

    def should_delay(self, current_hour: int, urgency: OrderUrgency) -> bool:
        """Check if we should wait for better execution window"""
        if urgency == OrderUrgency.HIGH:
            return False

        optimal_start, optimal_end = self.config.optimal_hours
        in_optimal = optimal_start <= current_hour <= optimal_end

        if urgency == OrderUrgency.LOW:
            return not in_optimal

        # MEDIUM: only delay if we're in really bad hours
        bad_hours = [6, 7, 8, 15, 16]
        return current_hour in bad_hours

    def calculate_twap_schedule(
        self,
        total_contracts: int,
        duration_minutes: int = None
    ) -> List[Tuple[int, int]]:
        """Generate TWAP schedule (minute_offset, contracts)"""
        duration = duration_minutes or self.config.twap_duration_minutes
        chunks = self.split_order(total_contracts)

        # spread chunks evenly with some randomness
        base_interval = duration / len(chunks)
        schedule = []

        current_time = 0
        for chunk in chunks:
            # add jitter to avoid predictable patterns
            jitter = np.random.uniform(-0.2, 0.2) * base_interval
            execute_at = max(0, current_time + jitter)
            schedule.append((int(execute_at), chunk))
            current_time += base_interval

        return schedule

    def estimate_slippage(self, contracts: int, hour: int) -> float:
        """Estimate slippage based on historical data"""
        # size factor
        if contracts <= 10:
            size_slip = 0.05
        elif contracts <= 25:
            size_slip = 0.15
        elif contracts <= 50:
            size_slip = 0.35
        else:
            size_slip = 0.60

        # time factor
        time_multipliers = {
            6: 1.8, 7: 1.5, 8: 1.3,
            9: 0.8, 10: 0.7, 11: 0.9,
            12: 1.0, 13: 0.95, 14: 0.85,
            15: 1.4, 16: 1.6
        }
        time_mult = time_multipliers.get(hour, 1.0)

        return size_slip * time_mult

nothing fancy.

split big orders into smaller chunks.

wait for good execution windows when possible.

add some randomness so the algos don’t front-run me.

results so far
#

been running the optimizer for 2 weeks.

before optimization (Q4):

  • avg slippage: 0.31%
  • worst day: 0.89%
  • slippage as % of gross P&L: 24.7%

after optimization (jan 1-17):

  • avg slippage: 0.14%
  • worst day: 0.38%
  • slippage as % of gross P&L: 11.2%

cut slippage in half.

that’s real money - probably $4-5k/month back in my pocket.

what i learned
#

1. measure everything

if you’re not tracking slippage per trade, you’re flying blind.

2. size kills

anything over 25 contracts on ES needs to be split. period.

3. timing matters

pre-market and close are slippage traps. avoid unless you have to.

4. don’t be predictable

add randomness to order sizes and timing. the HFT algos are watching.

been sharing some of this analysis with other algo traders on NexusFi - execution optimization is one of those topics that separates hobbyists from profitable traders.

next steps
#

building a real-time slippage tracker that alerts me when fills are worse than expected.

if slippage spikes mid-day, something’s wrong with market microstructure.

want to know immediately so i can pause the algo.


2:45am saturday. couldn’t sleep so ran a slippage analysis instead. found i was losing 25% of my edge to execution. fixed it by splitting orders and timing execution better. cut slippage in half in 2 weeks. measure everything.

-AK

Related

volatility regime detection - when to switch strategies
the market doesn’t care what strategy you’re running. it runs whatever regime it wants. your job is to detect the regime and adapt. why regime matters # every strategy has conditions where it crushes and conditions where it bleeds.
first week 2026 - january momentum algo kicking off
new year. new momentum. first real trading week of 2026 in the books. january effect algo activated. the january effect # some people think it’s BS.
year-end portfolio rebalancing algo - detecting institutional flows
december means institutional rebalancing. pension funds, endowments, mutual funds all adjusting. built an algo to detect and trade the flows. the concept # year-end rebalancing patterns:
vix term structure algo - contango/backwardation trading
been researching VIX term structure trades. contango vs backwardation. predictable patterns. finally got an algo working. the concept # contango: front month VIX < back month VIX
sector rotation algo - implementation with relative strength scoring
been working on a sector rotation algo. concept: own the strongest sectors, short the weakest. simple in theory. complex in implementation. the core idea # sectors rotate in predictable cycles.
earnings volatility filter - implementation and early results
been running the earnings volatility filter for a week now. early results are promising. the problem # earnings = binary events. stock moves 5-10% or nothing.