搞一个简单的交易策略回测(难点的我也不会啊)。此策略基于布林通道,当股价低于布林通道下轨线且持仓为0时,以下轨线价格全仓买入(不关心风险,反正也不要本钱)。当股价突破布林线上轨且有持仓时,以上轨线价格清仓卖出。(期间也没有考虑交易的费用)
首先,从MySQL数据库读取已经下载好的股票历史数据,我用的库名是stock_databases。股票就随便选一个。
from sqlalchemy import create_engine
import pandas as pd
def calculate_boll(code):
# 用于数据读取的表字段
columns = 'date, open, high, low, close, volume'
# 连接数据库引擎对象
engine = create_engine("mysql+pymysql://root:123456@localhost:3306/stock_databases?charset=utf8")
# 读取数据
df = pd.read_sql('SELECT {} FROM {}_{}'.format(columns, code[:2], code[3:]),
con=engine,index_col='date')
print(df.info())
calculate_boll('sh.600478')
数据类型在我保存到数据库之前就做了相应的处理,接着计算布林通道:
from sqlalchemy import create_engine
import pandas as pd
def calculate_boll(code):
# 用于数据读取的表字段
columns = 'date, open, high, low, close, volume'
# 连接数据库引擎对象
engine = create_engine("mysql+pymysql://root:123456@localhost:3306/stock_databases?charset=utf8")
# 读取数据
df = pd.read_sql('SELECT {} FROM {}_{}'.format(columns, code[:2], code[3:]),
con=engine,index_col='date')
# 计算标准差
standard_deviations = df['close'].rolling(20).std(ddof=1)
# 添加布林通道的上轨、下轨和中轨
df['md'] = round(df['close'].rolling(20).mean(), 2)
df['upper'] = round(df['md'] + 2 * standard_deviations, 2)
df['lower'] = round(df['md'] - 2 * standard_deviations, 2)
return df
有了布林通道的数据后,就可以通过比较最高价、最低价与上下轨的大小来确定交易信号。
import numpy as np
import pandas as pd
from sqlalchemy import create_engine
def trade_signal(code):
"""
计算交易信号,当股价低于布林下轨时买入,高于上轨时卖出
:param code: 股票代码
:return: 包含交易信号的DataFrame
"""
data = calculate_boll(code)
# 新建一个DataFrame,以data的index为index
df = pd.DataFrame(index=data.index)
df[['price', 'upper', 'lower']] = data[['close', 'upper', 'lower']]
# 比较最高价、最低价与BOLL上下轨的大小来确定突破信号
df['buy_signal'] = np.where(data['low'] < data['lower'], True, False)
df['sell_signal'] = np.where(data['high'] > data['upper'], True, False)
# 初始化订单状态为0
df['orders'] = 0
# 初始仓位为0
position = 0
# 遍历数据表
for i in range(len(df)):
# 当买入信号为True且仓位为0时买入
if df.buy_signal[i] and position == 0:
# 买入指令为1
df.orders.values[i] = 1
# 仓位加1
position = 1
# 当卖出信号为True且仓位为1时卖出
elif df.sell_signal[i] and position == 1:
# 卖出指令为-1
df.orders.values[i] = -1
# 仓位清0
position = 0
return df
有了交易指令,就可以买进卖出了。当指令为1且持仓为0时,以布林下轨价格全仓买入。当指令为-1且有持仓时,以布林上轨价格清仓卖出。
def calculate_income(code):
"""
计算收益
:param code: 股票代码
:return: 计算后的DataFrame
"""
data = trade_signal(code)
# 初始最大可持仓量,可用资金为100000元
max_position = 0
money = 100000.0
# 初始持仓股票手数、可用资金
data['stock'] = 0
data['money'] = 100000.0
for i in range(len(data)):
# 当买卖指令为1
if data.orders.values[i] == 1:
# 以下轨价全仓买入
max_position = money // (data.lower.values[i] * 100)
# 可用资金减去买入股票所花费的资金
money = money - max_position * data.lower.values[i] * 100
# 当买卖指令为-1
elif data.orders.values[i] == -1:
# 可用资金加上卖出股票所得到的资金
money = money + max_position * data.upper.values[i] * 100
# 持仓清零
max_position = 0
# 持仓手数、可用资金
data['stock'].values[i] = max_position
data['money'].values[i] = money
# 账户资产总额
data['total'] = data['stock'] * data['price'] * 100 + data['money']
return data
如上图所示,2022年8月31日,交易指令为1,全仓买入,狠狠的干了一票。再看'total'列,账户总资产从最初的100000元变成了现在的560000+元(当然我没有考虑交易费用等等之类因素)。乍一看好像还不错,实际上行不行的通?用matplotlib可视化来看一看。
def plt_show(code):
"""
数据可视化
:param code:股票代码
:return: None
"""
# 用来正常显示中文字体
plt.rcParams['font.sans-serif'] = ['KaiTi']
# 解决负号显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
data = calculate_income(code)
# 设置画布大小
plt.figure(figsize=(10, 6))
# 设置标签
plt.title('{}布林策略收益图'.format(code))
plt.xlabel('日期')
plt.ylabel('股价')
# 绘制股价、布林上轨和下轨的折线图
plt.plot(data.index, data.price, color='black', label='股价')
plt.plot(data.index, data.upper, color='blue', label='上轨')
plt.plot(data.index, data.lower, color='pink', label='下轨')
# 用红色正三角标记买入点
plt.scatter(data.loc[data.orders == 1].index, data['lower'][data.orders == 1],
marker='^', c='r', s=80, label='买入')
# 用绿色倒三角标记卖出点
plt.scatter(data.loc[data.orders == -1].index, data['upper'][data.orders == -1],
marker='v', c='g', s=80,label='卖出')
# 图例放在左上角
plt.legend(loc='upper left')
# 绘制双坐标
plt.twinx()
# 绘制收益折线图
plt.plot(data.index, data.total, color='red', label='总资产')
# 图例放在右上角
plt.legend(loc='upper right')
plt.show()
这个。。。。看起来好乱啊!从图中可以看出这是近20年的数据。注意图中红线,从18年开始就脱离了股价噌噌往上,显得有些不真实,是不是我哪里计算错了?
完整代码:
from sqlalchemy import create_engine
import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
def calculate_boll(code):
"""
计算BOLL通道
:param code:股票代码
:return:添加了BOLL数值的DataFrame
"""
# 用于数据读取的表字段
columns = 'date, open, high, low, close, volume'
# 连接数据库引擎对象
engine = create_engine("mysql+pymysql://root:123456@localhost:3306/stock_databases?charset=utf8")
# 读取数据
df = pd.read_sql('SELECT {} FROM {}_{}'.format(columns, code[:2], code[3:]),
con=engine, index_col='date')
# 计算标准差
standard_deviations = df['close'].rolling(20).std(ddof=1)
# 添加布林通道的上轨、下轨和中轨.这里注意:下面的保留2位小数,因为python的缘故,并不能做到真正的四舍五入
df['md'] = round(df['close'].rolling(20).mean(), 2)
df['upper'] = round(df['md'] + 2 * standard_deviations, 2)
df['lower'] = round(df['md'] - 2 * standard_deviations, 2)
return df
def trade_signal(code):
"""
计算交易信号,当股价低于布林下轨时买入,高于上轨时卖出
:param code: 股票代码
:return: 包含交易信号的DataFrame
"""
data = calculate_boll(code)
# 新建一个DataFrame,以data的index为index
df = pd.DataFrame(index=data.index)
df[['price', 'upper', 'lower']] = data[['close', 'upper', 'lower']]
# 比较最高价、最低价与BOLL上下轨的大小来确定突破信号
df['buy_signal'] = np.where(data['low'] < data['lower'], True, False)
df['sell_signal'] = np.where(data['high'] > data['upper'], True, False)
# 初始化订单状态为0
df['orders'] = 0
# 初始仓位为0
position = 0
# 遍历数据表
for i in range(len(df)):
# 当买入信号为True且仓位为0时买入
if df.buy_signal[i] and position == 0:
# 买入指令为1
df.orders.values[i] = 1
# 仓位加1
position = 1
# 当卖出信号为True且仓位为1时卖出
elif df.sell_signal[i] and position == 1:
# 卖出指令为-1
df.orders.values[i] = -1
# 仓位清0
position = 0
return df
def calculate_income(code):
"""
计算收益
:param code: 股票代码
:return: 计算后的DataFrame
"""
data = trade_signal(code)
# 初始最大可持仓量,可用资金为100000元
max_position = 0
money = 100000.0
# 初始持仓股票手数
data['stock'] = 0
data['money'] = 100000.0
for i in range(len(data)):
# 当买卖指令为1
if data.orders.values[i] == 1:
# 以下轨价全仓买入
max_position = money // (data.lower.values[i] * 100)
# 可用资金减去买入股票所花费的资金
money = money - max_position * data.lower.values[i] * 100
# 当买卖指令为-1
elif data.orders.values[i] == -1:
# 可用资金加上卖出股票所得到的资金
money = money + max_position * data.upper.values[i] * 100
# 持仓清零
max_position = 0
# 持仓手数、可用资金
data['stock'].values[i] = max_position
data['money'].values[i] = money
# 账户资产总额
data['total'] = data['stock'] * data['price'] * 100 + data['money']
return data
def plt_show(code):
"""
数据可视化
:param code:股票代码
:return: None
"""
# 用来正常显示中文字体
plt.rcParams['font.sans-serif'] = ['KaiTi']
# 解决负号显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
data = calculate_income(code)
# 设置画布大小
plt.figure(figsize=(10, 6))
# 设置标签
plt.title('{}布林策略收益图'.format(code))
plt.xlabel('日期')
plt.ylabel('股价')
# 绘制股价、布林上轨和下轨的折线图
plt.plot(data.index, data.price, color='black', label='股价')
plt.plot(data.index, data.upper, color='blue', label='上轨')
plt.plot(data.index, data.lower, color='pink', label='下轨')
# 用红色正三角标记买入点
plt.scatter(data.loc[data.orders == 1].index, data['lower'][data.orders == 1],
marker='^', c='r', s=80, label='买入')
# 用绿色倒三角标记卖出点
plt.scatter(data.loc[data.orders == -1].index, data['upper'][data.orders == -1],
marker='v', c='g', s=80,label='卖出')
# 图例放在左上角
plt.legend(loc='upper left')
# 绘制双坐标
plt.twinx()
# 绘制收益折线图
plt.plot(data.index, data.total, color='red', label='总资产')
# 图例放在右上角
plt.legend(loc='upper right')
plt.show()
plt_show('sz.002056')
股市有风险,入市须谨慎!
这个粗糙的策略是极不靠谱的。