系列文章:
- 策略1:使用python+mongodb实现股票网格交易代码----附python源码
- 使用matplotlib画k线(2条k线同列)----附python源码
- 策略3:动态再平衡----附python源代码
思路:
- 选取2只股票或基金
- 在某一日,按照次日价格买入,买入的比例是1:1;各买入的金额是预计投入的总金额的一半
- 在每个周五,根据2只股票的市值(份额 * 周五收盘价),进行一次再平衡(市值高的卖出,市值低的买入,卖出和买入金额相同),
以此保证2只股票的市值比例仍旧是1:1 - 测算从x到y日期,一直进行再平衡,所获取的最终的收益情况
import math
import pandas as pd
from utils.mongo_util.MongoUtil import MongoUtil
class ReBalance:
def __init__(self) -> None:
super().__init__()
# 测算的开始时间
self.start_date = "2018-01-02"
self.meta_info = {
# 初始本金
"total_money": 100000,
# 构造mongodb数据的查询条件
"start_condition": {"date": {"$gte": self.start_date}},
# 要进行再平衡测算的2只股票代码
"security": ["SH510300", "SH601939"],
# 存放上述2只股票代码的dataframe形式的历史收盘价格信息
"history": []
}
# 每次进行再平衡后的市值金额和份额信息,[0]下标代表第一只股票,[1]下标代表第二只股票
self.hold_info = [
{"capital": 0, "shares": 0},
{"capital": 0, "shares": 0}
]
# 类似{'date': '2018-01-08', 'security': 'SH510300', 'direction': '卖出', 'close': 3.9056,
# 'shares_before': 13100, 'shares_trans': 300, 'shares_remain': 12800,
# 'capital_before': 50335.44, 'capital': 49991.68}
# 用于记录每次再平衡时的买入或者卖出的交易信息,格式如上
self.transactions = []
self.today_info = {"date": ""}
self.mongo_util = MongoUtil()
def start(self):
self.load_data_2_df()
count = 1
for today_date in self.meta_info['history'][0].index:
# 下面的5,就是间隔周期,间隔多少天进行一次再平衡操作
if count % 5 == 0 or count == 1:
print("-------------------------------------------")
self.today_info['date'] = today_date
row1 = self.meta_info['history'][0].loc[today_date]
row2 = self.meta_info['history'][1].loc[today_date]
price1 = row1['close']
price2 = row2['close']
last_shares1 = self.hold_info[0]['shares']
last_shares2 = self.hold_info[1]['shares']
if today_date == self.start_date:
# 第一天只执行买入
trans_shares1 = self.cal_shares_by_price(self.meta_info['total_money'] / 2, price1)
trans_shares2 = self.cal_shares_by_price(self.meta_info['total_money'] / 2, price2)
self.operate(price1, trans_shares1, "买入", price2, trans_shares2, "买入")
else:
# 非第一天,开始运算
self.rebalance(price1, price2, last_shares1, last_shares2)
count += 1
def load_data_2_df(self):
# 从mongodb中读取数据到dataframe
for security in self.meta_info['security']:
coll_name = self.mongo_util.mongo_client['admin'][security]
df = pd.DataFrame(list(coll_name.find(self.meta_info['start_condition']).sort("date", 1)))
df.set_index('date', inplace=True)
self.meta_info['history'].append(df)
def cal_shares_by_price(self, capital, current_price):
"""
总结:根据股价,计算买入的份额 = math.floor(1000/股价/100)*100
:param current_price:
:return:
"""
return math.ceil(capital / current_price / 100) * 100
def rebalance(self, price1, price2, last_shares1, last_shares2):
gap = price1 * last_shares1 - price2 * last_shares2
trans_shares1 = self.cal_shares_by_price(gap / 2, price1)
remain_shares1 = last_shares1 - trans_shares1
new_capital1 = (last_shares1 - trans_shares1) * price1
print("本次预计 交易份额1:%s, 原份额:%s, 剩余份额1:%s, 重置后本金1:%s" % (str(-trans_shares1), str(last_shares1), str(remain_shares1), str(new_capital1)))
trans_shares2 = self.cal_shares_by_price(gap / 2, price2)
remain_shares2 = last_shares2 + trans_shares2
new_capital2 = (last_shares2 + trans_shares2) * price2
print("本次预计 交易份额2:%s, 原份额:%s, 剩余份额2:%s, 重置后本金2:%s" % (str(trans_shares2), str(last_shares2), str(remain_shares2), str(new_capital2)))
if trans_shares1 == 0 or trans_shares2 == 0:
print("再平衡份额较小,本次不执行")
return
if remain_shares1 < 0 or remain_shares2 < 0:
print("再平衡后将没有持仓,本次不执行")
return
if gap > 0:
self.operate(price1, trans_shares1, "卖出", price2, trans_shares2, "买入")
else:
self.operate(price1, -trans_shares1, "买入", price2, -trans_shares2, "卖出")
def operate(self, price1, trans_shares1, direction1, price2, trans_shares2, direction2):
if direction1 == '卖出':
shares1 = self.hold_info[0]['shares'] - trans_shares1
else:
shares1 = self.hold_info[0]['shares'] + trans_shares1
if direction2 == '卖出':
shares2 = self.hold_info[1]['shares'] - trans_shares2
else:
shares2 = self.hold_info[1]['shares'] + trans_shares2
trans1 = {
"uuid": self.meta_info['security'][0] + "|" + self.today_info['date'],
"date": self.today_info['date'],
"security": self.meta_info['security'][0],
"direction": direction1,
"close": price1,
"shares_before": self.hold_info[0]['shares'],
"shares_trans": trans_shares1,
"shares_remain": shares1,
"capital_before": self.hold_info[0]['capital'],
"capital": price1 * shares1
}
self.hold_info[0]['capital'] = price1 * shares1
self.hold_info[0]['shares'] = shares1
trans2 = {
"uuid": self.meta_info['security'][1] + "|" + self.today_info['date'],
"date": self.today_info['date'],
"security": self.meta_info['security'][1],
"direction": direction2,
"close": price2,
"shares_before": self.hold_info[1]['shares'],
"shares_trans": trans_shares2,
"shares_remain": shares2,
"capital_before": self.hold_info[1]['capital'],
"capital": price2 * shares2
}
self.hold_info[1]['capital'] = price2 * shares2
self.hold_info[1]['shares'] = shares2
self.transactions.append(trans1)
self.transactions.append(trans2)
# 将本次交易数据存储到MongoDB
print(trans1)
print(trans2)
self.mongo_util.save_or_update_by_uuid(mongodb_collection="动态再平衡", data_dict=trans1)
self.mongo_util.save_or_update_by_uuid(mongodb_collection="动态再平衡", data_dict=trans2)
##############################################################
reBalance = ReBalance()
# 每次测算前,先清理掉上一次测算的结果
reBalance.mongo_util.mongo_client['admin']['动态再平衡'].drop()
reBalance.start()
测算结果明细excel: