(量化) 用 Tushare 包实现一个简单的回测框架

【Reference】
1. B站:清华计算机博士带你学-Python金融量化分析
2. Tushare 官网(作者ID:492952)

本博客所包含的项目代码基本参考Reference1,并对其中tushare API的更新做了对应的修改。全部代码已上传至作者的 github,下文中仅针对代码思路做一个知识梳理

1. 回测需求 & 效果展示

以中国平安(601318.SH)为买卖对象,检验双均线策略在 2020-05-10 至 2021-01-01 的收益情况。效果如下:
请添加图片描述

2. 代码框架

2.1 对象

(1) 存储账户信息、回测信息

账户信息:现金,所持股票

回测信息:

  1. 开始/结束日期
  2. 当前日期
  3. 基准:一般会以一只股票或一个指数为基准,用于比较策略优劣性
  4. 开始至结束之间所有交易日的信息
class Context:
    def __init__(self, cash, start_date, end_date):
        # 账户信息
        self.cash = cash    # 现金
        self.positions = {
    
    } # 持有的股票信息
        # 回测信息
        self.start_date = start_date
        self.end_date = end_date
        self.dt = start_date
        self.benchmark = None
        self.date_range = trade_cal[(trade_cal['is_open'] == '1') & \
                                    (trade_cal['cal_date'] >= start_date) & \
                                    (trade_cal['cal_date'] <= end_date)]['cal_date'].values

其中,trade_cal 存储了所有交易日的日期信息。可通过 tushare 包的 pro.trade_cal() 获取

(2) 存储其他全局变量

class G:
	pass

2.2 函数

(0) 主体函数

def run():
    initialize(context) # 初始化

	# 创建一个DataFrame,储存画图所需数据
    plt_df = pd.DataFrame(index=pd.to_datetime(context.date_range), columns=['value'])
    # 存储股票的上一个交易日的价格信息,避免以为股票停牌而无法获取价格
    last_prices = {
    
    }
    # 回测开始时的股票价格,用于计算之后的价格变动曲线
    initial_value = context.cash
    
    for dt in context.date_range:
        context.dt = dt # 更新当前时间
        handle_data(context)
		# 计算账户价值 = 现金 + 持仓股票
        value = context.cash
        for stock in context.positions.keys():
            try:
                data = get_today_data(stock)
                last_prices[stock] = data['open']
            except KeyError:
                # 如果取不到,说明当日停牌,取上一个交易日的价格
                price = last_prices[stock]
            value += price * context.positions[stock].amount
        plt_df.loc[dt, 'value'] = value
        
    # 绘制策略
    plt_df['ratio'] = (plt_df['value'] - initial_value) / initial_value
    # 绘制基准
    bm_df = attribute_daterange_history(context.benchmark, context.start_date, context.end_date)
    bm_init = bm_df['open'][0]
    plt_df['benchmark_ratio'] = (bm_df['open'] - bm_init) / bm_init

    plt_df[['ratio', 'benchmark_ratio']].plot()
    plt.show()

(1) 初始化函数

  1. 设置目标股票
  2. 设置基准
  3. 设置双均线信息(g.p1 & g.p2)
  4. 获取目标股票的历史数据:在以下代码中,hist_1 获取的是回测开始日期前 g.p2 天的数据,hist_2 获取的是回测开始至结束之间的数据。将两者合并后即可得到双均线回测所需要的所有股票价格数据
def initialize(context):
	g.security = '601318.SH'
    set_benchmark('601318.SH')
    g.p1 = 5
    g.p2 = 60

    hist_1 = attribute_history(g.security, g.p2)
    hist_2 = attribute_daterange_history(g.security, context.start_date, context.end_date)
    g.hist = hist_1.append(hist_2) 

(2) 每个交易日都需执行的函数

def handle_data(context):
    hist = g.hist[:dateutil.parser.parse(context.dt)][-g.p2:]
    ma5 = hist['close'][-g.p1:].mean()
    ma60 = hist['close'].mean()

	# 实现双均线策略:
	# 如果短均线高于长均线,且股票不在持仓中,则买入
	# 反之,且股票在持仓中,则卖出
    if ma5 > ma60 and g.security not in context.positions.keys():
        order_value(g.security, context.cash)
    elif ma5 < ma60 and g.security in context.positions.keys():
        order_target(g.security, 0)

(3) 下单函数

github 中上传的代码共包含四个下单函数:

  1. order: 购买一定的股票数量
  2. order_value 购买一定的股票金额
  3. order_target 购买到一定数量的股票
  4. order_target_value 购买到一定金额的股票

而这四个下单函数均基于:

def _order(today_data, security, amount):
    if today_data.empty: return
    # 获取股票价格
    price = today_data['open']
    # 判断是否持有该股票
    try:
        test = context.positions[security]
    except KeyError:
        # 如果卖出操作,直接退出函数
        if amount <= 0: return
        # 如果买入操作,创建position
        context.positions[security] = pd.Series(dtype=float)
        context.positions[security]['amount'] = 0

    # 买入/卖出操作时,必须以100的倍数购买,除非全部卖出
    if (amount % 100 != 0) and (amount != -context.positions[security].amount):
        amount = int(amount/100) * 100

    # 更新持仓
    context.positions[security].amount = context.positions[security].get('amount') + amount
    if context.positions[security].amount == 0: # 如果持仓股数为0,删除
        del context.positions[security]
    # 更新现金
    context.cash -= amount * price

猜你喜欢

转载自blog.csdn.net/weixin_43728138/article/details/122859016
今日推荐