分享一个流水穿透工具

分享一个流水穿透工具
2023年06月06日 11:01 审计之家

以前发过一个流水穿透闭环工具,传送门:银行流水闭环核查工具

那个是我用VBA写的,可以指定一个终点,穿透所有从起点账户到终点账户的路径,这个工具主要有几个问题:

一是没有考虑时间因素,比如5号A→B,3号B→C,这条路径应该是错的(资金的时间先后),但还是被穿出来了;

二是必须要设置一个终点,如果我想看所有的流水是怎么发散的,看不到;

三是可视化程度比较一般

对于第一个问题,我其实琢磨过很久,如果流水的完整性不能保证的话,日期其实没有太大意义,比如5号A→B,3号B→C,如果考虑日期先后,那么这个路径就不成立了,但万一是因为你少要了一张卡的流水,或者他取现了,缺了2号A→B的数据呢,那么实际应该穿透的反而没有穿透出来..不过完全不考虑日期也有点说不过去..

这次利用了自己的服务器,重写并部署了一个可视化的流水穿透工具给大家免费使用,大概长这样↓

看似比较乱... 别急,接着看↓

01

首先鼠标放在账户的节点(节点就是这个圆圈,代表这个账户)上,可以显示所有他所在的路径(计算了时间先后顺序),比如这里右下角的U账户↓

02

其次,我们可以看到节点有大小之分,颜色深浅之分,线有粗细之分

节点的大小,代表他的边数,最大的点,代表经过他的线是最多的,也就是资金汇集或者分发的地方,比如这个这个最大的节点,选中的时候他所关联的线都会加粗显示↓

03

节点颜色的深浅,是使用了pagerank算法计算的重要度,pagerank算法是谷歌搜索引擎的网页排序算法,可以用在有向图上,感兴趣的可以去搜索一下这个算法,颜色越深代表这个账户在该算法的下的重要性越高。

当然,这个重要性也只是用来作为参考

04

在没有选中任何节点时,线的粗细代表他的金额,金额最大的线就是最粗的,比如这条最粗的线,全场最高,500W↓

05

可以利用筛选的功能,将我们关注的账户筛出来,比如在03图中,我看到了A→F→D这条线,想看看他们的情况,我可以在网页上方的筛选器中依次选择node(节点)-id(名称),并多选账户名↓

或者我想看一下所有与E账户有关的路径,那么筛选edge(边)-from/to-E账户,筛选两次↓

06

最下面还有一些图的物理属性可以设置,比如线的长短,松紧度等等,可以自己玩玩↓

所以这个图看起来好像挺乱,但是配合这些要素,可以比较轻松地浏览一个大概出来,再配合筛选去详细看他们的交易应该能省掉一些翻流水的时间

下面是使用方法:

1.打开流水的填写模板,把信息填进去并保存

2.填完以后打开网页,选择刚保存好的模板,点击预览,即可在线看到解析结果,点击下载即可将文档(html网页文件)下载到本地

注意:

这个解析工具是部署在我自己的服务器上,因此数据是需要上传到我的服务器进行解析的,由于流水是非常敏感的数据,大家肯定在数据的安全性上有更多的考虑,毕竟如果我自己,当我得知什么账套啊,流水数据要上传到别人的服务器解析,那我是坚决不会用的

混淆后会生成字典↓

混淆的代码可以直接打开VBE编辑器(ALT+F11)查看,动没动手脚一看便知↓

如果你既不想混淆,又过于谨慎.. 又想用..

那么写好的代码直接给你,你用python自己跑一下也行

# -*- coding: utf-8 -*-import networkx as nximport pandas as pdimport matplotlib.pyplot as pltfrom pyvis.network import Network
def cmap_to_hex(cmap, value):    rgba = cmap(value)    r, g, b, a = int(rgba[0]*255), int(rgba[1]*255), int(rgba[2]*255), int(rgba[3]*255)    return f"#{r:02x}{g:02x}{b:02x}"
def is_time_ordered(date_list):    for k in range(0, len(date_list) - 1):        if date_list[k] > date_list[k + 1]:            return False    return True
df = pd.read_excel('资金穿透模板.xlsm','数据源')#-----------------------------------------------------------------------------------------G = nx.MultiDiGraph()  # 创建多边有向图grouped = df.groupby(['本方账户', '对方账户', '日期'])['支出'].sum()  # 汇总grouped2 = df.groupby(['本方账户', '对方账户', '日期'])['收入'].sum()  # 汇总
max_value = grouped.max()min_value = grouped.min()  # 获取最小值weight_factor = 3 / (max_value - min_value)  # 假设边的最大宽度为 3
for index, value in grouped.items():    source, target, dated = index  # 解包得到分组的键    date = dated.strftime('%Y-%m-%d')    if value != 0:        weight = int(value * weight_factor)  # 根据金额计算边的粗细        amount_str = "{:,.2f}".format(value)  # 显示千分符        G.add_edge(str(source), str(target), amount=amount_str,                   prev=str(source) + '→' + str(target) + ':' + str(amount_str) + ',时间:' + date,                   weight=weight, date=date)

for index, value in grouped2.items():    target, source, dated = index  # 解包得到分组的键    date = dated.strftime('%Y-%m-%d')    if value != 0:        weight = int(value * weight_factor)  # 根据金额计算边的粗细        amount_str = "{:,.2f}".format(value)  # 显示千分符        G.add_edge(str(source), str(target), amount=amount_str,                   prev=str(source) + '→' + str(target) + ':' + str(amount_str) + ',时间:' + date,                   weight=weight, date=date)

nodesize = dict(G.out_degree)  # 节点大小max_Ns = max(nodesize.values())node_sizes = {}node_size_scale = 800
edgecount = nx.pagerank(G, alpha=0.85)  # 颜色深浅cmap = plt.get_cmap('YlOrRd')
net = Network(height='800px', width='100%', directed=True, notebook=True, filter_menu=True              )  # filter_menu=True  notebook
# 拿到所有路径的列表path_list = []for node in G.nodes:    try:        paths = nx.shortest_path(G, source=node)        path_list.extend(paths.values())    except:        pass
# 将包含该节点的穿透路径(考虑时间)添加title信息for node in G.nodes:    path_node_list = []  # 包含该节点的所有路径    for path in path_list:        if node in path:            path_node_list.append(path)
    # 拿了该节点所有路径以后,判断时间,加载到title    node_path_str = []    for i in range(0, len(path_node_list) - 1):
        if len(path_node_list[i]) > 1:  # 如果子列表有2个元素,才开始遍历,判断时间            date_list = []            for j in range(0, len(path_node_list[i]) - 1):                st = path_node_list[i][j]                ed = path_node_list[i][j + 1]                edge_data = G.get_edge_data(st, ed)  # 拿到所有两个节点的路径
                edge_minindex = min(edge_data, key=lambda x: edge_data[x]['date'])                min_date = edge_data[edge_minindex]['date']  # 拿到所有路径中的最小日期                date_list.append(min_date)
            if len(date_list) == 1:  # 如果时间列表只有一个,那么只有2个节点,不用判断时间对穿透的影响,直接添加                node_path_str.append(path_node_list[i])            else:                if is_time_ordered(date_list):  # 如果时间列表超过一个,那么从前向后判断时间是否小于后者,满足要求再添加                    node_path_str.append(path_node_list[i])
    node_path_title = '\n'.join([' -> '.join(map(str, node_path)) for node_path in node_path_str])    net.add_node(node, value=nodesize[node] / max_Ns * node_size_scale,                 color=cmap_to_hex(cmap, edgecount[node] / max(edgecount.values())),                 alpha=0.8, label=node, title=node_path_title)
for u, v, d in G.edges(data=True):    # 添加边,并设置title属性    net.add_edge(u, v, title=str(d['prev']), width=d['weight'], color='black',                 label=d['date'] + ":" + d['amount'])  # ,label=d['date']
net.force_atlas_2based(spring_length=500, overlap=1, spring_strength=0.001)net.show_buttons(filter_=['physics'])  # filter_=['physics']#---------------------------------------------------------net.show('资金穿透.html')

海量资讯、精准解读,尽在新浪财经APP
算法

VIP课程推荐

加载中...

APP专享直播

1/10

热门推荐

收起
新浪财经公众号
新浪财经公众号

24小时滚动播报最新的财经资讯和视频,更多粉丝福利扫描二维码关注(sinafinance)

7X24小时

  • 06-08 汇隆活塞 833455 3.15
  • 06-08 开创电气 301448 18.15
  • 06-07 西高院 688334 14.16
  • 06-06 飞沃科技 301232 72.5
  • 06-06 恒勃股份 301225 35.66
  • 产品入口: 新浪财经APP-股票-免费问股
    新浪首页 语音播报 相关新闻 返回顶部