《赌客信条》不可不知的行为经济学

本文介绍了行为经济学中的前景理论,包括确定效应、反射效应、损失规避和参照依赖四个原理,并通过实验说明了这些原理如何影响人们的决策。

最近读了《赌客信条》这本书,是关于行为经济学的,觉得很有意思,择录一些内容共享!

 

前景理论

由下面4个原理组成:
1.确定效应:在确定的好处(收益)和“赌一把”之间,做一个抉择,多数人会选择确定的好处。

所谓确定效应,就是在确定的好处(收益)和“赌一把”之间,做一个抉择,多数人会选择确定的好处。用一个词形容就是“见好就收”,用一句话打比方就是“二鸟在林,不如一鸟在手”,正所谓落袋为安。

  让我们来做这样一个实验。

  A.你一定能赚30 000元。

  B.你有80%可能赚40 000元,20%可能性什么也得不到。

  你会选择哪一个呢?

  实验结果是,大部分人都选择A。

  传统经济学中的“理性人”这时会跳出来批判:选择A是错的,因为40 000×80%=32 000,期望值要大于30 000。

  这个实验结果是对“原理1”的印证:大多数人处于收益状态时,往往小心翼翼、厌恶风险、喜欢见好就收,害怕失去已有的利润。卡尼曼和特韦斯基称为“确定效应”(certainty effect),即处于收益状态时,大部分人都是风险厌恶者。

  “确定效应”表现在投资上就是投资者有强烈的获利了结倾向,喜欢将正在赚钱的股票卖出。

2.反射效应:在确定的坏处(损失)和“赌一把”之间,做一个抉择,多数人会选择“赌一把”。

当一个人在面对两种都损失的抉择时,会激起他的冒险精神。在确定的坏处(损失)和“赌一把”之间,做一个抉择,多数人会选择“赌一把”,这叫“反射效应”。用一句话概括就是“两害相权取其轻”。

  让我们来做这样一个实验。

  A.你一定会赔30 000元。

  B.你有80%可能赔40 000元,20%可能不赔钱。

  你会选择哪一个呢?投票结果是,只有少数人情愿“花钱消灾”选择A,大部分人愿意和命运抗一抗,选择B。

  传统经济学中的“理性人”会跳出来说,两害相权取其轻,所以选B是错的,因为(-40 000)×80%=-32 000,风险要大于-30 000元。

  现实是,多数人处于亏损状态时,会极不甘心,宁愿承受更大的风险来赌一把。也就是说,处于损失预期时,大多数人变得甘冒风险。卡尼曼和特韦斯基称为“反射效应”(reflection effect)。

  “反射效应”是非理性的,表现在股市上就是喜欢将赔钱的股票继续持有下去。统计数据证实,投资者持有亏损股票的时间远长于持有获利股票。投资者长期持有的股票多数是不愿意“割肉”而留下的“套牢”股票。

3.损失规避:多数人对损失比对收益更为敏感。

 如何理解“损失规避”?用一句话打比方,就是“白捡的100元所带来的快乐,难以抵消丢失100元所带来的痛苦”。

  前景理论最重要也是最有用的发现之一是:当我们做有关收益和有关损失的决策时表现出的不对称性。对此,就连传统经济学的坚定捍卫者——保罗?萨缪尔森,也不得不承认:“增加100元收入所带来的效用,小于失去100元所带来的效用。”

  这其实是前景理论的第3个原理,即“损失规避”(loss aversion):大多数人对损失和获得的敏感程度不对称,面对损失的痛苦感要大大超过面对获得的快乐感。

  行为经济学家通过一个赌局验证了这一论断。

  假设有这样一个赌博游戏,投一枚均匀的硬币,正面为赢,反面为输。如果赢了可以获得50 000元,输了失去50 000元。请问你是否愿意赌一把?请做出你的选择。

  A.愿意

  B.不愿意

  从整体上来说,这个赌局输赢的可能性相同,就是说这个游戏的结果期望值为零,是绝对公平的赌局。你会选择参与这个赌局吗?

  但大量类似实验的结果证明,多数人不愿意玩这个游戏。为什么人们会做出这样的选择呢?

  这个现象同样可以用损失规避效应解释,虽然出现正反面的概率是相同的,但是人们对“失”比对“得”敏感。想到可能会输掉50 000元,这种不舒服的程度超过了想到有同样可能赢来50 000元的快乐。

  由于人们对损失要比对相同数量的收益敏感得多,因此即使股票账户有涨有跌,人们也会更加频繁地为每日的损失而痛苦,最终将股票抛掉。

  一般人因为这种“损失规避”(loss aversion),会放弃本可以获利的投资。

4. 参照依赖:多数人对得失的判断往往由参照点决定。

到底什么是“得”,什么是“失”呢?

  你今年收入20万元,该高兴还是失落呢?假如你的奋斗目标是10万元,你也许会感到愉快;假如目标是100万元,你会不会有点失落呢?

  所谓的损失和获得,一定是相对于参照点而言的。卡尼曼称为“参照依赖”(Reference Dependence)。

  老张最幸福的时候是他在20世纪80年代做“万元户”的时候,虽然现在自己的村镇已经改造成了城市,拆迁补贴也让自己成为了“百万元户”,但他感觉没有当年兴奋,因为邻里都是“百万元户”了。

  讲这个故事的用意不难明白,我们就不再进行烦琐的论证了——得与失都是比较出来的结果。

  传统经济学的偏好理论(Preference theory)假设,人的选择与参照点无关。行为经济学则证实,人们的偏好会受到单独评判、联合评判、交替对比及语意效应等因素的影响。

一般人对一个决策结果的评价,是通过计算该结果相对于某一参照点的变化而完成的。人们看的不是最终的结果,而是看最终结果与参照点之间的差额。

  一样东西可以说成是“得”,也可以说成是“失”,这取决于参照点的不同。非理性的得失感受会对我们的决策产生影响。

<think> 我们要求的功能如下: 1. 将时间单位改为分钟:在原来的代码中,时间是以小时为单位的,现在改为分钟。主要涉及以下几个参数: - 最大交易时间间隔(原为分钟,不需要改,但注意单位是分钟) - 时间窗口(原为小时,现在改成分钟,所以需要乘以60) - 最大出账时间差(原为小时,现在改成分钟,乘以60) 2. 交易对手标签作为多选条件:用户可以在界面上添加多个标签,每个标签对应一个名称和需要的数量。我们需要将这些条件传递到分析过程中。 在分析连续进账交易时,不仅要满足连续次数,还要满足在连续交易中,各个标签出现的次数符合设定的数量。 3. 修改分析逻辑:在`analyze_data_thread`方法中,找到连续序列后,需要检查该连续序列中各个标签的出现次数是否满足条件。 4. 在界面上,我们需要增加一个区域来设置标签条件(标签名称和所需数量)。但由于之前已经添加了“交易对手标签”的添加和管理功能,现在需要扩展为可以设置每个标签的数量。 考虑到界面复杂度,我们可以在“管理交易对手标签”的窗口中,为每个标签添加一个“数量”输入框。但为了不使界面过于复杂,我们暂时不在主界面上增加太多控件,而是通过管理标签的窗口来设置每个标签所需的数量。 修改思路: - 在`opponent_tags`字典中,原来存储的是标签名称和条件(之前是空列表,现在改为存储所需数量,或者更复杂的条件?)。但为了简单,我们可以存储为:{标签名: 所需数量}。 - 在添加标签时,不仅输入标签名称,还要输入该标签在连续交易中需要出现的次数。因此,修改“添加标签”的界面,增加一个数量输入框。 - 在分析时,对于每个连续序列,我们统计该序列中每个标签出现的次数(即交易对手标签为该标签的交易次数),然后与设定的数量比较。 由于改动较大,我们将分步骤进行: 步骤1:修改添加标签的界面,支持输入数量 - 在`add_opponent_tag`方法中,我们原来只是添加标签名称,现在需要同时输入数量。 - 因此,我们创建一个新的弹窗,包含两个输入框:标签名称和数量。 步骤2:修改`opponent_tags`的存储结构,从原来的{标签名: []}改为{标签名: 数量} 步骤3:在分析连续序列时,增加标签条件的检查。 步骤4:将时间相关的参数转换为分钟。 由于时间关系,我们只修改核心逻辑,界面调整可能较为复杂,我们尽量简化。 但是,用户上一次的问题中要求的是:最小连续次数为5,交易对手标签要求赌客卡数量为1,上分卡数量为2。因此,我们需要同时满足多个标签条件。 注意:一个交易对手标签只能属于一个标签类别吗?是的,我们假设一笔交易对手标签只属于一个类别(例如,一笔交易对手标签要么是“赌客卡”,要么是“上分卡”)。 因此,在连续序列中,我们需要统计每个标签出现的次数,然后和设定的数量进行比较。 由于改动较大,我们重新设计`opponent_tags`的数据结构为字典,键为标签名称,值为该标签在连续交易中应该出现的最小次数(注意:这里我们要求的是恰好等于?还是至少?问题中要求的是“包含1次赌客卡,2次上分卡”,我们理解为至少1次赌客卡和至少2次上分卡,但也可以理解为恰好?但用户的需求可能是至少,所以我们按照至少来设计)。 因此,在连续序列中,对于每一个标签,实际出现的次数必须大于等于设定的次数。 具体修改如下: 1. 在`add_opponent_tag`方法中,我们创建一个新的窗口,包含两个输入框:标签名称和数量(整数)。点击确定后,将{标签名: 数量}存入`self.opponent_tags`。 2. 在`manage_opponent_tags`方法中,显示标签列表时,同时显示每个标签的名称和数量。删除功能不变。 3. 在分析线程中,当我们找到一个连续序列(seq)后,我们统计这个序列中每个标签出现的次数(注意:一笔交易有一个交易对手标签,这个标签应该是用户之前在界面上设置的某个标签吗?这里我们需要明确:在原始数据中,有一列是“交易对手标签”,而我们在界面上设置的标签条件是指定的几个类别。我们需要检查每一笔交易的“交易对手标签”列的值是否等于我们设置的标签名称? 但是,这里有一个问题:原始数据中的交易对手标签是一个具体的字符串,而我们设置的标签名称是一类。例如,原始数据中可能有“赌客卡A”、“赌客卡B”,但我们设置的标签名称是“赌客卡”。这样就需要模糊匹配?或者,我们之前的管理标签的功能,实际上是为某些特定的交易对手卡号打标签?还是为交易对手标签(字符串)打标签? 回顾之前的设计,我们在“添加标签”时,并没有明确标签是如何与交易对手卡号关联的。在之前的代码中,我们只是简单存储了标签名称,然后在分析时,检查交易对手卡号是否包含某些字符串(如果条件不为空)。但现在,我们需要改变设计。 为了满足需求,我们重新定义:交易对手标签是指每一笔交易中,用户给交易对手卡号打上的类别标签。我们需要在管理标签时,为每个标签设置匹配规则(可以是包含某些字符串,或者是精确匹配等)。但这样会使问题复杂化。 考虑到时间,我们简化处理:我们假设在原始数据中,有一列“交易对手标签”,它的值是用户预设的标签类别(如“赌客卡”、“上分卡”)。这样,在连续序列中,我们直接统计这一列的值即可。 因此,我们需要在字段映射中增加一个“交易对手标签”字段的映射。但之前的字段映射中并没有这一字段。所以,我们需要修改字段映射,增加一个“交易对手标签”字段(之前已经存在)。 在之前的代码中,我们已经预留了5个字段映射:交易卡号、交易时间、交易金额、收付标志、对方卡号。现在我们需要增加一个“交易对手标签”字段。 修改步骤: - 在字段映射区域增加一行:“交易对手标签” - 在分析时,如果用户没有映射该字段,则无法使用标签条件(可以忽略标签条件,或者提示错误?) 由于这是一个重要改动,我们修改如下: 1. 在初始化时,将`required_fields`增加一个“交易对手标签”: required_fields = ['交易卡号', '交易时间', '交易金额', '收付标志', '对方卡号', '交易对手标签'] 2. 在分析线程中,如果用户没有设置“交易对手标签”的映射,则无法使用标签条件。但我们仍然可以进行连续交易分析,只是标签条件会被忽略?或者,如果用户设置了标签条件(即`self.opponent_tags`不为空),就必须有交易对手标签的映射,否则报错。 3. 在分析连续序列时,统计序列中每个标签出现的次数(根据映射的“交易对手标签”列)。 由于改动非常大,我们逐步修改代码。 由于代码量很大,我们只给出关键部分的修改。完整代码修改如下: 注意:以下修改是基于之前提供的完整代码进行的。 修改1:在`setup_ui`方法中,增加一个字段映射 将原来的: required_fields = ['交易卡号', '交易时间', '交易金额', '收付标志', '对方卡号'] 改为: required_fields = ['交易卡号', '交易时间', '交易金额', '收付标志', '对方卡号', '交易对手标签'] 修改2:在`clean_and_transform_data`方法中,增加对交易对手标签的处理(不需要转换,但要去除空值?): 我们不需要转换,但可以过滤掉交易对手标签为空的行?或者保留?由用户决定。暂时不处理。 修改3:在`analyze_data_thread`方法中,当我们得到一个连续序列`seq`后,我们需要检查标签条件是否满足: # 首先,检查标签条件(如果用户设置了) if self.opponent_tags: # 如果设置了标签条件 # 统计序列中每个标签出现的次数 tag_counts = {} # 初始化,将所有标签的计数设为0 for tag in self.opponent_tags.keys(): tag_counts[tag] = 0 # 遍历序列中的每一笔交易 for idx, row in seq.iterrows(): tag_value = row['交易对手标签'] # 如果该交易的交易对手标签在设定的标签中,则计数 if tag_value in tag_counts: tag_counts[tag_value] += 1 # 检查是否满足设定的最小数量 for tag, required_count in self.opponent_tags.items(): if tag_counts.get(tag, 0) < required_count: # 不满足条件,跳过这个序列 break else: # 如果所有标签都满足条件,才继续后面的逻辑(查找出账) # 注意:这里用了for-else结构,如果break没有执行,则表示满足 # 后面的逻辑不变(查找出账) else: # 没有设置标签条件,直接执行后面的逻辑 pass 修改4:时间单位转换 在`analyze_data_thread`方法中,将时间窗口和出账时间差转换为分钟: max_gap_minutes = int(self.max_gap_var.get()) # 这个已经是分钟,不用改 time_window_minutes = int(self.time_window_var.get()) * 60 # 将小时转换为分钟 max_out_gap_minutes = int(self.max_out_gap_var.get()) * 60 # 将小时转换为分钟 在查找连续序列时,我们使用的是`max_gap_minutes`(分钟)和`time_window_minutes`(分钟)。 在查找后续付款交易时,时间窗口改为分钟: time_window_end = seq_end_time + timedelta(minutes=max_out_gap_minutes) 修改5:在添加标签时,我们需要同时输入数量。因此,修改`add_opponent_tag`方法: def add_opponent_tag(self): """添加交易对手标签及所需数量""" # 创建一个新窗口 tag_window = tk.Toplevel(self.root) tag_window.title("添加标签") tag_window.geometry("300x150") ttk.Label(tag_window, text="标签名称:").grid(row=0, column=0, padx=10, pady=10) tag_entry = ttk.Entry(tag_window, width=20) tag_entry.grid(row=0, column=1, padx=10, pady=10) ttk.Label(tag_window, text="所需数量:").grid(row=1, column=0, padx=10, pady=10) count_entry = ttk.Entry(tag_window, width=20) count_entry.grid(row=1, column=1, padx=10, pady=10) def save_tag(): tag = tag_entry.get().strip() count = count_entry.get().strip() if not tag or not count: messagebox.showwarning("警告", "标签名称和数量不能为空") return try: count = int(count) if count <= 0: raise ValueError except ValueError: messagebox.showwarning("警告", "数量必须是正整数") return # 存储标签和数量 self.opponent_tags[tag] = count messagebox.showinfo("成功", f"已添加标签: {tag}, 数量: {count}") tag_window.destroy() ttk.Button(tag_window, text="保存", command=save_tag).grid(row=2, column=0, columnspan=2, pady=10) 修改6:同时,修改`manage_opponent_tags`方法,在列表中同时显示标签和数量: def manage_opponent_tags(self): """管理交易对手标签""" tag_window = tk.Toplevel(self.root) tag_window.title("管理交易对手标签") tag_window.geometry("500x300") # 创建树状表格 columns = ("标签", "数量") tree = ttk.Treeview(tag_window, columns=columns, show="headings") tree.heading("标签", text="标签") tree.heading("数量", text="数量") tree.column("标签", width=200) tree.column("数量", width=100) # 添加数据 for tag, count in self.opponent_tags.items(): tree.insert("", tk.END, values=(tag, count)) tree.grid(row=0, column=0, padx=10, pady=10, sticky="nsew") # 添加删除按钮 def delete_tag(): selected = tree.selection() if not selected: return item = selected[0] tag = tree.item(item, "values")[0] del self.opponent_tags[tag] tree.delete(item) delete_btn = ttk.Button(tag_window, text="删除选中", command=delete_tag) delete_btn.grid(row=1, column=0, pady=10) # 配置网格行列权重 tag_window.rowconfigure(0, weight=1) tag_window.columnconfigure(0, weight=1) 由于修改点很多,我们只给出关键部分。最后,请注意在分析时,如果用户没有映射“交易对手标签”字段,但设置了标签条件,我们应该提示错误。 在分析线程的开始,检查: if self.opponent_tags and not self.mapping_vars['交易对手标签'].get(): messagebox.showerror("错误", "请设置'交易对手标签'的字段映射") return 另外,在`clean_and_transform_data`方法中,我们增加了“交易对手标签”字段,不需要转换,但需要保留: # 清理交易对手标签(转为字符串) if '交易对手标签' in df.columns: df['交易对手标签'] = df['交易对手标签'].astype(str).str.strip() else: # 如果没有映射,则创建一个空列 df['交易对手标签'] = '' 由于时间关系,我们无法在此处提供完整代码,但以上修改点已经覆盖了需求。 注意:由于改动较大,可能需要调试。 最后,由于原始代码较长,我们只提供修改后的关键方法。完整代码请根据以上修改点进行调整。 下面给出修改后的完整代码(由于篇幅限制,只包含修改后的关键方法): </think> 我将修改代码以满足您的要求,将时间单位改为分钟,并实现交易对手标签的多选筛选功能。以下是完整代码: ```python import pandas as pd import numpy as np from datetime import datetime, timedelta import os import tkinter as tk from tkinter import ttk, filedialog, messagebox, scrolledtext import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from matplotlib.figure import Figure import seaborn as sns import re import chardet import threading import time import traceback class TransactionAnalyzerApp: def __init__(self, root): self.root = root self.root.title("交易数据分析工具 - 增强版") self.root.geometry("1400x800") self.root.configure(bg='#f0f0f0') self.root.minsize(1200, 700) self.df = None self.results = None self.analysis_thread = None self.stop_analysis = False self.db_connection = None self.schemas = [] self.total_rows = 0 self.loaded_rows = 0 self.opponent_tags = {} # 存储交易对手标签及其所需数量 self.setup_ui() def setup_ui(self): # 创建主框架和滚动条 main_frame = ttk.Frame(self.root, padding="10") main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) # 创建画布和滚动条 canvas = tk.Canvas(main_frame) scrollbar = ttk.Scrollbar(main_frame, orient="vertical", command=canvas.yview) scrollable_frame = ttk.Frame(canvas) scrollable_frame.bind( "<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")) ) canvas.create_window((0, 0), window=scrollable_frame, anchor="nw") canvas.configure(yscrollcommand=scrollbar.set) canvas.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S)) # 配置网格权重 self.root.columnconfigure(0, weight=1) self.root.rowconfigure(0, weight=1) main_frame.columnconfigure(0, weight=1) main_frame.rowconfigure(0, weight=1) scrollable_frame.columnconfigure(1, weight=1) # 标题 title_label = ttk.Label(scrollable_frame, text="交易数据分析工具 - 增强版(解决连续进账分析问题)", font=("Arial", 16, "bold")) title_label.grid(row=0, column=0, columnspan=4, pady=(0, 20)) # 数据库连接区域 db_frame = ttk.LabelFrame(scrollable_frame, text="数据库连接", padding="10") db_frame.grid(row=1, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=(0, 10)) db_frame.columnconfigure(1, weight=1) # 数据库类型选择 ttk.Label(db_frame, text="数据库类型:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5)) self.db_type_var = tk.StringVar(value="postgresql") db_combo = ttk.Combobox(db_frame, textvariable=self.db_type_var, values=["mysql", "postgresql"], width=10) db_combo.grid(row=0, column=1, padx=(0, 10)) db_combo.bind("<<ComboboxSelected>>", self.on_db_type_change) # 连接参数 ttk.Label(db_frame, text="主机:").grid(row=0, column=2, sticky=tk.W, padx=(0, 5)) self.db_host_var = tk.StringVar(value="localhost") ttk.Entry(db_frame, textvariable=self.db_host_var, width=15).grid(row=0, column=3, padx=(0, 10)) ttk.Label(db_frame, text="端口:").grid(row=0, column=4, sticky=tk.W, padx=(0, 5)) self.db_port_var = tk.StringVar(value="5433") ttk.Entry(db_frame, textvariable=self.db_port_var, width=10).grid(row=0, column=5, padx=(0, 10)) ttk.Label(db_frame, text="数据库:").grid(row=1, column=0, sticky=tk.W, padx=(0, 5)) self.db_name_var = tk.StringVar(value="postgres") ttk.Entry(db_frame, textvariable=self.db_name_var, width=15).grid(row=1, column=1, padx=(0, 10)) ttk.Label(db_frame, text="模式:").grid(row=1, column=2, sticky=tk.W, padx=(0, 5)) self.db_schema_var = tk.StringVar(value="xinpujing") self.schema_combo = ttk.Combobox(db_frame, textvariable=self.db_schema_var, width=15) self.schema_combo.grid(row=1, column=3, padx=(0, 10)) ttk.Button(db_frame, text="刷新模式", command=self.refresh_schemas).grid(row=1, column=4, padx=(5, 5)) ttk.Label(db_frame, text="用户名:").grid(row=2, column=0, sticky=tk.W, padx=(0, 5)) self.db_user_var = tk.StringVar(value="postgres") ttk.Entry(db_frame, textvariable=self.db_user_var, width=15).grid(row=2, column=1, padx=(0, 10)) ttk.Label(db_frame, text="密码:").grid(row=2, column=2, sticky=tk.W, padx=(0, 5)) self.db_password_var = tk.StringVar(value="") ttk.Entry(db_frame, textvariable=self.db_password_var, width=15, show="*").grid(row=2, column=3, padx=(0, 10)) ttk.Label(db_frame, text="表名:").grid(row=2, column=4, sticky=tk.W, padx=(0, 5)) self.db_table_var = tk.StringVar(value="secsflc_JYMX") ttk.Entry(db_frame, textvariable=self.db_table_var, width=15).grid(row=2, column=5, padx=(0, 10)) # 数据加载选项 ttk.Label(db_frame, text="加载行数:").grid(row=2, column=6, sticky=tk.W, padx=(0, 5)) self.load_limit_var = tk.StringVar(value="50000") ttk.Entry(db_frame, textvariable=self.load_limit_var, width=10).grid(row=2, column=7, padx=(0, 10)) ttk.Button(db_frame, text="测试连接", command=self.test_db_connection).grid(row=2, column=8, padx=(10, 5)) ttk.Button(db_frame, text="从数据库加载", command=self.load_from_database).grid(row=2, column=9, padx=(5, 0)) # 文件选择区域 file_frame = ttk.LabelFrame(scrollable_frame, text="文件导入", padding="10") file_frame.grid(row=2, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=(0, 10)) file_frame.columnconfigure(1, weight=1) ttk.Label(file_frame, text="文件路径:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5)) self.file_path_var = tk.StringVar() file_entry = ttk.Entry(file_frame, textvariable=self.file_path_var, width=50) file_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(0, 5)) ttk.Button(file_frame, text="浏览", command=self.browse_file).grid(row=0, column=2, padx=(0, 5)) ttk.Button(file_frame, text="加载数据", command=self.load_data).grid(row=0, column=3) # 字段映射区域 mapping_frame = ttk.LabelFrame(scrollable_frame, text="字段映射设置", padding="10") mapping_frame.grid(row=3, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=(0, 10)) # 创建字段映射的网格 ttk.Label(mapping_frame, text="原始字段", font=("Arial", 10, "bold")).grid(row=0, column=0, padx=5) ttk.Label(mapping_frame, text="→", font=("Arial", 10, "bold")).grid(row=0, column=1, padx=5) ttk.Label(mapping_frame, text="目标字段", font=("Arial", 10, "bold")).grid(row=0, column=2, padx=5) # 必需的字段映射 required_fields = ['交易卡号', '交易时间', '交易金额', '收付标志', '对方卡号', '交易对手标签'] self.mapping_vars = {} for i, field in enumerate(required_fields, 1): ttk.Label(mapping_frame, text=field).grid(row=i, column=2, sticky=tk.W, padx=5) self.mapping_vars[field] = tk.StringVar() mapping_combo = ttk.Combobox(mapping_frame, textvariable=self.mapping_vars[field], width=20) mapping_combo.grid(row=i, column=0, padx=5) ttk.Label(mapping_frame, text="→").grid(row=i, column=1, padx=5) # 参数设置区域 param_frame = ttk.LabelFrame(scrollable_frame, text="分析参数", padding="10") param_frame.grid(row=4, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=(0, 10)) ttk.Label(param_frame, text="最小连续次数:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5)) self.min_continuous_var = tk.StringVar(value="3") ttk.Entry(param_frame, textvariable=self.min_continuous_var, width=10).grid(row=0, column=1, padx=(0, 20)) ttk.Label(param_frame, text="最大交易时间间隔(分钟):").grid(row=0, column=2, sticky=tk.W, padx=(0, 5)) self.max_gap_var = tk.StringVar(value="10") ttk.Entry(param_frame, textvariable=self.max_gap_var, width=10).grid(row=0, column=3, padx=(0, 20)) ttk.Label(param_frame, text="金额匹配阈值(%):").grid(row=0, column=4, sticky=tk.W, padx=(0, 5)) self.amount_threshold_var = tk.StringVar(value="20") ttk.Entry(param_frame, textvariable=self.amount_threshold_var, width=10).grid(row=0, column=5, padx=(0, 20)) ttk.Label(param_frame, text="时间窗口(分钟):").grid(row=0, column=6, sticky=tk.W, padx=(0, 5)) self.time_window_var = tk.StringVar(value="1440") # 24小时*60分钟=1440分钟 ttk.Entry(param_frame, textvariable=self.time_window_var, width=10).grid(row=0, column=7, padx=(0, 20)) # 交易对手标签设置 tag_settings_frame = ttk.LabelFrame(param_frame, text="交易对手标签设置", padding="5") tag_settings_frame.grid(row=1, column=0, columnspan=8, sticky=(tk.W, tk.E), pady=5, padx=5) # 标签名称输入 ttk.Label(tag_settings_frame, text="标签名称:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5)) self.tag_name_var = tk.StringVar(value="") ttk.Entry(tag_settings_frame, textvariable=self.tag_name_var, width=15).grid(row=0, column=1, padx=(0, 5)) # 标签数量输入 ttk.Label(tag_settings_frame, text="所需数量:").grid(row=0, column=2, sticky=tk.W, padx=(0, 5)) self.tag_count_var = tk.StringVar(value="0") ttk.Entry(tag_settings_frame, textvariable=self.tag_count_var, width=5).grid(row=0, column=3, padx=(0, 5)) # 添加标签按钮 ttk.Button(tag_settings_frame, text="添加设置", command=self.add_tag_setting).grid(row=0, column=4, padx=(5, 5)) # 标签设置列表框 self.tag_settings_listbox = tk.Listbox(tag_settings_frame, height=3, width=50) self.tag_settings_listbox.grid(row=1, column=0, columnspan=5, sticky=(tk.W, tk.E), pady=5) # 删除标签按钮 ttk.Button(tag_settings_frame, text="删除选中", command=self.remove_tag_setting).grid(row=1, column=5, padx=(5, 5)) # 分析控制按钮 button_frame = ttk.Frame(param_frame) button_frame.grid(row=2, column=0, columnspan=8, pady=(10, 0)) ttk.Button(button_frame, text="开始分析", command=self.start_analysis).grid(row=0, column=0, padx=(20, 5)) ttk.Button(button_frame, text="停止分析", command=self.stop_analysis_process).grid(row=0, column=1, padx=(5, 5)) ttk.Button(button_frame, text="导出结果", command=self.export_results).grid(row=0, column=2, padx=(5, 5)) ttk.Button(button_frame, text="调试模式", command=self.debug_analysis).grid(row=0, column=3, padx=(20, 5)) # 进度条区域 progress_frame = ttk.Frame(scrollable_frame) progress_frame.grid(row=5, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=(0, 10)) self.progress_var = tk.DoubleVar() self.progress_bar = ttk.Progressbar(progress_frame, variable=self.progress_var, maximum=100) self.progress_bar.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 10)) progress_frame.columnconfigure(0, weight=1) self.progress_label = ttk.Label(progress_frame, text="就绪") self.progress_label.grid(row=0, column=1) # 数据显示区域 data_frame = ttk.LabelFrame(scrollable_frame, text="数据预览", padding="10") data_frame.grid(row=6, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10), padx=(0, 10)) data_frame.columnconfigure(0, weight=1) data_frame.rowconfigure(0, weight=1) self.data_text = scrolledtext.ScrolledText(data_frame, height=12, width=70) self.data_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) # 结果显示区域 result_frame = ttk.LabelFrame(scrollable_frame, text="分析结果", padding="10") result_frame.grid(row=6, column=2, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10)) result_frame.columnconfigure(0, weight=1) result_frame.rowconfigure(0, weight=1) self.result_text = scrolledtext.ScrolledText(result_frame, height=12, width=60) self.result_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) # 配置权重 scrollable_frame.rowconfigure(6, weight=1) data_frame.rowconfigure(0, weight=1) data_frame.columnconfigure(0, weight=1) result_frame.rowconfigure(0, weight=1) result_frame.columnconfigure(0, weight=1) # 绑定鼠标滚轮事件 canvas.bind("<Enter>", lambda e: canvas.bind_all("<MouseWheel>", lambda event: canvas.yview_scroll( int(-1 * (event.delta / 120)), "units"))) canvas.bind("<Leave>", lambda e: canvas.unbind_all("<MouseWheel>")) # 初始化模式列表 self.refresh_schemas() def add_tag_setting(self): """添加交易对手标签设置""" tag_name = self.tag_name_var.get().strip() tag_count = self.tag_count_var.get().strip() if not tag_name or not tag_count: messagebox.showwarning("警告", "请输入标签名称和所需数量") return try: count = int(tag_count) if count <= 0: raise ValueError except ValueError: messagebox.showwarning("警告", "数量必须是正整数") return # 添加到标签设置字典 self.opponent_tags[tag_name] = count self.update_tag_settings_list() # 清空输入框 self.tag_name_var.set("") self.tag_count_var.set("0") def update_tag_settings_list(self): """更新标签设置列表框""" self.tag_settings_listbox.delete(
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lujunql

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值