先入先出(FIFO)比较容易理解,买入早的优先卖出。与之对应的是后人先出(LIFO),就是优先卖出最近买入的,例如:第8行卖出2K,当天还没有买入记录,只能找前一天的买入记录(即第7行),此示例中恰巧二者出入平衡,如果第7行买入数据不足,那么继续向前找(假设还有可用库存)。这只是一个最简单的粗略介绍,如果读者需要更深入了解LIFO的相关知识,请自行网络搜索。
接下来用几个辅助列粗略解释一下统计逻辑
I列
为数量累加值,统计从期初到当前行的数量汇总值,如果数值为0,说明到当前行位置出入平衡J列
为当日的累加值,如果数值为0,说明该天出入平衡,例如:20250410
步骤1:确定起始日期,查找满足如下条件的最后一行日期(或者行号),结果为20250314(第5行),第6行将作为起始行。
- 累加值(I列)为0 【满足此条件的有5行,标记为浅红色】
- 某行是当日最后一条记录 【满足两个条件的有第3,5行,此处应取第5行】
步骤2:在累加列中从上到下查找首次出现的最终累加值(5800)作为终止日期,此示例中为20250409(行号20)。其中的逻辑是由于20行和第28行累加值相同,说明第21~28行的出入平衡,读者可以自行验证。
步骤3:对于起始日期(不包含)到截止日期之间的,剔除当日小计为零的日期,在每日最后一行提取均价作为最新价(填充H列),当日小计作为数量(填充G列)
结果如下图中G列和H列所示。
示例代码如下。
Sub Demo()
Dim rngData As Range, i As Long, sKey As String, v
Dim iSum As Long, sStart As String, sEnd As String
Dim objDic As Object: Set objDic = CreateObject("scripting.dictionary")
Dim objDicDay As Object: Set objDicDay = CreateObject("scripting.dictionary")
Set rngData = Range("A1").CurrentRegion.Offset(1)
Dim arr: arr = rngData.Value
Dim iTotal As Long: iTotal = Application.Sum(rngData.Columns(4))
For i = LBound(arr) To UBound(arr) - 1
sKey = arr(i, 1)
iSum = iSum + arr(i, 4)
objDicDay(sKey) = objDicDay(sKey) + arr(i, 4)
objDic(sKey) = Array(i, iSum, objDicDay(sKey))
If arr(i, 1) <> arr(i + 1, 1) Then
If iSum = 0 Then sStart = arr(i, 1)
If iSum = iTotal And sEnd = "" Then sEnd = arr(i, 1)
End If
Next i
Range("G:H").ClearContents
Range("G1:H1").Value = Array("数量", "最新价")
For Each v In objDic.keys
If v <= sStart Or v > sEnd Or objDic(v)(1) = 0 Or objDic(v)(2) = 0 Then
objDic.Remove v
Else
i = objDic(v)(0)
Cells(i + 1, "G") = objDic(v)(2)
Cells(i + 1, "H") = arr(i, 5)
End If
Next
End Sub
【代码解析】
第4~5行代码创建字典对象。
第6行代码获取当前数据区域向下偏移一行的单元格区域,其目的在于
- 去除标题行
- 单元格区域末尾包含一个空行,后续代码中可以更简洁的定位每日最后数据行
第7行代码将数据读取到数组中。
第8行代码调用工作表函数获取数量
列汇总值,也就是最终累加值。
第9~18行代码循环遍历数据表。
第10行代码获取第一列日期作为字典的键。
第11行代码获取数量累加值。
第12行代码使用objDicDay
统计当日的汇总值。
第13行代码将数组保存在objDic
中,数组包含三个元素
- 第1个元素:当前数据所在行号(注意此处比数据在工作表中的实际行号少一,第6行代码区域偏移导致的)
- 第2个元素:数量累加值
- 第3个元素:当日数量小计
对于每个日期存在多行数据的情况,由于我们只关心最后数据行,所以此处无需使用Exists判断sKey是否存在于字典中。
第14行代码判断下一个数据行的日期是否与当前行相同,如果不同,说明当前行为当日最后一行数据。
第15行代码判断数量累加值是否为0,如果满足条件,那么该数据行的日期作为起始日期(sStart)。
第16行代码判断数量累加值是否与累加终值,并且结束日期为空,如果满足条件,那么该数据行的日期为结束日期(sEnd)。
第19行代码清空G和H列,用于保存结果。
第20行代码填充标题行。
第21~29行代码循环遍历字典对象objDic
,将结果写入工作表。
第22行代码判断字典中数组是否满足如下任意条件
- 行号小于等于起始行
- 行号大于结束行
- 数量累加为0
- 当日数量小计为0
如果满足任意一个条件,第23行代码将从字典中删除该记录(并非必需)。
如果不满足任意条件,那么将结果写入工作表。
第25行代码获取数据行的行号。
第26行代码将当日数量小计写入G列。
第27行代码将均价写入H列。