skills/auction-winners-curse-haircut/SKILL.md
Applies a Bayesian haircut to a bid valuation for common-value auctions where winning is itself evidence the bidder over-estimated. Takes a raw valuation, a value-type classification (common_value / private_value / mixed), the number of informed bidders N, and a signal-dispersion estimate, and returns an adjusted valuation. Domain-neutral and reusable across fantasy FAAB, prediction markets, M&A bids, ad-auction budgets, and any generic bidding context. Use when user mentions "winner's curse", "common value auction", "valuation haircut", "adverse valuation", "Bayesian bid adjustment", or "over-paying in auction".
npx skillsauth add lyndonkl/claude auction-winners-curse-haircutInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Scenario: Bidder has estimated a target's value at raw_valuation = $30. Six informed bidders are competing. Estimates across bidders are moderately dispersed (signal_dispersion = 40 out of 100). The target is a well-known commodity (everyone models it similarly).
Inputs:
raw_valuation: 30value_type: common_valuen_informed_bidders: 6signal_dispersion: 40Haircut computation:
haircut_pct = min(35, 10 + log(6) x 5 + 40 x 0.2)
= min(35, 10 + 1.792 x 5 + 8)
= min(35, 10 + 8.96 + 8)
= min(35, 26.96)
= 26.96 (clamped below 35 ceiling)
Output:
adjusted_valuation = 30 x (1 - 0.2696) = $21.91haircut_pct = 26.96classification_rationale: "Common-value target with 6 informed bidders and moderate signal dispersion. Winning is material evidence of over-estimation; Kagel-Levin experimental range (15-30%) applies."applied: trueContrast -- private-value case: Same raw_valuation = $30, but value_type = private_value (target matters uniquely to this bidder). Haircut = 0. Adjusted = $30. Applied = false. Rationale: "No informational-asymmetry discount; winning is not adverse because other bidders do not value the target similarly."
Copy this checklist and track progress:
Winner's-Curse Haircut Progress:
- [ ] Step 1: Classify value_type (common / private / mixed)
- [ ] Step 2: Validate inputs (range checks, N >= 1)
- [ ] Step 3: Short-circuit for private-value
- [ ] Step 4: Compute haircut_pct via formula
- [ ] Step 5: Apply haircut to raw_valuation
- [ ] Step 6: Emit structured output with rationale
Step 1: Classify value_type
Classification is a judgment call and MUST be explicit. The caller should pass it; this skill validates the choice against the decision tree in resources/template.md.
common_value -- target's value is similar for all bidders because information is shared and the underlying quantity is the same (examples: named closer on waivers, headline prospect call-up, publicly traded stock in a tender, liquid commodity)private_value -- target's value is meaningfully higher (or lower) for this bidder than for others, due to fit, complementarity, or idiosyncratic preference (examples: handcuff to a reliever you already own, platoon fit for your lineup, a house next door to an existing property)mixed -- target has a shared core value plus a private-value increment (examples: late-season FAAB claim on a hot hitter where everyone agrees on the base projection but the bidder's specific category need is extra)Step 2: Validate inputs
raw_valuation is a finite non-negative numbervalue_type is one of the three allowed stringsn_informed_bidders is an integer >= 1 (clamp at upper bound if exotic, e.g. 50)signal_dispersion is in [0, 100]Step 3: Short-circuit for private-value
If value_type == "private_value", skip the formula entirely:
haircut_pct = 0adjusted_valuation = raw_valuationapplied = falseSee resources/methodology.md for the Bayesian reason this short-circuit is correct.
Step 4: Compute haircut percentage
For common_value targets:
haircut_pct = min(35, 10 + log(N) x 5 + signal_dispersion x 0.2)
The min(35, ...) ceiling hard-caps the haircut at 35% even at extreme N and dispersion. This reflects that empirical Kagel-Levin estimates rarely exceed 30%; 35% is the outer envelope.
For mixed targets, interpolate:
mix_common_weight = 0.6 (default; caller may override)
mix_private_weight = 1 - mix_common_weight
haircut_pct = mix_common_weight x (common_value_haircut) + mix_private_weight x 0
See resources/methodology.md for formula intuition (log-N captures the adverse-selection severity growing with more competitors; linear dispersion term captures the variance of bidder estimates).
Step 5: Apply haircut
adjusted_valuation = raw_valuation x (1 - haircut_pct / 100)
Step 6: Emit structured output
Return:
{
"adjusted_valuation": <number>,
"haircut_pct": <number in [0, 35]>,
"classification_rationale": "<one-sentence justification>",
"applied": <bool>
}
Validate using resources/evaluators/rubric_auction_winners_curse_haircut.json. Minimum standard: average score >= 3.5.
Pattern 1: Headline Common-Value Target, Many Bidders
auction-first-price-shading for shading)Pattern 2: Private-Value Complement (Handcuff)
private_valuePattern 3: Mixed Late-Season Streaming Claim
mixed with mix_common_weight around 0.5-0.7Pattern 4: Thin-Field Common Value (Low N)
Classification must be explicit. Never infer value_type silently from other inputs. The caller passes it; the skill validates. A missing or ambiguous classification is an error, not a default.
Private-value short-circuit is absolute. If the caller asserts private-value, haircut is zero even when N is large. This is correct: if others genuinely value the target less, then their bids do not carry adverse information about your own estimate.
Never stack this haircut with another winner's-curse correction. Downstream systems that already apply Bayesian bid shading (e.g., auction-first-price-shading's N-bidder shade) are correcting a different phenomenon (strategic shading for expected surplus). Apply both; do not apply either twice.
Cap at 35%. The empirical Kagel-Levin range is 15-30%. The 35% ceiling provides headroom for very large N plus high dispersion but prevents the formula from producing absurd discounts (e.g., 80%).
N >= 1. N = 1 means the bidder is alone; log(1) = 0 so the formula yields haircut_pct = 10 + signal_dispersion x 0.2. For a true monopsony (no competing informed bidder), the caller should pass private_value instead -- there is no adverse-selection mechanism without competitors.
Signal dispersion is a proxy, not a measurement. In practice it is rarely directly observable. Estimate from: historical bid-spread in comparable auctions, disagreement among public projection systems, or degree of public information asymmetry. Document the basis.
Mixed value requires an explicit weight. Do not silently default to 0.6. Callers should state the common/private split and its justification. If they cannot, classify as common_value (conservative) or private_value (aggressive), not mixed.
Domain-neutral contract. This skill does not know about FAAB, fantasy baseball, or any specific auction environment. Callers translate their domain inputs into the generic four-field contract; the skill returns a generic output which the caller then interprets.
Core formula:
if value_type == "private_value":
haircut_pct = 0
applied = false
elif value_type == "common_value":
haircut_pct = min(35, 10 + log(N) x 5 + signal_dispersion x 0.2)
applied = true
elif value_type == "mixed":
common_haircut = min(35, 10 + log(N) x 5 + signal_dispersion x 0.2)
haircut_pct = mix_common_weight x common_haircut # default 0.6
applied = true
adjusted_valuation = raw_valuation x (1 - haircut_pct / 100)
Haircut lookup (common_value, approximate):
| N | dispersion=0 | dispersion=25 | dispersion=50 | dispersion=100 | |---|--------------|---------------|---------------|----------------| | 1 | 10.0% | 15.0% | 20.0% | 30.0% | | 2 | 13.5% | 18.5% | 23.5% | 33.5% | | 4 | 16.9% | 21.9% | 26.9% | 35.0% (cap) | | 6 | 19.0% | 24.0% | 29.0% | 35.0% (cap) | | 8 | 20.4% | 25.4% | 30.4% | 35.0% (cap) | | 12 | 22.4% | 27.4% | 32.4% | 35.0% (cap) |
Input contract:
| Field | Type | Range | Required |
|-------|------|-------|----------|
| raw_valuation | number | >= 0 | yes |
| value_type | string | common_value / private_value / mixed | yes |
| n_informed_bidders | int | >= 1 | yes |
| signal_dispersion | number | [0, 100] | yes |
| mix_common_weight | number | [0, 1] | only if mixed (default 0.6) |
Output contract:
| Field | Type | Range |
|-------|------|-------|
| adjusted_valuation | number | [0, raw_valuation] |
| haircut_pct | number | [0, 40] |
| classification_rationale | string | one sentence |
| applied | bool | false iff private-value |
Key resources:
testing
Cluster a conference's event records into a small set of coarse themes with finer sub-clusters, an explicit outlier bucket, and soft (multi-membership) affinities — using the hybrid embed-then-label pipeline (embed abstracts, reduce, density-cluster, then LLM-label the clusters) when embedding libraries are available, and an LLM-reasoned hierarchical fallback when they are not. Embeddings do the grouping; the LLM only names the groups. Conference-agnostic. Use when turning structured event records into a navigable theme map for preference elicitation and scheduling, when you need 6-8 reasonable themes rather than 20 muddy ones, or when overlapping talks must belong to more than one theme. Trigger keywords - theme clustering, cluster talks, embed then label, soft membership, outlier talks, conference themes, topic map.
development
Build a personal conference schedule as a constraint-optimization problem — hard constraints (no time overlap, room-to-room travel time, capacity/registration, the attendee's own must-attends and blackouts) plus a user-owned weighted objective trading interest against breadth, pacing (maximize contiguous free time), and serendipity. Surfaces unbreakable conflicts (two high-value overlapping talks the model cannot rank) as decisions for the human rather than silently picking, and reports what each choice traded away. Conference-agnostic. Use to turn a preference profile plus a theme map into a day-by-day plan, to resolve overlapping sessions, or to balance a packed vs paced schedule. Trigger keywords - schedule optimization, conference schedule, constraint optimization, overlapping talks, contiguous free time, conflict surfacing, packed vs paced.
development
Parse a heterogeneous conference program (markdown, HTML, PDF-derived text, or JSON) into normalized event records with per-field confidence scores and independent classification axes (topic, depth, format, prerequisites, recorded, capacity). Detects the program's format before extracting, treats every inferred field as uncertain (present vs inferred vs missing), and flags thin or missing abstracts so downstream enrichment can target them. Conference-agnostic. Use when ingesting a conference or event schedule into a structured store, normalizing a talk/session list, or extracting per-session metadata with calibrated confidence. Trigger keywords - program ingestion, parse schedule, session extraction, event records, conference program, talk metadata, per-field confidence.
development
Build a personalized preference profile from a small number of well-chosen, cluster-grounded questions instead of a long survey. Represents the person's interests as an uncertainty region over the theme map, picks the single highest-information-gain choice-based question (contrasting real talks from different clusters), balances exploiting known interests against exploring uncertain ones, deliberately injects outlier probes to fight selection bias, and stops as soon as the schedule would be stable. Also elicits the user-owned objective weights and hard constraints. Interactive — runs where it can actually ask the person. Conference-agnostic. Use to turn a theme map into a preference profile, to decide what to ask a conference attendee, or to elicit scheduling priorities. Trigger keywords - preference elicitation, ask few questions, information gain, choice-based questions, selection bias probe, objective weights, attendee preferences.