Bids that correct themselves
Automated bids on DAX measures over time frames
A manager edits bids by hand once a day — and still lags the market. We made it so the bid computes itself from a clear rule, and the human only sets the goal.
The target bid per SKU is a DAX measure inside a time frame: take the ACoS target, actual CTR/CR and margin, compute the safe ceiling. A Go service pulls the result every few minutes and pushes thousands of bids through the API.
01 Why manual bidding always lags
Advertising on a marketplace lives by the hour: in the morning the bid is fine, by midday competitors have raised the cost per click, and you either overpay or sink in the ranking. The manager opens the console, scans dozens of campaigns, edits bids by hand — and still reacts half a day late. And there are thousands of SKUs.
The main trouble isn't even speed, it's the logic. "Bump it $0.10 where it's bad" isn't a strategy. It's unclear how much you can pay per click for the ad to still be in the black given the margin of that specific product.
02 The idea: the target bid is a measure, not a button
We flip the task. Instead of "what bid do I set?", you answer one clear question: how much you're willing to spend on ads per $100 of revenue for this product (that's ACoS — advertising cost of sales). Then the math turns the goal into a cost per click.
The logic is simple: if out of 100 clicks three buy on average, and the average order value is known — then it's clear how much revenue one click brings. Multiply by the target ACoS — and you get how much that click is worth paying for. Drag the ACoS goal and watch the target bids recompute:
| SKU | Time frame | CR | AOV | Rev / click | Target bid |
|---|
* below ~30 clicks in a frame the measure stays silent — too little data to trust the funnel. Such rows are dimmed.
03 Time frames: the key trick
The same product sells differently in the morning, midday and on weekends. So we don't compute one "average" bid but a separate one for each characteristic stretch of time — a "frame". The system knows this SKU converts better on Sunday evening, and calmly raises the ceiling exactly then.
CR 2.8% · bid ↓
CR 3.6% · bid ↑
CR 4.1% · bid ↑↑
CR 1.2% · bid ↓↓
CR and buyout are computed inside the frame, so the bid adapts to the daily and weekly rhythm of demand instead of being smoothed into an average. The table above already uses these per-frame conversion rates.
04 How bids reach the console
Computing the bid isn't enough — it has to be set in the console, and not one but thousands. A small service does that: every 15 minutes it takes the fresh target bids from the model and carefully sends them to the Amazon Advertising console through its official interface. The human only watches the goal.
01 Why manual bidding breaks
Manual bidding breaks on two things: frequency and the unit of decision. A person edits bids "once a day", while the auction shifts over hours. And the decision is made "by eye, per campaign", whereas the economically correct unit is the tuple SKU × placement × time frame, each with its own bid ceiling.
So you don't need "one more export" but a measure that, for each such tuple, computes the maximum allowable bid from the target ACoS and the actual funnel — and recomputes as often as we can deliver the result to the console.
02 The target bid from the funnel
The target bid is derived from the funnel. Expected revenue per click is the conversion to order times the average order value (adjusted for buyout). Multiply by the target ACoS — and you get the cost-per-click ceiling:
CR — click→order conversion · AOV — average order value · Buyout — buyout share · ACoS — target cost share
All four factors are measures that already live in the model for reporting. Autobid doesn't add a new source of truth — it reuses the same one as the operating-summary dashboards. So the bid and the report never diverge.
03 Time frames in DAX
A frame is a funnel-aggregation window: e.g. day-of-week × hour-bucket, or rolling 7/14/28 days for smoothing. In DAX it's a measure that respects the current frame context. A simplified skeleton:
// Target bid in the current frame context (SKU × placement × window) Target Bid = VAR _cr = DIVIDE( [Frame orders], [Frame clicks] ) VAR _aov = DIVIDE( [Frame revenue], [Frame orders] ) VAR _buy = [Frame buyout share] // 0..1 VAR _acos = [ACoS Target] // set by the product owner VAR _raw = _cr * _aov * _buy * _acos // protection against small-data spikes VAR _safe = MINX( { _raw, [Bid Ceiling], [Prev bid × 1.5] }, [Value] ) RETURN IF( [Frame clicks] >= 30, ROUND( _safe, 2 ), BLANK() )
04 How bids reach the console
The model is the source of truth, delivery is a separate layer. The calculation is exported (a table SKU · placement · frame · bid), and a Go service diffs it against the current console state and sends only the changes, in batches, respecting API limits:
Go wasn't chosen for fashion: thousands of requests with retries, backoff and strict rate-limit compliance is about concurrency, where Go is strong. Each run is idempotent: it fails mid-way — a re-run finishes the rest without double-setting bids.
- Diff, not overwrite: we send only the bids that actually changed — fewer requests, less risk of hitting the limit.
- Kill switches: a daily step limit, a global "wheel" to pause, an SKU whitelist — for promos and non-standard situations.
- Log: every send is recorded — what, when, from which old bid to which new one and why.
05 What it gave
The manager stopped living in the ad console. Instead of two or three hours of manual edits a day, they set an ACoS goal and watch the result. Advertising reacts to the market in 15 minutes, not "when there's time", and stays within the economics of each product.
| Metric | Before (manual) | After |
|---|---|---|
| Bids in the target ACoS band | 32% | 88% |
| Time on bidding | 2–3 h/day | ~0 |
| Reaction period | ~1 day | 15 min |
| SKU coverage | top, by hand | 3,400+ auto |
Let's compute the target bid on your data
In a free review we'll show what ACoS your SKUs hold right now and how much you overpay per click because of manual bidding. No obligation.
