最近工作需要,编写了一个程序,实现了使用python读取交割单信息,自动计算每日账户资产。可以为账户收益率计算节省大量时间。
交割单为csv文件。可以将交易APP中的历史交割单数据导出后进行格式修改使用。
程序计算量比较小,计算结果直接显示即可。
实现原理:
- 将交割单数据从后向前一条一条读取。
- 交割日期大于指定日期的不显示,直接跳过。
- 交割日期小于等于指定日期的记录,第一条记录中的资金余额为账户资金余额。
- 每次新碰到一个证券代码,对应的证券余额是持仓证券的数量,如果不是新的证券代码,说明前面已经显示过了,这样重复证券代码的记录直接忽略。
- 证券数量大于0的记录,显示证券代码,证券名称和证券数量。证券数量为零的证券说明已经清仓了,不需要显示。
- 根据证券代码和指定日期,查询到对应的收盘价格。根据持仓数量和收盘价格可以算出证券市值。
- 持仓市值加上资金余额即账户收盘资产。
注意几点:
- 交割单中的证券余额和资金余额要准确。如果有误,需要手工调制。尤其是有红股和分红数据时。
- 根据证券代码和指定日期或者收盘价格有限制,只能自动获取A股上市股票的交易日收盘价格。如果指定日期是非交易日,baostock无法获得非交易日的收盘价格。这时候需要更换为交易日的日期。如果是基金,可以将基金对应日期的净值单独存在文件中,这样程序读取文件中的收盘净值即可。
不多说了,直接上代码。
'''
Created on 2021年8月26日
@author: yangs
此程序用于计算证券账户指定日期的资产总值。
第一步:读取对账单到dataframe中,
第二步:取指定日期之后的记录,重复的证券代码的,只显示最后一条记录。数量为0的也不显示。
第三步:根据证券代码和指定日期,获得收盘价格。股票的收盘价格从baostock中获取,并从手动表中补充其他的价格
第四步:计算出收盘后的账户资产值
数据源文件位置需要根据实际情况调整,缺省是当前目录
计算结果只用于显示。本程序较小,运行速度很快,直接显示内容。
'''
import pandas as pd
from datetime import datetime
import baostock as bs
class MyTools(object):
"""
工具类
"""
def __init__(self):
pass
def read_tradeFile(self, full_file_name):
'''
读取对账单文件。
返回格式: dataframe
对账单为csv文件,格式如下。
其中要用到的数据包括交收日期,证券代码,证券余额,资金余额。其他列数据暂时不用
序号,交收日期,证券代码,证券名称,交易类别,成交价格,成交数量,证券余额,成交金额,资金发生数,资金余额,,流水序号,业务标志,业务名称,发生金额,后资金额,货币类别,备注,,,,,,,,,,,,,费用合计,净佣金,规费,印花税,过户费,币种,合同号,资金账号,股东代码
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 )
return tmpdata
def read_text_file(self, full_file_name ):
"""
读取文本文件,返回文件内容。
返回格式:字符串
"""
with open(full_file_name, encoding = "utf-8", mode = "r") as f:
text_file_content = f.read()
return text_file_content
def get_curr_price_manual(self, stock_code, check_date):
"""
从csv文件中读取证券代码指定日期的收盘价格
csv文件名称是证券代码加上"收盘价格.csv",每种证券一个文件。
返回格式:字符串
csv文件格式如下:日期必须是 2016-06-14 的格式。
code,date,price,name
159902,2016-06-14,3.0970 ,中小板
159902,2016-05-26,3.0490 ,中小板
"""
file_name = ""+stock_code+"收盘价格.csv"
manual_data = self.read_text_file(file_name)
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 ]
return close_price
def get_spec_date_closeprice(self, stock_code, curr_date):
"""
获取沪深A股 stock_code 指定日期 curr_date 的收盘价格
返回值:收盘价格,如果找不到收盘价格,就返回数值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, create_date):
'''
初始化账户
'''
self.account_name = account_name # 账户名称
self.notes = "" # 账户备注信息
self.creat_date = create_date # 账户设立日期
self.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_info(self,check_date):
"""
显示账户指定日期的净值。指定日期必须是交易日期。如果不是交易日期,程序会出现异常报错或者数据计算错误。
"""
curr_check_date = datetime.strptime(check_date, '%Y-%m-%d') #将字串转为日期格式
print("\n当前账户名称:\t",self.account_name, end="\t")
print("检查日期点是:\t", check_date)
tmpdata = self.source_data
my_tools = MyTools()
# 取指定日期之前的值,包括指定日期
# 注意,以下的显示是从后向前检查的
i=tmpdata.shape[0]
inv_item = []
total_asset = 0
firstdisplay = 1
while(i>0): #从后先前显示
i=i-1
tmp1 = datetime.strptime(tmpdata.iloc[i,1], '%Y-%m-%d') #将字串转为日期格式
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才显示
stock_code = int(tmpdata.iloc[i,2])
stock_code = "{:0>6d}".format(stock_code) # 整数转为6位前补0的字串
stock_name = tmpdata.iloc[i,3]
stock_hold_number = tmpdata.iloc[i,7]
print(i,"\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):
curr_price = my_tools.get_curr_price_manual(stock_code,check_date)
print(curr_price, end="\t")
print('%.2f' % ( float(stock_hold_number)*float(curr_price)) ,end="\t")
total_asset = total_asset + float(stock_hold_number)*float(curr_price)
# print('%.2f' % total_asset,end="\t" )
print(" ")
print("账户名称:\t",self.account_name,"\t", check_date,end="\t")
print('%.2f' % total_asset )
print("========================== 账户信息显示完毕 ========================= \n\n")
if __name__ == '__main__':
"""
主程序...
"""
lg = bs.login()
account_name = "国泰君安"
trade_log_file = "2006-2015国泰君安账户对账单V2.csv"
create_date = "2006-08-23"
gtja_account = InvestAccount(account_name,trade_log_file,create_date)
account_name = "广发高新"
trade_log_file = "2014-2016广发证券账户对账单.csv"
create_date = "2014-05-28"
gfgx_account = InvestAccount(account_name,trade_log_file,create_date)
#程序将计算下列日期的收盘市值
dates= """
2015-05-11
2017-12-04
2017-12-05
2017-12-06
2017-12-07
2017-12-08
"""
datelist = dates.splitlines(False)
i=1
for item in datelist:
if(len(item) > 1):
gtja_account.show_account_info( item )
gfgx_account.show_account_info( item )
i=i+1
bs.logout()
print("L33 恭喜恭喜,程序运行正常结束。")