Python 读取交割单计算每日账户资产v2

最近工作需要,编写了一个程序,实现了使用python读取交割单信息,自动计算每日账户资产。可以为账户收益率计算节省大量时间。

本次变动:1 将对账单文件从csv改为xls,2 增加了命令行方式。其他大致和上次一样。

程序计算量比较小,计算结果直接显示即可。

实现原理:

将交割单数据从后向前一条一条读取。

交割日期大于指定日期的不显示,直接跳过。

交割日期小于等于指定日期的记录,第一条记录中的资金余额为账户资金余额。

每次新碰到一个证券代码,对应的证券余额是持仓证券的数量,如果不是新的证券代码,说明前面已经显示过了,这样重复证券代码的记录直接忽略。

证券数量大于0的记录,显示证券代码,证券名称和证券数量。证券数量为零的证券说明已经清仓了,不需要显示。

根据证券代码和指定日期,查询到对应的收盘价格。根据持仓数量和收盘价格可以算出证券市值。

持仓市值加上资金余额即账户收盘资产。

关于对账单文件:

对账单文件是Excel 97-2003的格式。

交割单中的证券余额和资金余额要准确。如果有误,需要手工调制。尤其是有红股和分红数据对应的证券余额和资金余额。

某些券商的对账单中有对比对应的资金冻结和解冻。直接删掉这些记录,就按照资金未冻结处理。

如果对账单文件中包括基金交易的数据,也可以使用此程序。

查询日期:

查询日期如果不是交易日,则显示上一个交易日的信息。

证券价格:

A股当前交易的股票查对应询日期的价格可以通过baostock获得。其他日期只能手工按照固定格式列出;港股和基金的价格也只能手工给出。程序会读取对应文件,从而获得指定日期的价格。

以下为代码。

 ''' Created on 2021年8月26日 @author: yangs 此程序用于计算证券账户指定日期的资产总值。 第一步:读取对账单到dataframe中。 注意对账单的格式要正确,并且需要自己验证个股票代码后面对应的证券余额的正确性。 查询日期按照年月日写作 XXXX-XX-XX 新股中签后的代码和数量需要自己确保正确。从券商系统导出的数据一般有下列问题,需要手动处理正确后本程序才能正确显示资产值。 1 股息分红的记录中,股票代码后面的证券数量可能不正确,需要手工修正 2 红股入账时的证券数量可能不正确,需要手工修正。 3 冻结和解冻资金会导致剩余金额有变动,所以这个资金变动可以忽略。需要删掉冻结和解冻。然后修正资金余额 4 新股新债申购中签后,会有中签记录,冻结资金,新股债入账,到最后的卖出。对应的股票代码可能变了好几次。需要手工调制为一个代码,并且修正扣款和持股的逻辑关系。 如果记录扣款了,就要记录对应的持股。 5 新股新债的持股可能早于交易日期。因此需要在股票价格对应的文件中补全交易日之前的价格。 6 冻结的资金,如果一直没有解冻。就直接按照已解冻修正资金余额。 7 目前支持的港股就三个 福寿园,腾讯控股,京东。超出这三个的需要自己修改程序。 第二步:取指定日期之前的记录,重复的证券代码的,只显示最后一条记录。数量为0的也不显示。 第三步:根据证券代码和指定日期,获得收盘价格。股票的收盘价格从baostock中获取,并从手动表中补充其他的价格 注意,港股需要特殊处理 第四步:计算出收盘后的账户资产值 注意:本版本程序对应的 数据源文件是 Excel 97-2003版本 ''' import pandas as pd from pandas import read_excel from datetime import datetime import baostock as bs from sqlparse.sql import Begin import sys class MyTools(object): """ 工具类 """ def __init__(self): pass def para_input(self): if(len(sys.argv ) == 1): check_date = "2020-12-31" input_files = ["2006-2015gtja_check_sheetV2.xls","2014-2022gfgx_check_sheetV2.xls","2020-2022ggpa_check_sheet.xls"] elif( len(sys.argv ) == 2): check_date = sys.argv[1] input_files = ["2006-2015gtja_check_sheetV2.xls","2014-2022gfgx_check_sheetV2.xls","2020-2022ggpa_check_sheet.xls"] elif(len(sys.argv) ==3 ): check_date = sys.argv[1] input_file = sys.argv[2] input_files=[] input_files.append(input_file) return check_date, input_files def myshow(self, data1,data2): """ 显示账户信息 """ pd.set_option('display.max_rows',None) print("\n------------------------------------------------------------") i = 0 maxi = data1.shape[0] j=0 maxj = data1.shape[1] while(i< maxi): j=0 while(j<maxj): # print("L82 i,3:\t",data1.iloc[i,3],"\t i,4:\t",data1.iloc[i,4] ) if(float(data1.iloc[i,3]) != 0.0): print(data1.iloc[i,j],end="\t") j=j+1 if(float(data1.iloc[i,3]) > 0.0): print(" ") i=i+1 # print("\nL92 data2 \n",data2) print("\nL94 data2") print(data2.iloc[0,0],end="\t") print(data2.iloc[0,1],end="\t") print(data2.iloc[0,2],end="\t") print(data2.iloc[0,3],end="\t") print(data2.iloc[0,4],end="\t") print(data2.iloc[0,5],end="\t") print(data2.iloc[0,6],end="\n") # print("\nL47 data1 \n",data1[data1.number >0.0]) print("------------------------------------------------------------\n") def check_trade_date(self,query_date): """ 核实query_date是否为交易日。如果为交易日,返回query_date。如果不是交易,返回该日期的前一个交易日 query_date 格式要求:必须是 2015-12-01 的格式。共十位,用-分开。前后不能有空格 """ file_name = "D:\\temp\\收益率计算\\股票价格\\"+"000300"+"收盘价格.csv" file_content = self.read_text_file(file_name) tmp_lines = file_content.splitlines(False) tmp_date = "2000-01-01" for item in tmp_lines: # print("L48: ",item,"\t",item[7:17]) if( query_date == item[7:17]): return query_date elif(query_date > item[7:17]): tmp_date = item[7:17] else: break if(tmp_date < query_date): return tmp_date else: return query_date def read_tradeFile(self, full_file_name): ''' 读取对账单文件。 返回格式: dataframe 对账单为xls文件,格式如下。 其中要用到的数据包括交收日期,证券代码,证券余额,资金余额。其他列数据暂时不用 序号,交收日期,证券代码,证券名称,交易类别,成交价格,成交数量,证券余额,成交金额,资金发生数,资金余额,,流水序号,业务标志,业务名称,发生金额,后资金额,货币类别,备注,,,,,,,,,,,,,费用合计,净佣金,规费,印花税,过户费,币种,合同号,资金账号,股东代码 1,2014-06-09,,,银行转存,,,,5475,5475.00 ,5475.00 ,,9,2041,银行转存,5475,5475,人民币,银行返回码[0000]返回信息[[0000]交易成功]|转账成功,,,,,,,,,,,,,,,,,,,,, 2,2014-06-16,,,银行转取,,,,-5475,-5475.00 ,0.00 ,,7,2042,银行转取,-5475,0,人民币,银行返回码[0000]返回信息[[0000]交易成功]|转账成功,,,,,,,,,,,,,,,,,,,, ''' # tmpdata = pd.read_csv( full_file_name ) #读取csv文件 tmpdata = read_excel(full_file_name,sheet_name=0) #读取xls文件 return tmpdata def read_text_file(self, full_file_name ): """ 读取文本文件,返回文件内容。 返回格式:字符串 """ try: with open(full_file_name, encoding = "utf-8", mode = "r") as f: text_file_content = f.read() return text_file_content except: print("L168 打开文件出错!") return "" def get_hkd_exchange_rate(self, check_date): """ 根据检查日期给出港股通汇率数值 """ full_file_name = "D:\\temp\\收益率计算\\股票价格\\深交所结算汇兑比率.csv" tmpdata = pd.read_csv( full_file_name ) i=0 maxi = len(tmpdata) while(i<maxi): if(check_date > tmpdata.iloc[i,0]): target_value = tmpdata.iloc[i,2] target_date = tmpdata.iloc[i,0] else: break i=i+1 # print("L188 ", "查询日期 ", check_date, " 实际日期 ", target_date," 查询值 ", target_value) return target_value def get_curr_price_manual(self, stock_code, check_date): """ 从csv文件中读取证券代码指定日期的收盘价格 返回格式:字符串 csv文件格式如下:日期必须是 2016-06-14 的格式。 code,date,price,name 159902,2016-06-14,3.0970 ,中小板 159902,2016-05-26,3.0490 ,中小板 """ try: file_name = "D:\\temp\\收益率计算\\股票价格\\"+stock_code+"收盘价格.csv" with open(file_name, encoding = "utf-8", mode = "r") as f: manual_data = f.read() find_str = stock_code+","+check_date position = manual_data.find(find_str) start_position = position+len(find_str)+1 end_position = manual_data.find(",",start_position )-1 close_price = manual_data[start_position : end_position ] close_price = close_price.lstrip().rstrip() close_price = float(close_price) return close_price except: return 0 def get_spec_date_closeprice(self, stock_code, curr_date): """ 从baostock上获取沪深A股 stock_code 指定日期 curr_date 的收盘价格 返回值:收盘价格,如果找不到收盘价格,就返回数值0 """ if(stock_code in ['00700','01448','09618']): return 0 stock_code1 = int( stock_code ) #证券代码转为整数 stock_code2 = "{:0>6d}".format(stock_code1) # 整数转为6位前补0的字串 first_char = stock_code2[0:1] if(first_char == "6"): full_stock_code = "sh."+stock_code2 else: full_stock_code = "sz."+stock_code2 rs = bs.query_history_k_data_plus(full_stock_code, "date,code,close,preclose,volume,amount,adjustflag,turn,tradestatus,pctChg,isST", start_date=curr_date, end_date=curr_date, frequency="d", adjustflag="3") #### 打印结果集 #### data_list = [] while (rs.error_code == '0') & rs.next(): # 获取一条记录,将记录合并在一起 data_list.append(rs.get_row_data()) result = pd.DataFrame(data_list, columns=rs.fields) #### 结果集输出到csv文件 #### tmpdata = 0 if( len(result)>0 ): tmpdata = result.iloc[0,2] return float(tmpdata) class InvestAccount(object): ''' 投资账户类 ''' def __init__(self, account_name, trade_log_file): ''' 初始化账户 ''' self.account_name = account_name # 账户名称 self.notes = "" # 账户备注信息 # self.creat_date = create_date # 账户设立日期 # self.close_date = close_date # 账户关闭日期 self.source_data = pd.DataFrame() #账户交易数据 self.total_asset = 0 #账户资产 self.cash = 0 #账户现金 self.net_asset_value = 0 #净值 self.return_value = 0 #账户收益值 self.invest_item = [] my_tools = MyTools() self.source_data = my_tools.read_tradeFile( trade_log_file ) def show_tradelog(self): """ 显示读取的历史交易信息 """ print("\n显示历史交易记录\n") print(self.source_data) print(" =================== 历史交易记录显示完毕 =================== ") def show_account_reverse(self,check_date): """ 显示账户指定日期的净值。指定日期必须是交易日期。如果不是交易日期,程序会出现异常报错或者数据计算错误。 """ tmpdata = self.source_data first_date = tmpdata.iloc[0,1] last_date = tmpdata.iloc[-1,1] curr_check_date = datetime.strptime(check_date, '%Y-%m-%d') #将字串转为日期格式 # print("L359 ",creat_date,close_date) if(curr_check_date < first_date): print("L198: 账户",self.account_name,"当前未开户,或者查询日期小于第一个日期") return 1 if(curr_check_date > last_date): print("L200: 账户",self.account_name,"当前已销户,或者查询日期大于最后一个日期") return 1 print("\n当前账户名称:\t",self.account_name, end="\t") print("检查日期点是:\t", check_date) my_tools = MyTools() # 注意,以下的显示是从后向前检查的 i=tmpdata.shape[0] #数组总长度 inv_item = [] #【显示列表】或者投资清单列表 total_asset = 0 #账户总资产 firstdisplay = 1 #首次出现的标志 HK_stock_num = 0 hkd_exchange_rate = 1 while(i>0): #从后先前显示 i=i-1 # tmp1 = datetime.strptime(tmpdata.iloc[i,1], '%Y-%m-%d') #将字串转为日期格式 tmp1 = tmpdata.iloc[i,1] if(tmp1 > curr_check_date ): #日期之后的部分不显示,直接跳过 continue else: stock_code = tmpdata.iloc[i,2] if(stock_code not in inv_item): #不在【已显示列表】中的显示,在的就不显示 inv_item.append(stock_code) #将要显示的代码添加到【已显示列表】中,后续就不再显示了 #以下为第一次显示 if(firstdisplay == 1): #是第一个显示,取账户资金余额的数值 firstdisplay = 0 print("账户资金余额:\t",tmpdata.iloc[i,10]) total_asset = total_asset + float(tmpdata.iloc[i,10] ) print("当前持仓信息:") print("序号\t 证券代码 \t 证券名称 \t 持仓数量 \t 收盘价 \t 参考市值") #以下为非第一次显示 if(tmpdata.iloc[i,7] > 0.0): # 证券代码数量不为0才显示。数量为0的直接忽略 stock_code = int(tmpdata.iloc[i,2]) #缺省是浮点数 stock_code = "{:0>6d}".format(stock_code) # 整数转为6位前补0的字串 if(stock_code in ['000700','001448','009618']): #如果是港股,调整为5位数字 stock_code = stock_code[1:len(stock_code)] stock_name = tmpdata.iloc[i,3] stock_hold_number = tmpdata.iloc[i,7] print(i+1,"\t",stock_code,"\t",stock_name,"\t",stock_hold_number, end="\t") # 获取价格 curr_price = my_tools.get_spec_date_closeprice(stock_code,check_date) if(curr_price ==0):#如果网络上获取的价格为0,则从手工表中获取股票或者基金的价格 curr_price = my_tools.get_curr_price_manual(stock_code,check_date) #显示价格 print(curr_price,end="\t") # print("\n") # print("L369 check date is: ", check_date) # print("L369 获得的港币卖出汇率为:", my_tools.get_hkd_exchange_rate(check_date)) if(stock_code in ['00700','01448','09618']): hkd_exchange_rate = my_tools.get_hkd_exchange_rate(check_date) item_asset = float(stock_hold_number) * float(curr_price) * hkd_exchange_rate HK_stock_num = 1 else: item_asset = float(stock_hold_number) * float(curr_price) print('%.2f' % ( item_asset ) ,end="\t") total_asset = total_asset + item_asset print('%.2f' % total_asset,end="\t" ) print(" ") print("\nL458 账户名称:\t",self.account_name,"\t 日期:\t", check_date, end=" \t 账户资产合计:\t") if(HK_stock_num ==1 ): print('%.2f' % total_asset , "\t港股通汇率\t", hkd_exchange_rate) else: print('%.2f' % total_asset) print("L463 ========================= 账户信息显示完毕 ======================== \n\n") return total_asset #主程序开始处 if __name__ == '__main__': """ 主程序... """ lg = bs.login() my_tools = MyTools() check_date, input_files = my_tools.para_input() for item in input_files: account_name = item trade_log_file = item # create_date = "" # close_date = my_tools.get_last_date(input_files) curr_account = InvestAccount(account_name,trade_log_file) valid_date = my_tools.check_trade_date(check_date) #如果查询日期不是交易日,转换为前一个交易日。 # print("L391 ",item,valid_date ) if(valid_date != check_date): print("L731: 查询开始 日期",check_date,"不是交易日。因此后续显示前一个交易日",valid_date,"的数据 --------") else: print("L713: 查询开始 --------------------------------------------------------") # print("L733 显示",valid_date,"的账户信息") curr_account.show_account_reverse( valid_date ) print("L738: 查询完毕 ==================================================================================\n") bs.logout() print("L741 恭喜恭喜,程序运行正常结束。") 

免责声明:文章内容不代表本站立场,本站不对其内容的真实性、完整性、准确性给予任何担保、暗示和承诺,仅供读者参考,文章版权归原作者所有。如本文内容影响到您的合法权益(内容、图片等),请及时联系本站,我们会及时删除处理。

为您推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注