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 恭喜恭喜,程序运行正常结束。")
    
        
        
                

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值