You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Python实现FIFO先进先出名义库存账龄计算解决方案

FIFO证券名义库存账龄计算方案实现指南

核心逻辑适配思路

你的需求属于典型的带状态FIFO冲销场景,每一步计算依赖之前的持仓结果,不适合用纯pandas向量化操作实现,用队列结构做逐行迭代是最直接、可维护性最高的方案,不需要复杂的中断循环逻辑。
具体处理步骤:

  • 维护两个队列分别存储未冲销的正持仓、负持仓,每个队列元素记录持仓金额、产生日期,用来计算账龄
  • 额外存储上一交易日的名义本金符号,用于判断是否触发持仓重置
  • 逐行处理每日交易数据:
    1. 先校验当日Notional和上一日Notional的符号是否相反,若相反直接清空两个持仓队列,将当日Notional全额存入对应符号的队列,结束当日处理(触发持仓重置规则)
    2. 未触发重置的话计算当日day trade净额:
      • 净额为正:优先从负持仓队列按先进先出规则逐笔冲销,冲销后仍有剩余的,将剩余金额作为新的正持仓存入正持仓队列
      • 净额为负:优先从正持仓队列按先进先出规则逐笔冲销,冲销后仍有剩余的,将剩余金额作为新的负持仓存入负持仓队列
    3. 冲销过程中可同步记录每笔被冲销持仓的产生日期、持有天数,输出账龄统计结果

适用工具

  • 标准库collections.deque:专门用来实现队列结构,左端弹出(取出最早持仓)的时间复杂度为O(1),比Python原生list的pop(0)效率高很多,适合万级以上的交易数据处理
  • pandas:用于数据预处理、结果输出,遍历数据优先用itertuples(),效率比iterrows()高3~10倍

示例实现代码

import pandas as pd
from collections import deque

# 测试数据和你提供的一致
df = pd.DataFrame()
df['Cusip'] = [123,123,123,123,123,123,123,123,123,123]
df['Day'] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
df['Notional'] = [50, 200, 230, 130, -30, -40, -20, 100, -10, 30]
df['In'] = [50, 150, 30, 0, 0, 0, 20, 120, 0, 40]
df['Out'] = [0, 0, 0, -100, -160, -10, 0, 0, -110, 0]
df['day_trade'] = df['In'] + df['Out']

# 初始化状态
pos_queue = deque()  # 正持仓队列,元素格式(金额, 产生日期)
neg_queue = deque()  # 负持仓队列
prev_notional_sign = 0
result = []

for row in df.itertuples():
    day = row.Day
    notional = row.Notional
    day_trade = row.day_trade
    
    # 步骤1:判断是否触发名义本金符号变更的持仓重置
    current_notional_sign = 1 if notional > 0 else -1 if notional < 0 else prev_notional_sign
    if prev_notional_sign != 0 and current_notional_sign != prev_notional_sign:
        # 持仓重置
        pos_queue.clear()
        neg_queue.clear()
        if notional > 0:
            pos_queue.append((notional, day))
        elif notional < 0:
            neg_queue.append((abs(notional), day))
        prev_notional_sign = current_notional_sign
        # 记录当日结果,可根据需要扩展账龄字段
        result.append({
            'Day': day,
            'Notional': notional,
            'day_trade': day_trade,
            'remaining_pos': sum(x[0] for x in pos_queue),
            'remaining_neg': -sum(x[0] for x in neg_queue)
        })
        continue
    
    # 步骤2:未触发重置,执行day trade的FIFO冲销
    remaining_trade = day_trade
    while remaining_trade != 0:
        if remaining_trade > 0:
            # 正交易,优先冲负持仓
            if not neg_queue:
                # 没有负持仓可以冲,剩余的加入正队列
                pos_queue.append((remaining_trade, day))
                remaining_trade = 0
            else:
                neg_amt, neg_day = neg_queue[0]
                if remaining_trade >= neg_amt:
                    # 冲销整笔负持仓
                    remaining_trade -= neg_amt
                    neg_queue.popleft()
                    # 这里可以加逻辑记录这笔负持仓的账龄:day - neg_day
                else:
                    # 冲销部分负持仓
                    neg_queue[0] = (neg_amt - remaining_trade, neg_day)
                    remaining_trade = 0
        else:
            # 负交易,优先冲正持仓,交易金额取绝对值
            abs_trade = abs(remaining_trade)
            if not pos_queue:
                # 没有正持仓可以冲,剩余的加入负队列
                neg_queue.append((abs_trade, day))
                remaining_trade = 0
            else:
                pos_amt, pos_day = pos_queue[0]
                if abs_trade >= pos_amt:
                    # 冲销整笔正持仓
                    abs_trade -= pos_amt
                    pos_queue.popleft()
                    # 这里可以加逻辑记录这笔正持仓的账龄:day - pos_day
                else:
                    # 冲销部分正持仓
                    pos_queue[0] = (pos_amt - abs_trade, pos_day)
                    abs_trade = 0
                remaining_trade = -abs_trade
    
    # 更新上一日名义本金符号
    prev_notional_sign = current_notional_sign
    # 记录当日结果
    result.append({
        'Day': day,
        'Notional': notional,
        'day_trade': day_trade,
        'remaining_pos': sum(x[0] for x in pos_queue),
        'remaining_neg': -sum(x[0] for x in neg_queue)
    })

# 转成DataFrame查看结果
result_df = pd.DataFrame(result)
print(result_df)

优化点说明

  • 如果需要按不同证券(Cusip)分开计算,只需要按Cusip分组,每组单独初始化队列处理即可
  • 持仓量大的时候可以把队列的总额单独存一个变量,不用每次sum计算,提升效率
  • 冲销过程中可以扩展字段记录每笔持仓的账龄、冲销金额,满足后续统计需求

内容的提问来源于stack exchange,提问作者Leo.W

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 20:09:04