3. 库存对冲核心算法:FIFO、LIFO 与加权平均

好,咱们今天聊点硬核的。库存对冲算法,说白了就是决定「哪一批货先被卖掉」的规则。你可能会想,这不就是个出库顺序吗?嗯,没那么简单。我当年在做一个电商仓储系统时,就因为选错了算法,导致财务账面和实际库存差了十几万,老板差点让我去仓库搬货……

今天我把三种最主流的算法掰开揉碎讲清楚:先进先出(FIFO)后进先出(LIFO)加权平均。每种我都会给出核心逻辑、代码实现,以及我踩过的坑。

3.1 先进先出(FIFO)对冲算法

FIFO 的逻辑最简单:先入库的货,先出库。这符合大多数人的直觉,也符合很多行业的实际流转规律——比如食品、药品,你总不能让先来的面包过期吧?

我在项目中遇到过一家生鲜电商,他们坚持用 FIFO。为什么?因为水果放不起。第一批进来的草莓如果不出掉,第三天就得扔。所以 FIFO 不仅是算法,更是生存法则。

核心逻辑

  • 每次出库时,从最早的批次开始扣减库存
  • 扣完一个批次,再扣下一个批次
  • 出库成本 = 最早批次的入库成本

代码示例(Python)

class FIFOEngine:
    def __init__(self):
        self.batches = []  # 每个元素: (qty, cost)
    
    def inbound(self, qty, cost):
        self.batches.append([qty, cost])
    
    def outbound(self, qty):
        total_cost = 0
        remaining = qty
        while remaining > 0 and self.batches:
            batch_qty, batch_cost = self.batches[0]
            if batch_qty <= remaining:
                total_cost += batch_qty * batch_cost
                remaining -= batch_qty
                self.batches.pop(0)  # 批次用完,移除
            else:
                total_cost += remaining * batch_cost
                self.batches[0][0] -= remaining
                remaining = 0
        return total_cost
💡 我的经验:FIFO 在代码实现上要注意「批次合并」的问题。如果同一批货分多次入库,建议先合并再处理,否则批次列表会膨胀得很厉害。我曾经见过一个系统,一天产生了 3000 个批次,查询速度直接崩了。

3.2 后进先出(LIFO)对冲算法

LIFO 正好反过来:后入库的货,先出库。你想想看,什么场景下会这样?我最早接触 LIFO 是在钢材贸易行业。钢材价格波动大,新进来的货成本高,老板想先把高成本的货出掉,这样账面利润低,能少交税。嗯,财务上的小心思。

但 LIFO 有个致命问题:实物可能不是后进先出的。比如你仓库里堆着水泥,新来的水泥压在最上面,工人肯定先搬上面的——这倒是符合物理规律。但如果是液体罐,新油和旧油混在一起,LIFO 就完全没法用了。

核心逻辑

  • 每次出库时,从最新的批次开始扣减库存
  • 扣完一个批次,再扣上一个批次
  • 出库成本 = 最新批次的入库成本

代码示例(Python)

class LIFOEngine:
    def __init__(self):
        self.batches = []
    
    def inbound(self, qty, cost):
        self.batches.append([qty, cost])
    
    def outbound(self, qty):
        total_cost = 0
        remaining = qty
        while remaining > 0 and self.batches:
            batch_qty, batch_cost = self.batches[-1]  # 取最后一个
            if batch_qty <= remaining:
                total_cost += batch_qty * batch_cost
                remaining -= batch_qty
                self.batches.pop()
            else:
                total_cost += remaining * batch_cost
                self.batches[-1][0] -= remaining
                remaining = 0
        return total_cost
⚠️ 避坑指南:我曾经在一个 ERP 项目里用了 LIFO,结果审计的时候被查了。因为国内会计准则对 LIFO 有限制,不是所有行业都能用。如果你做的是财务对账系统,一定要先确认合规性。否则,代码写得再好也是白搭。

3.3 加权平均对冲算法

加权平均,说白了就是「不管哪批货,统一按平均成本算」。这个算法最省心,也最常用。尤其是那些无法区分批次的场景——比如散装液体、颗粒物料、或者你根本懒得管批次。

加权平均有两种常见变体:

  • 移动加权平均:每次入库后重新计算平均成本
  • 月末一次加权平均:月底统一算一次,平时只记数量

我个人习惯用移动加权平均,因为实时性更好。但要注意,如果入库频率特别高,计算量会比较大。我做过一个日订单量 10 万+的系统,移动加权平均的 CPU 消耗占了整个库存模块的 40%。后来改成了定时批量计算,才压下去。

核心逻辑

  1. 维护当前总库存数量和总库存成本
  2. 每次入库:更新总数量和总成本
  3. 每次出库:按当前平均成本计算出库成本
  4. 平均成本 = 总成本 / 总数量

代码示例(Python)

class WeightedAverageEngine:
    def __init__(self):
        self.total_qty = 0
        self.total_cost = 0.0
    
    @property
    def avg_cost(self):
        if self.total_qty == 0:
            return 0.0
        return self.total_cost / self.total_qty
    
    def inbound(self, qty, cost):
        self.total_cost += qty * cost
        self.total_qty += qty
    
    def outbound(self, qty):
        if qty > self.total_qty:
            raise ValueError("库存不足")
        cost = qty * self.avg_cost
        self.total_qty -= qty
        self.total_cost -= cost
        return cost
💡 我的建议:加权平均算法在财务上最「平滑」,不会因为某批货价格高导致利润剧烈波动。但要注意精度问题——浮点数运算会有误差,建议用 Decimal 类型,或者保留 4 位小数。我曾经因为浮点数误差,导致月底对账差了 0.01 元,查了整整两天……

3.4 三种算法对比

特性 FIFO LIFO 加权平均
出库顺序 先入先出 后入先出 无顺序,统一平均
适用场景 食品、药品、快消品 钢材、大宗商品(财务需求) 散装物料、无法区分批次
计算复杂度 低(需维护批次队列) 低(需维护批次栈) 极低(仅维护总量)
财务影响 利润偏高(物价上涨时) 利润偏低(物价上涨时) 利润平滑
合规性 全球通用 部分国家限制 全球通用

3.5 算法选择决策流程

你可能会问:那我到底该用哪个?别急,我画了一张流程图,帮你快速决策。

开始选择算法 能否区分批次? (实物可追溯) 加权平均算法 无需批次管理 财务合规要求? (LIFO是否允许) 允许LIFO LIFO 算法 后进先出 不允许LIFO FIFO 算法 先进先出 确定算法,开始实现

这张图我建议你保存下来。每次做新项目,先问自己两个问题:能不能分批次?财务允不允许 LIFO?答案出来,算法就定了。

📌 核心要点:
  • FIFO 最符合直觉,适合保质期敏感的商品
  • LIFO 有财务避税优势,但合规性要提前确认
  • 加权平均最省事,适合无法区分批次的场景
  • 没有「最好」的算法,只有「最合适」的算法

好了,三种算法的核心逻辑和代码实现都讲完了。你可能会觉得代码很简单,嗯,确实不复杂。但真正难的是——当你的系统有 10 万种 SKU、每天几十万笔出入库时,如何保证性能、精度和一致性。这些实战问题,咱们后面会慢慢聊。


专注资料整理