拍卖是一种古老而高效的价格发现机制,从艺术品到无线电频谱都依赖拍卖进行资源配置。本文用Java实现四种经典拍卖模型,深入讲解博弈论中的纳什均衡、占优策略与期望收益最大化,帮助读者理解拍卖设计中的算法原理。
一、拍卖类型与博弈模型
1.1 四种经典拍卖
| 拍卖类型 | 规则 | 最优策略特点 |
|---|---|---|
| 英式拍卖 | 公开竞价,价高者得 | 持续出价直到心理价位 |
| 荷式拍卖 | 价格递减,首个应价者得 | 需精确估计他人保留价 |
| 第一价格密封 | 同时独立出价,最高出价支付 | 出价低于真实估值 |
| 第二价格密封 | 同时独立出价,最高出价支付第二高价 | 真实报价是占优策略 |
1.2 第二价格拍卖的数学证明
设玩家 ( i ) 对商品的估值为 ( v_i ),出价为 ( b_i )。其他玩家的最高出价为 ( B_{-i} )。
玩家收益函数:
[
u_i(b_i) = \begin{cases}
v_i – B_{-i} & \text{if } b_i > B_{-i} \
0 & \text{if } b_i < B_{-i}
\end{cases}
]
定理:在第二价格密封拍卖中, ( b_i = v_i ) 是弱占优策略。
证明:分三种情况讨论 ( b_i ) 与 ( v_i ) 的关系:
– 若 ( b_i > v_i ):当 ( B_{-i} \in (v_i, b_i) ) 时,玩家以高于估值的价格获胜,收益为负
– 若 ( b_i < v_i ):当 ( B_{-i} \in (b_i, v_i) ) 时,玩家错失正收益机会
– 若 ( b_i = v_i ):无论 ( B_{-i} ) 为何值,收益均最优
二、期望收益与最优出价
2.1 第一价格拍卖的最优出价
假设 ( n ) 个玩家的估值 ( v_i ) 独立同分布于 ([0, 1]) 上的均匀分布。对称均衡策略为:
[ b(v) = \frac{n-1}{n} v ]
即玩家应按估值的 ( \frac{n-1}{n} ) 比例出价。
2.2 收益等价定理
在独立私有价值模型下,满足以下条件时四种标准拍卖给卖方带来相同期望收益:
– 风险中性买家
– 估值独立同分布
– 保留价相同
三、Java实现
3.1 拍卖参与者模型
import java.util.*;
/**
* 拍卖参与者(竞拍者)
* 每个竞拍者有自己的私有估值和出价策略
*/
public class Bidder {
private final String name;
private final double trueValue; // 真实估值(私有信息)
private double bid; // 实际出价
private final double riskFactor; // 风险因子(0=风险厌恶, 1=风险中性)
private Random random;
public Bidder(String name, double trueValue, double riskFactor) {
this.name = name;
this.trueValue = trueValue;
this.riskFactor = riskFactor;
this.random = new Random();
}
public String getName() { return name; }
public double getTrueValue() { return trueValue; }
public double getBid() { return bid; }
/**
* 英式拍卖策略:逐步加价直到接近估值
* @param currentPrice 当前最高价
* @param increment 最小加价幅度
* @return 新的出价,若放弃则返回-1
*/
public double englishAuctionBid(double currentPrice, double increment) {
// 策略:出价不超过估值的riskFactor比例
double maxBid = trueValue * riskFactor;
if (currentPrice + increment <= maxBid) {
bid = currentPrice + increment;
return bid;
}
return -1; // 放弃
}
/**
* 荷式拍卖策略:在价格降到阈值时接受
* @param currentPrice 当前报价
* @return 是否接受当前价格
*/
public boolean dutchAuctionAccept(double currentPrice) {
// 策略:当价格降到估值的riskFactor比例时接受
double threshold = trueValue * (1 - (1 - riskFactor) * 0.5);
return currentPrice <= threshold;
}
/**
* 第一价格密封拍卖策略:按估值比例 shading
* @param totalBidders 总竞拍人数
*/
public void firstPriceSealedBid(int totalBidders) {
// 均衡策略:b(v) = (n-1)/n * v
double shadingFactor = (double) (totalBidders - 1) / totalBidders;
bid = trueValue * shadingFactor * riskFactor;
}
/**
* 第二价格密封拍卖策略:真实报价(占优策略)
*/
public void secondPriceSealedBid() {
// 占优策略:按真实估值出价
bid = trueValue;
}
/**
* 计算收益
* @param pricePaid 实际支付价格
* @param won 是否获胜
*/
public double calculateProfit(double pricePaid, boolean won) {
return won ? (trueValue - pricePaid) : 0;
}
@Override
public String toString() {
return String.format("%s[估值=%.2f, 出价=%.2f]", name, trueValue, bid);
}
}
3.2 四种拍卖实现
/**
* 拍卖系统核心类
* 实现四种经典拍卖机制
*/
public class AuctionSystem {
private List<Bidder> bidders;
private double reservePrice; // 保留价
public AuctionSystem(List<Bidder> bidders, double reservePrice) {
this.bidders = new ArrayList<>(bidders);
this.reservePrice = reservePrice;
}
/**
* 英式拍卖(公开升价)
* @param increment 每次最小加价
* @return 拍卖结果
*/
public AuctionResult runEnglishAuction(double increment) {
System.out.println("\n=== 英式拍卖开始 ===");
double currentPrice = reservePrice;
List<Bidder> activeBidders = new ArrayList<>(bidders);
while (activeBidders.size() > 1) {
List<Bidder> nextRound = new ArrayList<>();
for (Bidder bidder : activeBidders) {
double newBid = bidder.englishAuctionBid(currentPrice, increment);
if (newBid > 0) {
currentPrice = newBid;
nextRound.add(bidder);
System.out.printf("%s 出价: %.2f%n", bidder.getName(), currentPrice);
} else {
System.out.printf("%s 放弃%n", bidder.getName());
}
}
activeBidders = nextRound;
if (activeBidders.size() == 1) break;
if (activeBidders.isEmpty()) {
System.out.println("无人出价,流拍");
return new AuctionResult(null, 0, false);
}
}
if (activeBidders.isEmpty()) {
return new AuctionResult(null, 0, false);
}
Bidder winner = activeBidders.get(0);
double profit = winner.calculateProfit(currentPrice, true);
System.out.printf("赢家: %s, 成交价: %.2f, 收益: %.2f%n",
winner.getName(), currentPrice, profit);
return new AuctionResult(winner, currentPrice, true);
}
/**
* 荷式拍卖(公开降价)
* @param startPrice 起始价格
* @param decrement 每次降价幅度
* @return 拍卖结果
*/
public AuctionResult runDutchAuction(double startPrice, double decrement) {
System.out.println("\n=== 荷式拍卖开始 ===");
double currentPrice = startPrice;
while (currentPrice >= reservePrice) {
System.out.printf("当前价格: %.2f%n", currentPrice);
for (Bidder bidder : bidders) {
if (bidder.dutchAuctionAccept(currentPrice)) {
double profit = bidder.calculateProfit(currentPrice, true);
System.out.printf("%s 接受! 成交价: %.2f, 收益: %.2f%n",
bidder.getName(), currentPrice, profit);
return new AuctionResult(bidder, currentPrice, true);
}
}
currentPrice -= decrement;
}
System.out.println("价格降至保留价以下,流拍");
return new AuctionResult(null, 0, false);
}
/**
* 第一价格密封拍卖
* @return 拍卖结果
*/
public AuctionResult runFirstPriceSealed() {
System.out.println("\n=== 第一价格密封拍卖开始 ===");
// 所有竞拍者同时出价
for (Bidder bidder : bidders) {
bidder.firstPriceSealedBid(bidders.size());
System.out.println(bidder);
}
// 找出最高出价者
Bidder winner = null;
double highestBid = reservePrice;
for (Bidder bidder : bidders) {
if (bidder.getBid() > highestBid) {
highestBid = bidder.getBid();
winner = bidder;
}
}
if (winner == null) {
System.out.println("无有效出价,流拍");
return new AuctionResult(null, 0, false);
}
double profit = winner.calculateProfit(highestBid, true);
System.out.printf("赢家: %s, 成交价: %.2f, 收益: %.2f%n",
winner.getName(), highestBid, profit);
return new AuctionResult(winner, highestBid, true);
}
/**
* 第二价格密封拍卖(Vickrey拍卖)
* @return 拍卖结果
*/
public AuctionResult runSecondPriceSealed() {
System.out.println("\n=== 第二价格密封拍卖开始 ===");
// 所有竞拍者按真实估值出价
for (Bidder bidder : bidders) {
bidder.secondPriceSealedBid();
System.out.println(bidder);
}
// 排序找出最高和第二高出价
List<Bidder> sorted = new ArrayList<>(bidders);
sorted.sort((a, b) -> Double.compare(b.getBid(), a.getBid()));
if (sorted.isEmpty() || sorted.get(0).getBid() < reservePrice) {
System.out.println("无有效出价,流拍");
return new AuctionResult(null, 0, false);
}
Bidder winner = sorted.get(0);
double secondHighest = sorted.size() > 1 ? sorted.get(1).getBid() : reservePrice;
secondHighest = Math.max(secondHighest, reservePrice);
double profit = winner.calculateProfit(secondHighest, true);
System.out.printf("赢家: %s, 成交价(第二高价): %.2f, 收益: %.2f%n",
winner.getName(), secondHighest, profit);
return new AuctionResult(winner, secondHighest, true);
}
/**
* 拍卖结果记录
*/
public static class AuctionResult {
public final Bidder winner;
public final double price;
public final boolean sold;
public AuctionResult(Bidder winner, double price, boolean sold) {
this.winner = winner;
this.price = price;
this.sold = sold;
}
}
}
3.3 蒙特卡洛模拟与收益分析
/**
* 拍卖模拟器:大量重复实验比较不同拍卖机制
*/
public class AuctionSimulator {
private Random random = new Random(42);
private int bidderCount;
private double reservePrice;
public AuctionSimulator(int bidderCount, double reservePrice) {
this.bidderCount = bidderCount;
this.reservePrice = reservePrice;
}
/**
* 生成随机竞拍者,估值服从[0,1]均匀分布
*/
private List<Bidder> generateBidders() {
List<Bidder> bidders = new ArrayList<>();
for (int i = 0; i < bidderCount; i++) {
double value = random.nextDouble(); // U[0,1]
double risk = 0.9 + random.nextDouble() * 0.1; // 风险中性偏保守
bidders.add(new Bidder("竞拍者" + (i+1), value, risk));
}
return bidders;
}
/**
* 运行模拟并统计收益
*/
public SimulationResult simulate(int rounds) {
double englishRevenue = 0, dutchRevenue = 0;
double firstPriceRevenue = 0, secondPriceRevenue = 0;
int englishSold = 0, dutchSold = 0;
int firstSold = 0, secondSold = 0;
for (int i = 0; i < rounds; i++) {
List<Bidder> baseBidders = generateBidders();
// 每种拍卖使用相同的竞拍者副本
var r1 = new AuctionSystem(cloneBidders(baseBidders), reservePrice)
.runEnglishAuction(0.01);
var r2 = new AuctionSystem(cloneBidders(baseBidders), reservePrice)
.runDutchAuction(1.0, 0.01);
var r3 = new AuctionSystem(cloneBidders(baseBidders), reservePrice)
.runFirstPriceSealed();
var r4 = new AuctionSystem(cloneBidders(baseBidders), reservePrice)
.runSecondPriceSealed();
if (r1.sold) { englishRevenue += r1.price; englishSold++; }
if (r2.sold) { dutchRevenue += r2.price; dutchSold++; }
if (r3.sold) { firstPriceRevenue += r3.price; firstSold++; }
if (r4.sold) { secondPriceRevenue += r4.price; secondSold++; }
}
return new SimulationResult(
englishRevenue / rounds, dutchRevenue / rounds,
firstPriceRevenue / rounds, secondPriceRevenue / rounds,
(double) englishSold / rounds, (double) dutchSold / rounds,
(double) firstSold / rounds, (double) secondSold / rounds
);
}
private List<Bidder> cloneBidders(List<Bidder> original) {
List<Bidder> clones = new ArrayList<>();
for (Bidder b : original) {
clones.add(new Bidder(b.getName(), b.getTrueValue(), 0.95));
}
return clones;
}
static class SimulationResult {
double englishRev, dutchRev, firstRev, secondRev;
double englishRate, dutchRate, firstRate, secondRate;
SimulationResult(double er, double dr, double fr, double sr,
double erate, double drate, double frate, double srate) {
this.englishRev = er; this.dutchRev = dr;
this.firstRev = fr; this.secondRev = sr;
this.englishRate = erate; this.dutchRate = drate;
this.firstRate = frate; this.secondRate = srate;
}
}
}
3.4 主程序
public class AuctionMain {
public static void main(String[] args) {
// 单次演示
List<Bidder> demoBidders = Arrays.asList(
new Bidder("Alice", 0.8, 0.95),
new Bidder("Bob", 0.6, 0.90),
new Bidder("Carol", 0.9, 0.92),
new Bidder("David", 0.5, 0.88)
);
AuctionSystem auction = new AuctionSystem(demoBidders, 0.1);
auction.runEnglishAuction(0.05);
auction.runDutchAuction(1.0, 0.05);
auction.runFirstPriceSealed();
auction.runSecondPriceSealed();
// 大规模模拟验证收益等价定理
System.out.println("\n=== 大规模蒙特卡洛模拟(1000轮)===");
AuctionSimulator simulator = new AuctionSimulator(5, 0.1);
var result = simulator.simulate(1000);
System.out.printf("英式拍卖 期望收益: %.4f 成交率: %.2f%%%n",
result.englishRev, result.englishRate * 100);
System.out.printf("荷式拍卖 期望收益: %.4f 成交率: %.2f%%%n",
result.dutchRev, result.dutchRate * 100);
System.out.printf("一价密封 期望收益: %.4f 成交率: %.2f%%%n",
result.firstRev, result.firstRate * 100);
System.out.printf("二价密封 期望收益: %.4f 成交率: %.2f%%%n",
result.secondRev, result.secondRate * 100);
}
}
四、复杂度分析
| 项目 | 时间复杂度 | 空间复杂度 | 说明 |
|---|---|---|---|
| 英式拍卖 | ( O(R \times N) ) | ( O(N) ) | R为轮次,N为竞拍人数 |
| 荷式拍卖 | ( O(P / D) ) | ( O(N) ) | P为价格区间,D为降价步长 |
| 一价密封 | ( O(N) ) | ( O(N) ) | 单次遍历找最大值 |
| 二价密封 | ( O(N \log N) ) | ( O(N) ) | 需排序找第二高价 |
| 蒙特卡洛模拟 | ( O(M \times T) ) | ( O(N) ) | M为模拟次数,T为单次拍卖时间 |
五、核心要点总结
- 占优策略:第二价格拍卖中真实报价是弱占优策略,消除了策略性出价的复杂性
- 出价Shading:第一价格拍卖中竞拍者会策略性压低出价,均衡出价为 ( \frac{n-1}{n}v )
- 收益等价:在独立私有价值、风险中性假设下,四种标准拍卖期望收益相等
- 保留价效应:提高保留价可提高卖方收益,但会增加流拍概率
本文通过Java完整实现了四种经典拍卖机制,读者可以修改估值分布、竞拍人数和风险偏好参数,深入探索拍卖理论中的核心博弈策略。