最近通过对元宵大师的小册Python股票数据分析:手把手构建量化交易系统的学习,对股票的量化交易有了新的认识,并且自己根据大师教学写出了自己的首个策略,在此跟大家分享,还望大家多多指导:
炒股谨记
时刻铭记“韭菜”的特征,反省自身的问题,避免加入“韭菜”队伍中。
“韭菜”普遍喜欢追求胜率,希望自己所有的交易都能以盈利收尾。直接表现为盈利的单子稍有一点利润就忙于兑现,亏损的交易却不卖出,等待持有到可以再次盈利为止。 “韭菜”交易的主要目的是寻找交易的刺激,企图抓住市场中每一次价格的波动,贪婪地不想错过任何一次机会,甚至认为要像上班一样每天进行交易。 “韭菜”由于捡便宜的心理,喜欢在股票下跌的时候买入,殊不知此时很有可能是逆势操作。 “韭菜”喜欢在交易中时刻盯着行情报价系统,无法豁然面对价格的波动,幻想在低点买入,在高点卖出,抓住市场中的每一次波动。 “韭菜”往往不能坚持自己的策略决策,往往因为临时的一点点小的变动,将自己的交易搁置甚至反转。
股票量化交易策略
股票数据获取
获取股票数据的方法有多种,包括pandas、tushare等很多,个人偏好于tushare获取,相比较来说速度比较快点:
import tushare as ts
# '600410', '2018-10-01', '2019-04-01'
def get_stock_hist_data(stock_code, start_date, end_date):
df_sh = ts.get_hist_data(stock_code, start_date, end_date)
df_sh.index = pd.to_datetime(df_sh.index)
df_sh.sort_index(inplace=True)
return df_sh
得到股票历史数据如:

择时策略
择时策略采用大师提供的N日突破择时策略,其中参数N1、N2参数配置15、5,两参数可以控制买入卖出的频次,参数配置越小买入卖出的频率就越高 对获取到的股票历史数据df_sh进行处理,新增了cloumn买入卖出列signal=0 代表卖出 signal=1 代表买入
def deal_stock_data(stock_code=None, stock_s_date=None, stock_e_date=None, N1=None, N2=None):
df_sh = get_stock_hist_data(stock_code, stock_s_date, stock_e_date)
df_sh['N1_high'] = df_sh.high.rolling(window=N1).max()
df_sh['N1_high'] = df_sh.N1_high.shift(1)
fill_max = df_sh.close.expanding().max()
df_sh['N1_high'].fillna(value=fill_max, inplace=True)
df_sh['N2_low'] = df_sh.low.rolling(window=N2).min()
df_sh['N2_low'] = df_sh.N2_low.shift(1)
fill_min = df_sh.close.expanding().min()
df_sh['N2_low'].fillna(value=fill_min, inplace=True)
for kindex, ktoday in df_sh.iterrows():
if ktoday.close > ktoday.N1_high:
print("N-day_buy_signal:", kindex, ktoday.close)
buy_price = ktoday.close
df_sh.loc[kindex, 'signal'] = 1
elif ktoday.close < ktoday.N2_low:
print('sell:', kindex, ktoday.close)
df_sh.loc[kindex, 'signal'] = 0
buy_price = 0
else:
pass
df_sh['signal'].fillna(method='ffill', inplace=True)
df_sh['signal'] = df_sh.signal.shift(1)
df_sh['signal'].fillna(method='bfill', inplace=True)
return df_sh
之后根据上述方法获取到股票的历史信息及买入卖出点数据后,再计算出股票的基准首义率及执行策略后的收益率:
# 计算基准收益率
df_sh['benchmark_profit'] = np.log(df_sh.close/df_sh.close.shift(1))
# 计算执行策略后对应收益率
df_sh['trend_profit'] = df_sh.signal * df_sh.benchmark_profit
对股票数据进行可视化,转换成图表:
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
fig = plt.figure(figsize=(8, 6), dpi=100, facecolor='white') gs = matgrid.GridSpec(3, 1, left=0.10, bottom=0.15, right=0.96, top=0.96, wspace=None,
hspace=0, height_ratios=[4, 2, 2])
graph_buyorsell = fig.add_subplot(gs[0, :]) # 股票收盘价及买卖点指示图
graph_profit = fig.add_subplot(gs[1, :]) # 股票基准收益率及策略收益率
graph_money = fig.add_subplot(gs[2, :]) # 资金曲线图
根据买入卖出数据点标识出买入卖出区间,并计算账户余额:
for index, today in df_sh.iterrows():
if today.signal == 1 and skip_today == 0:
skip_today = -1
stock_hold = int(cash_total / today.close)
stock_hold_money = 0
cash_total = 0
start = df_sh.index.get_loc(index)
graph_buyorsell.annotate('买入', xy=(index, df_sh.close.asof(index)),
xytext=(index, df_sh.close.asof(index) + 2),
arrowprops=dict(facecolor='r', shrink=0.1),
horizontalalignment='left', verticalalignment='top')
# print("buy: ", index)
elif today.signal == 0 and skip_today == -1:
skip_today = 0
end = df_sh.index.get_loc(index)
cash_total = int(stock_hold * today.close)
stock_hold_money = 0
if df_sh.close[end] < df_sh.close[start]:
graph_buyorsell.fill_between(df_sh.index[start:end], 0, df_sh.close[start:end], color='green',
alpha=0.38)
else:
graph_buyorsell.fill_between(df_sh.index[start:end], 0, df_sh.close[start:end], color='red', alpha=0.38)
graph_buyorsell.annotate('卖出', xy=(index, df_sh.close.asof(index)), xytext=(index,
df_sh.close.asof(index) + 2),
arrowprops=dict(facecolor='g', shrink=0.1), horizontalalignment='left',
verticalalignment='top')
# print("sell : ", (df_sh.close[end] - df_sh.close[start]))
profit_total[index] = df_sh.close[end] - df_sh.close[start]
if skip_today == -1:
stock_hold_money = int(stock_hold * today.close)
df_sh.loc[index, 'total'] = stock_hold_money
else:
df_sh.loc[index, 'total'] = cash_total
根据账户金额及股票市值,计算资金最大滚动曲线:
df_sh['max_total'] = df_sh['total'].expanding().max()
df_sh[['max_total', 'total']].plot(grid=True, ax=graph_money)
df_sh['per_total'] = df_sh['total'] / df_sh['max_total']
min_point_total = df_sh.sort_values(by=['per_total']).iloc[[0], df_sh.columns.get_loc('per_total')]
max_point_total = df_sh[df_sh.index <= min_point_total.index[0]].sort_values(by=['total'],
ascending=False).iloc[
[0], df_sh.columns.get_loc('total')]
graph_money.annotate('滚动最大点',
xy=(max_point_total.index[0], df_sh.total.asof(max_point_total.index[0])),
xytext=(max_point_total.index[0], df_sh.total.asof(max_point_total.index[0]) + 4),
arrowprops=dict(facecolor='yellow', shrink=0.1), horizontalalignment='left',
verticalalignment='top')
graph_money.annotate('最大回撤点',
xy=(min_point_total.index[0], df_sh.total.asof(min_point_total.index[0])),
xytext=(min_point_total.index[0], df_sh.total.asof(min_point_total.index[0]) + 4),
arrowprops=dict(facecolor='yellow', shrink=0.1), horizontalalignment='left',
verticalalignment='top')
最后去掉第一二个图标的x标签:
for label in graph_buyorsell.xaxis.get_ticklabels():
label.set_visible(False)
for label in graph_profit.xaxis.get_ticklabels():
label.set_visible(False)
plt.legend(loc='best')
plt.title(u'招商银行')
plt.show()
执行代码后:
show_pic_stock(cash_total=10000, stock_code='000002', st='2019-01-01', et='2020-01-01',
N1=15, N2=5)
结果如下图:

风险管理策略
我们根据股票的ATR(Average True Range)指标作为止盈止损的基数, 止盈值设置为n_win倍的ATR值,止损值设置为n_loss倍的ATR值,n_win和n_loss分别为最大止盈系数和最大止损系数,此处设置最大止盈系数为2,最大止损系数为0.9,倾向于盈利值要大于亏损值。触发止盈止损条件为:
当n_win*ATR值 > (今日收盘价格 - 买入价格),触发止盈信号,卖出股票
当n_loss*ATR值 > (买入价格 - 今日收盘价格),触发止损信号,卖出股票
修改方法:deal_stock_data(stock_code=None, stock_s_date=None, stock_e_date=None, N1=None, N2=None, n_loss=None, n_win=None) 增加 n_loss=None, n_win=None两个参数,得到如下结果:

增加仓位管理
仓位管理
首先创建账户类,来操作买入股票卖出股票
class ST_account:
def __init__(
self,
init_hold={},
init_cash=10000):
self.hold = init_hold
self.cash = init_cash
# 查询账户持股数量
def hold_available(self, code=None):
if code in self.hold:
return self.hold[code]
# 查询账户可用资金
def cash_available(self):
return self.cash
# 更新账户资产
def latest_assets(self, price):
assets = 0
for code in self.hold.values():
assets += code * price
assets += self.cash
return assets
# 账户买入卖出股票
def send_order(self, code=None, amount=None, price=None, order_type=None):
if order_type == 'buy':
self.cash = self.cash - amount*price
self.hold[code] = amount
else:
self.cash = self.cash + amount*price
self.hold[code] = self.hold[code] - amount
if self.hold[code] == 0:
del self.hold[code]
修改show_pic_stock(account, stock_code=None, st=None, et=None, N1=None, N2=None, n_loss=None, n_win=None)方法,传入account对象,再修改买入卖出操作时对account对象操作的代码:
# 买入
account.send_order(code=stock_code, amount=stock_hold, price=today.close, order_type='buy')
# 卖出
account.send_order(code=stock_code, amount=stock_hold, price=today.close,order_type='sell')
动态仓位管理
《海龟交易法则》建议第一笔仓位的一个ATR波动与总资金1%波动相对应。即:买入股票数量 * ATR = 资金 * 1% 假如1月1日股票A向上突破4.12元出现买入点,手上有10万元资金,那么10万资金的1%波动就是1000元。截止1月1日,股票A的14日ATR为0.15元,1000元÷0.15元=6666股。 我们股票买入数量更改为:
stock_hold = int(account.cash_available() * 0.01 / today.atr14)
买入股票后我们可以根据ATR值的变化来控制我们的持仓数量,在根据股票买卖信号买入或卖出后做如下处理:
if skip_today == -1 and not np.isnan(today.atr14):
posit_num_wave = int(account.total_cash * 0.01 / today.atr14) # 动态计算持仓的股票数量
am = int(posit_num_wave - account.hold_available(code=stock_code))
print("postice_num:{} ava_num{}, am bigger than 0:{}".format(posit_num_wave,
account.hold_available(code=stock_code),
am > 0))
if am > 0: # 波动后加仓
print("adjust buy{} minux{}".format(posit_num_wave, am))
account.send_order(code=stock_code,
amount=int(posit_num_wave - account.hold_available(code=stock_code)),
price=today.close, order_type='buy')
else:
print("adjust buy{} minux{}".format(posit_num_wave, am))
account.send_order(code=stock_code,
amount=int(account.hold_available(code=stock_code) - posit_num_wave),
price=today.close, order_type='sell')
展示持股数量如下图所示,我们股票的持股数量随着ATR的变化而变化:

蒙特卡洛算法优化
我们的方法:def show_pic_stock(account, stock_code=None, st=None, et=None, N1=None, N2=None, n_loss=None, n_win=None):中的 参数N1,N2,n_loss,n_win前面是按照经验随机取值,我们可以通过蒙特卡洛算法对4个参数优化后选出最优的参数组合。 在N1取10-30,N2取5-15,n_loss取0.5-1.5,n_win取1.5,2.5范围内取200组随机组合,进行计算:
def cal_navagel(n=200):
n1_min, n1_max = 10, 30
n2_min, n2_max = 5, 15
win_min, win_max = 1.5, 2.5
loss_min, loss_max = 0.5, 1.5
ma_list = []
profit_list = []
for i in range(0, n + 1):
n1 = int(random.uniform(n1_min, n1_max))
n2 = int(random.uniform(n2_min, n2_max))
win = round(random.uniform(win_min, win_max), 1)
loss = round((random.uniform(loss_min, loss_max)), 1)
ma_list.append([n1, n2, win, loss])
profit_list.append(run_for_para(stock_code='000002', st='2019-01-01', et='2020-01-01',
N1=n1, N2=n2, n_loss=loss, n_win=win))
profit_max = max(profit_list)
ma_max = ma_list[profit_list.index(max(profit_list))]
plt.bar(np.arange(0, len(ma_list), 1), profit_list)
plt.annotate(str(ma_max) + '\n' + str(profit_max),
xy=(ma_list.index(ma_max), profit_max), xytext=(ma_list.index(ma_max) - 10, profit_max - 10),
arrowprops=dict(facecolor='yellow', shrink=0.1),
horizontalalignment='left', verticalalignment='top')
# 设置坐标标签字体大小
plt.xlabel('均线参数')
plt.ylabel('资金收益')
# 设置坐标轴的取值范围
plt.ylim(min(profit_list) * 0.99, max(profit_list) * 1.01)
# 设置图例字体大小
plt.legend(['profit_list'], loc='best')
plt.title("均线最优参数")
plt.show()
得出如图所示:



个人学习心得
这是我个人自学python后首次接触股票量化交易,通过对小册的学习,元宵大师老师的疑问解答觉得自己学到不少东西,提升自己python编程的技术外,也对股票量化交易有了一个初步的认识。 股票量化交易对股票历史数据的分析,各种图形化展示确实方便直观很多,但是量化交易也只是也只能够根据历史数据来进行预测的工具,策略方面本人也只是刚入门以后还有很多学习的地方。
感谢您的阅读!