基金资管系统相关计算汇总
基金资管系统相关计算汇总
前言
为了统计基金产品的净值变化情况,最近简单开发了一套资管系统,主要涉及股票和期货的交易流水的处理,虽然业务逻辑不复杂,但是对于没有金融基础的同学,在写计算相关的代码时难免磕磕碰碰,所以在这里梳理下整体思路,供大家参考。
基础概念
-
基金份额/基金净值/基金市值
基金份额很好理解,指某个基金产品平均分为几份;基金净值指每1基金份额的价格;基金市值就是 基金净值*基金份额;通常来说,基金产品开始运行时,它的基金净值为1,基金份额为初始金额(基金A发行了1000w,则净值为1,份额为1000w)
当基金产品运行过程中,基金自身产生的收益会影响基金净值,而资金操作则会影响基金份额。下一章节将举例说明其具体的计算过程。
-
做空/做多
做空和做多可以简单理解为: 以某个时间点的价格作为成本价,某个金融产品价格的上升为做多方收益(做空放的亏损),价格的下降为做多放的亏损(做空的收益)。
-
开仓/平仓
开仓指买入金融产品(空单/多单),平仓指卖出金融产品。
实例
时间 | 投资者 | 净值 | 申购金额 | 赎回金额 | 赎回份额 | 基金份额 |
---|---|---|---|---|---|---|
2018/12/24 | A | 1 | 20000 | 20000 | ||
2018/12/24 | B | 1 | 20000 | 20000 | ||
2018/12/24 | C | 1 | 20000 | 20000 | ||
2018/12/25 | D | 1 | 150000 | 150000 | ||
2018/12/25 | E | 1 | 150000 | 150000 | ||
2018/12/25 | F | 1 | 150000 | 150000 | ||
2018/12/25 | B | 1 | 30000 | 30000 | ||
2019/4/3 | A | 1.4382 | 30000 | 20859 | ||
2019/5/10 | D | 1.2637 | 100000 | 79135 | ||
2019/7/2 | D | 1.2472 | 150000 | 120270 | 120270 |
表1 资金流水表
投资者名称 | 实缴金额 | 基金份额 |
---|---|---|
A | 50000 | 40859 |
B | 50000 | 50000 |
C | 20000 | 20000 |
D | 100000 | 108865 |
E | 150000 | 150000 |
F | 150000 | 150000 |
总计 | 519724 |
表2 投资者份额表
时间 | 账户金额 | 净值 |
---|---|---|
2018/12/25 | 540000 | 1 |
2019/4/3 | 776638 | 1.43821 |
2019/5/9 | 708733 | 1.26365 |
2019/7/2 | 798209 | 1.24721 |
表3 基金净值表
申购时间 | 申购份额 |
---|---|
2018/12/25 | 540000 |
2019/4/3 | 560859 |
2019/5/10 | 639994 |
2019/7/2 | 519724 |
表4 基金份额表
上表为记录基金产品净值变化过程的表格。
2018/12/24至2018/12/25,A,B,C,D等人募集了资金54w,此时每个投资者的份额为其投入的初始资金,产品净值为1,总申购份额540000;
2019/4/3,查询表3,基金总资金776638,776638除以初始份额540000,得到当时净值为1.43821,此时A追加投资3w,30000除以1.43821得到可申购份额20859,加上B原来持有的份额20000,总共持有份额40859,基金总份额变为表4所示560859;
2019/5/10,基金总资金708733,708733除以当时份额560859,得到当时净值1.26365,此时D追加投资10w,100000除以1.26365得到可申购份额79135,同理,2019/7/2,D赎回资金15w,最终计算得到D持有份额为108865,有些同学可能会注意到,诶,D怎么总共花10w却得到了108865的份额呢,岂不是比最开始用15w获得15w的份额更加划算?其实,可以这么想,此时若D想重新获得15w的份额,则需要投入(150000-108865)*1.2472=51303的资金,最终大于15w成本,也就是说降低份额成本其实是靠牺牲了初始份额总数才获得的。
,
分析
资源系统相关计算主要分为以下几块:
- 流水计算,通过交易所导出的交易流水,逐笔计算持仓市值、现金以及持仓量。
- 净值计算,完成每日流水计算的前提下,统计总市值变化得出每日净值
综上,数据库表结构设计如下:
流水表:现金流水用于记录现金存取,会影响产品份额;股票期货流水记录交易详细,影响产品净值
持仓表:产品每日的持仓状态
市值表:产品每日的做多市值,做空市值,现金,份额(initialFunds),净值变化百分比
计算流程
期货股票流水:计算更新每日做多市值,做空市值,现金
资金流水:计算更新现金,份额
当日总市值(m) = 做多市值 + 现金 + 做空成本 + (做空成本-期货做空市值)
注: 为了便于计算,针对期货的做空成本,没有去考虑杠杠,即做空100w股指期货,虽然可能实际只需要15w,但是系统仍按照100w成本计算,即现金减少100w。
当日净值变化 = (m0-m~1)/前一日份额 = (当日做多市值-昨日做多市值 + 当日现金-昨日现金 + 当日期货做空平衡项 + (昨日期货做空市值-当日期货做空市值)+ 当日期货做空平衡项)/前一日份额
注: 当日期货做空平衡项 可能描述的有点怪异,其实就是2天的做空成本差,也就是当日期货做空相关的开平仓流水,因为系统是基于流水去计算所有数值,这样可以免去计算做空成本这个中间环节。此处除以前一日份额是考虑当日存在现金流水的情况下,当日份额会发生变化,所以将现金调整操作规定在收盘之后,用前一日的份额来计算净值变动,同时用变动后的净值计算资金流水对份额的影响。
整体逻辑: 每日计算时,从持仓表和市值表获取前一日的持仓状态和市值状态,根据流水逐步计算。先处理交易流水,获得最新的持仓状态以及市值状态,计算净值,然后处理现金流水,根据实例所述过程更新份额。
补充
关于实时信息显示,可以首先让系统在每次交易流水录入的时候,触发净值计算,将最近的Balance和Position状态更新在数据库中,每次获取实时信息时,直接读取数据库表,若当日有流水上传则可以获取到当日的状态,若没有流水上传则可以获取昨日的状态。
-
当日实时净值变化:查询Balance表当日的净值变化值(x),无数据(即无流水上传)则取0,同时获取表中最近1天的Position和Balance,结合最新价,计算Position的最新做多市值和做空市值,与Balance中记录的做多市值和做空市值做差,此差值除以份额就是离上次净值计算后由于价格影响导致的净值变化,再加上x,就是当日净值变化,此处不用在考虑可用现金对净值的影响,因为若流水已经上传,由于系统会触发计算更新净值,已经包含了现金的影响,而流水没上传的情况下,可用现金不会变化。
-
实时净值:昨日净值+当日实时净值变化
-
实时仓位以及仓位市值:直接读取Position表获取仓位,结合最新价计算市值。
-
当日实时盈亏:获取昨日持仓状态,分别根据当日交易流水,计算每个交易品种的持仓盈利、平仓盈利、开仓盈利的总和。
注:对于做多盈亏
持仓盈利 = 持有仓位 * (当前最新价 - 昨日收盘价)
平仓盈利 = 当日所平仓位 * (平仓价 - 昨日收盘价)
开仓盈利 = 当日所开仓位 * (当前最新价 - 开仓价)
-
当日持仓盈亏
实时信息相关代码
def count_real_time_profit(fund_id):
history_balance = Balance.query.filter(
and_(Balance.FundId == fund_id, Balance.Date < datetime.datetime.strptime(datetime.datetime.today().strftime(common.TRADEFLOW_FORMAT),common.TRADEFLOW_FORMAT))).all()
today_db_balance = Balance.query.filter(Balance.FundId == fund_id).order_by(Balance.Date.desc()).first()
last_position_day = Position.query.filter(Position.FundId == fund_id).order_by(Position.Date.desc()).first().Date
today_db_position = Position.query.filter(
and_(Position.FundId == fund_id, Position.Date == last_position_day)).all()
temp_long_market_value, temp_short_market_value = profit_calculator.get_market_value(today_db_position,ask_last=True)
profit_dict = profit_calculator.get_temp_position_profit(fund_id)
long_market_value_change = 0
short_market_value_change = 0
for profit_key in profit_dict:
profit_info = profit_dict[profit_key]
if profit_info.direction == 1:
long_market_value_change = long_market_value_change + profit_info.sum_profit()
else:
short_market_value_change = short_market_value_change + profit_info.sum_profit()
# print(fund_id, long_market_value_change, short_market_value_change)
# 上次流水计算完成后(cash变动完成)与当前价格的市值变化
long_market_value_diff = temp_long_market_value - float(today_db_balance.MarketValue)
short_market_value_diff = float(today_db_balance.ShortMarketValue) - temp_short_market_value
today_profit = float(today_db_balance.Profit) if today_db_balance.Date.strftime(
common.LT["day"]) == datetime.datetime.today().strftime(common.LT["day"]) else 0
temp_profit = today_profit + (short_market_value_diff + long_market_value_diff) / float(
today_db_balance.InitialFunds) * 100
net_profit = 0
for balance in history_balance:
net_profit = net_profit + float(balance.Profit)
net_profit = (net_profit + temp_profit + 100) / 100
stock_0 = {"Code": "SK总计", "DaySumProfit": 0, "MarketValue": 0, "PositionProfit": 0}
future_0 = {"Code": "FU总计", "DaySumProfit": 0, "MarketValue": 0, "PositionProfit": 0}
stock_list = []
future_list = []
for profit_key in profit_dict:
p = profit_dict[profit_key].to_dict()
if "SK" in p["Code"]:
stock_list.append(p)
stock_0["DaySumProfit"] += p["DaySumProfit"]
stock_0["MarketValue"] += p["MarketValue"]
stock_0["PositionProfit"] += p["PositionProfit"]
else:
future_list.append(p)
future_0["DaySumProfit"] += p["DaySumProfit"]
future_0["MarketValue"] += p["MarketValue"]
future_0["PositionProfit"] += p["PositionProfit"]
stock_0["DaySumProfit"] = round(stock_0["DaySumProfit"], 2)
stock_0["MarketValue"] = round(stock_0["MarketValue"], 2)
stock_0["PositionProfit"] = round(stock_0["PositionProfit"], 2)
future_0["PositionProfit"] = round(future_0["PositionProfit"], 2)
future_0["PositionProfit"] = round(future_0["PositionProfit"], 2)
future_0["PositionProfit"] = round(future_0["PositionProfit"], 2)
stock_list = [stock_0] + stock_list
future_list = [future_0] + future_list
print(today_db_balance.InitialFunds)
result = {
"Time": last_position_day.strftime("%Y-%m-%d"),
"NetProfit": round(net_profit, 4),
"NetProfitChange": round(temp_profit, 3),
"StockMarketValue": round(temp_long_market_value, 2),
"FutureMarketValue": round(temp_short_market_value, 2),
"StockMarketValueChange": round(long_market_value_change, 2),
"FutureMarketValueChange": round(short_market_value_change, 2),
"ProfitInfo": [stock_list, future_list],
"allMoney": round(float(today_db_balance.InitialFunds) * net_profit, 4)
}
return result