大小盘轮动


无脑大小盘轮动

我大A风格明显,一段时间大盘好于中小盘,一段时间中小盘好于大盘。所以就有了以下策略

量化策略:https://mp.weixin.qq.com/s/hPjVbBKomfMhowc32jUwhA

回测

  1. 交易对象:沪深300(或上证50)、创业板指数的ETF(或中证500),前者代表大市值,后者代表小市值。(选择两个长期向上的、相关性低的指数)

  2. 筛选条件:用最近N日的涨跌幅,作为筛选条件(默认N=20,即大约为最近一个月)。

  3. 风格轮动:每日根据条件,选择涨幅大的指数持有。

回测代码


import pandas as pd
import numpy as np
from function import *
import matplotlib.pyplot as plt
import os
os.chdir(os.path.abspath(os.path.dirname(__file__)))

pd.set_option('expand_frame_repr', False)  # 当列太多时不换行
# pd.set_option('display.max_rows', 5000)  # 最多显示数据的行数

# 读取数据
# df_coin1 = pd.read_csv('BTCUSD-1d.csv', encoding='gbk', parse_dates=['candle_end_time'])
# df_coin2 = pd.read_csv('ETHUSD-1d.csv', encoding='gbk', parse_dates=['candle_end_time'])

df_coin1 = pd.read_csv('SZ#159919.txt', encoding='gbk', header=1, names=['candle_end_time', 'open', 'high', 'low', 'close', 'volume', 'amount'])
df_coin2 = pd.read_csv('SH#510500.txt', encoding='gbk', header=1, names=['candle_end_time', 'open', 'high', 'low', 'close', 'volume', 'amount'])
df_coin1 = df_coin1.dropna()
df_coin2 = df_coin2.dropna()
# df_coin1['candle_end_time'] = df_coin1['candle_end_time'] + ' 08:00:00'
# df_coin2['candle_end_time'] = df_coin2['candle_end_time'] + ' 08:00:00'

df_coin1['candle_end_time'] = pd.to_datetime(df_coin1['candle_end_time'], format='%Y-%m-%d')
df_coin2['candle_end_time'] = pd.to_datetime(df_coin2['candle_end_time'], format='%Y-%m-%d')
# print(df_coin1)
# print(df_coin2)
# 设置参数
# trade_rate = 2.5 / 1000  # 千分之2.5的交易费用远高于市场平均水平
trade_rate = 0.6/10000  # 千分之2.5的交易费用远高于市场平均水平
# trade_rate = 0  # 千分之2.5的交易费用远高于市场平均水平
momentum_days = 20  # 计算多少天的涨跌幅

# 计算两种币每天的涨跌幅pct
df_coin1['coin1_pct'] = df_coin1['close'].pct_change(1)
df_coin2['coin2_pct'] = df_coin2['close'].pct_change(1)
# 重命名行
df_coin1.rename(columns={'open': 'coin1_open', 'close': 'coin1_close'}, inplace=True)
df_coin2.rename(columns={'open': 'coin2_open', 'close': 'coin2_close'}, inplace=True)
# 合并数据
df = pd.merge(left=df_coin1[['candle_end_time', 'coin1_open', 'coin1_close', 'coin1_pct']], left_on=['candle_end_time'],
              right=df_coin2[['candle_end_time', 'coin2_open', 'coin2_close', 'coin2_pct']],
              right_on=['candle_end_time'], how='left')
# 计算N日的涨跌幅momentum
df['coin1_mom'] = df['coin1_close'].pct_change(periods=momentum_days)
df['coin2_mom'] = df['coin2_close'].pct_change(periods=momentum_days)
# 轮动条件
df.loc[df['coin1_mom'] > df['coin2_mom'], 'style'] = 'coin1'
df.loc[df['coin1_mom'] < df['coin2_mom'], 'style'] = 'coin2'
df.loc[(df['coin1_mom'] < 0) & (df['coin2_mom'] < 0), 'style'] = 'empty'
# 相等时维持原来的仓位。
df['style'].fillna(method='ffill', inplace=True)
# 收盘才能确定风格,实际的持仓pos要晚一天。
df['pos'] = df['style'].shift(1)
# 删除持仓为nan的天数
df.dropna(subset=['pos'], inplace=True)
# 数字货币从17年开始回测
# df = df[df['candle_end_time'] >= pd.to_datetime('20170101')]
# 计算策略的整体涨跌幅strategy_pct
df.loc[df['pos'] == 'coin1', 'strategy_pct'] = df['coin1_pct']
df.loc[df['pos'] == 'coin2', 'strategy_pct'] = df['coin2_pct']
df.loc[df['pos'] == 'empty', 'strategy_pct'] = 0

# 调仓时间
df.loc[df['pos'] != df['pos'].shift(1), 'trade_time'] = df['candle_end_time']
# 将调仓日的涨跌幅修正为开盘价买入涨跌幅
df.loc[(df['trade_time'].notnull()) & (df['pos'] == 'coin1'), 'strategy_pct_adjust'] = df['coin1_close'] / (df['coin1_open'] * (1 + trade_rate)) - 1
df.loc[(df['trade_time'].notnull()) & (df['pos'] == 'coin2'), 'strategy_pct_adjust'] = df['coin2_close'] / (df['coin2_open'] * (1 + trade_rate)) - 1

df.loc[df['trade_time'].isnull(), 'strategy_pct_adjust'] = df['strategy_pct']
# print(df)
# 扣除卖出手续费(这里存在一个BUG,因为我大A不是T+0的交易方式,所以当天买入,当天是不能卖出的)暂时略过这个BUG,后期可修改为一周操作一次。或者修改为开盘卖出
df.loc[(df['trade_time'].shift(-1).notnull()) & (df['pos'] != 'empty'), 'strategy_pct_adjust'] = (1 + df['strategy_pct_adjust']) * (1 - trade_rate) - 1
# df.loc[(df['trade_time'].shift(-1).notnull()) & (df['pos'] != 'empty'), 'strategy_pct_adjust'] = (1 + df['strategy_pct']) * (1 - trade_rate) - 1

# 将只持有一天的数据,推迟到第二天开盘卖出
df.loc[(df['pos'] == 'coin2') & (df['pos'].shift(-1) != 'coin2') & (df['pos'].shift(1) != 'coin2'), 'strategy_pct_adjust'] = df['coin2_open'].shift(-1) / (df['coin2_open'] * (1 + trade_rate)) - 1
df.loc[(df['pos'] == 'coin1') & (df['pos'].shift(-1) != 'coin1') & (df['pos'].shift(1) != 'coin1'), 'strategy_pct_adjust'] = df['coin1_open'].shift(-1) / (df['coin1_open'] * (1 + trade_rate)) - 1

# print(df)
# 空仓的日子,涨跌幅用0填充
df['strategy_pct_adjust'].fillna(value=0.0, inplace=True)
del df['strategy_pct'], df['style']

df.reset_index(drop=True, inplace=True)
# 计算净值
df['coin1_net'] = df['coin1_close'] / df['coin1_close'][0]
df['coin2_net'] = df['coin2_close'] / df['coin2_close'][0]
df['strategy_net'] = (1 + df['strategy_pct_adjust']).cumprod()

# 评估策略的好坏
res = evaluate_investment(df, 'strategy_net', time='candle_end_time')
print(res)

# 绘制图片
plt.plot(df['candle_end_time'], df['strategy_net'], label='strategy')
plt.plot(df['candle_end_time'], df['coin1_net'], label='coin1_net')
plt.plot(df['candle_end_time'], df['coin2_net'], label='coin2_net')
plt.show()
# 保存文件
print(df.tail(10))
df.to_excel('大小盘轮动12.xlsx', encoding='gbk', index=False)

回测结果

在这里插入图片描述

以上回测结果是明显好于两个标的物的。

我们再看看回测控制的如何

累积净值                     3.99
年化收益                   19.09%
最大回撤                  -30.04%
最大回撤开始时间  2018-01-24 00:00:00
最大回撤结束时间  2019-01-22 00:00:00
年化收益/回撤比                 0.64

也是非常好的,30%的最大回撤是我可接受的


文章作者: 江小白不喝酒
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 江小白不喝酒 !
评论
  目录