Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Traffic Shape Learning

Web traffic follows daily patterns. Promovolve learns these patterns separately for weekdays and weekends using a TrafficShapeTracker with 24 hourly buckets.

TrafficShapeTracker (from source)

class TrafficShapeTracker(
  bucketCount: Int = 24,        // hourly buckets
  alpha: Double = 0.1,          // EMA learning rate
  interpolateVolumes: Boolean = false  // sharp vs smooth peaks
)

Separate Weekday/Weekend Profiles

private val weekdayShape: Array[Double] = Array.fill(24)(1.0)
private val weekendShape: Array[Double] = Array.fill(24)(1.0)
private val todayCount: Array[Long] = Array.fill(24)(0L)  // reset daily

The active shape is selected via setDayType(isWeekend: Boolean) at the start of each day.

Recording & Learning

Per-Request Recording

recordRequest(bucket, time):
    todayCount[bucket] += 1

On Bucket Boundary Change

When traffic moves to a new hour:

observation = requestsInBucket / max(1.0, emaBucketRequests)
shape[bucket] = α × observation + (1 - α) × shape[bucket]
emaBucketRequests = α × requestsInBucket + (1 - α) × emaBucketRequests

Day Rollover Blending

At end of day:

rolloverDay(dayAlpha = 0.2):
    todayNormalized[i] = todayCount[i] / avgCount
    shape[i] = 0.2 × todayNormalized[i] + 0.8 × shape[i]
    reset todayCount

The 0.2 blend rate means about 5 days of data to significantly influence the profile.

CDF for Expected Spend

The traffic shape provides a cumulative distribution function that replaces the linear time fraction in expected spend calculations:

cumulativeFractionAtTime(elapsedSeconds):
    bucket = floor(elapsedSeconds / bucketDurationSec)
    fractionIntoBucket = (elapsedSeconds % bucketDurationSec) / bucketDurationSec

    prevCumulative = sum(shape[0..bucket-1])
    currentContribution = shape[bucket] × fractionIntoBucket

    return (prevCumulative + currentContribution) / sum(all buckets)

Without traffic shape: expectedSpendFraction = elapsedTime / totalTime (linear) With traffic shape: expectedSpendFraction = cumulativeFractionAtTime(elapsed) (shaped)

Relative Volume (for Base Target)

The base target impressions-per-second is scaled by the current hour’s relative volume:

relativeVolumeWithFeedforward(elapsedSeconds, feedforwardWindow):
    bucket = current hour
    currentVol = shape[bucket]
    nextVol = shape[(bucket + 1) % 24]

    if feedforwardWindow > 0 AND near end of bucket:
        // Smooth transition using ease-in-out curve
        blendFactor = position within feedforward window [0, 1]
        smoothBlend = blendFactor² × (3 - 2 × blendFactor)
        effectiveVol = currentVol + smoothBlend × (nextVol - currentVol)
    else:
        effectiveVol = currentVol

    avgVol = sum(all buckets) / 24
    return effectiveVol / avgVol

The feedforward window (default: 0.0 = disabled) allows the system to anticipate the next hour’s traffic pattern and begin adjusting before the bucket boundary.

Volatility Measurement

The coefficient of variation (CV = stddev / mean) of the shape buckets is used to auto-tune PI gains:

  • Low CV → uniform traffic → gentle PI gains
  • High CV → spiky traffic → aggressive PI gains

Site-Level Configuration

Per-site traffic shapes can be pre-configured via PacingConfig:

PacingConfig(
  weekdayShapeVolumes: Option[Vector[Double]],  // 24 hourly values
  weekendShapeVolumes: Option[Vector[Double]],
  dayDurationSeconds: Int = 86400,
  warmupMode: Boolean = false
)

When warmupMode = true, the system records traffic patterns but does not serve ads — useful for learning the traffic shape of a new site before enabling monetization.