每日算法 — 使用java实现拍卖:博弈论与最优出价策略

拍卖是一种古老而高效的价格发现机制,从艺术品到无线电频谱都依赖拍卖进行资源配置。本文用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为单次拍卖时间

五、核心要点总结

  1. 占优策略:第二价格拍卖中真实报价是弱占优策略,消除了策略性出价的复杂性
  2. 出价Shading:第一价格拍卖中竞拍者会策略性压低出价,均衡出价为 ( \frac{n-1}{n}v )
  3. 收益等价:在独立私有价值、风险中性假设下,四种标准拍卖期望收益相等
  4. 保留价效应:提高保留价可提高卖方收益,但会增加流拍概率

本文通过Java完整实现了四种经典拍卖机制,读者可以修改估值分布、竞拍人数和风险偏好参数,深入探索拍卖理论中的核心博弈策略。