这是一个没有未来函数的策略,回测结果非常的棒。斩获了131倍,同时最大回撤也才33.49%,但是,却有一个隐形的bug,我会写在文章的最后。
低价股策略回测步骤说明,一共分5步走:
Step1:登录聚宽JoinQuant量化平台,若无账号,用手机号可以免费注册。
网址:https://www.joinquant.com
Step2:进入【股票列表】界面,新建一个【股票策略】,并清空新建策略里面的所有内容
具体操作请见:https://www.joinquant.com/help/api/help#faq:%E5%A6%82%E4%BD%95%E5%88%9B%E5%BB%BA%E5%B9%B6%E8%BF%90%E8%A1%8C%E7%AD%96%E7%95%A5%EF%BC%9F
Step3:打开低价股策略源码文件【低价股策略-JoinQuant源码.txt】,全选复制当中全部源码,然后粘贴进上一步新建的策略当中。
Step4:回测参数设置。起止日期和起始资金根据个人需求设置,但运行频率必须是【分钟】,源码版本必须是【Python3】。
开始时间:2018-01-01
结束时间:2023-05-29
起始资金:100000
运行频率:分钟
源码版本:Python3
Step5:完成上一步参数设置后,点击界面右上方的蓝色回测按钮【运行回测】,等待完成回测即可。由于分钟级别的回测比较耗时,请耐心等待,切记中途不能关闭回测网页或关闭网络。
#导入函数库
from jqdata import *
import numpy as np
import pandas as pd
from pandas import Series
from datetime import datetime, time
# 初始化函数,设定基准等等
def initialize(context):
# 设定沪深300作为基准
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 开启“防未来函数”功能
set_option('avoid_future_data', True)
# 输出内容到日志
log.info('初始函数开始运行且全局只运行一次')
# 过滤掉order系列API产生的比error级别低的log
log.set_level('order', 'error')
# ******关闭“强制撮合”功能******
# set_option("match_by_signal", False) #原策略当中没有,后期测试加入
# ******设定成交量比例******
# 根据实际行情限制每个订单的成交量,原策略当中没有,后期测试加入
# set_option('order_volume_ratio', 0.001) #成交量不超过总成交量的0.1%
# ******为全部交易品种设定固定值滑点******
# 原策略收益高的原因就是没有设置滑点,设置正常滑点后就亏损了
# 0.02表示价差是2分钱,买入时买贵1分钱,卖出时卖便宜1分钱
# set_slippage(FixedSlippage(0.02)) #原策略当中没有,后期测试加入
# 指定股价的阈值,低于阈值的股票会被剔除
# 若测试高价股,则可以将阈值设为50、100、200等
g.price_thre = 1.5
# 选取的股票
g.sel_stock = None
# 策略开始买卖时间
g.strategy_starttime=time(10, 20)
# 策略尾盘撮合买入时间
g.strategy_endtime=time(14, 55)
### 股票相关设定 ###
# 股票类每笔交易时的手续费是:买入时佣金万分之2.5,卖出时佣金万分之2.5加千分之一印花税, 每笔交易佣金最低扣5块钱
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.00025, close_commission=0.00025, min_commission=5), type='stock')
run_weekly(weekly, weekday=1, time='09:31', reference_security='000300.XSHG')
# 开盘前运行
run_daily(before_market_open, time='before_open', reference_security='000300.XSHG')
# 开盘时运行
run_daily(market_open, time='every_bar', reference_security='000300.XSHG')
# 收盘后运行
run_daily(after_market_close, time='after_close', reference_security='000300.XSHG')
#调整持仓
def weekly(context):
g.sel_stock = None
current_data = get_current_data()
# 取得所有股票
stock_list=get_all_securities().index.tolist()
# 取得5天的close
last_5_prices=history(count=5, field='close', security_list=stock_list)
# 取5天close 平均数
last_5_prices=pd.DataFrame({'mean_val':last_5_prices.mean()})
# 均值排序
last_5_prices_sort=last_5_prices.sort_values(axis=0,ascending=True,by=['mean_val'])
# 取得均值>=1.5的股票
q='mean_val>=%s' %str(g.price_thre)
last_5_prices_query=last_5_prices_sort.query(q)
last_5_prices_stock=list(last_5_prices_query.index)
today_str=context.current_dt.strftime("%Y-%m-%d")
#log.info(last_5_prices_stock)
# 去除停牌、ST、退市股,选取股价最低的一个
for security in last_5_prices_stock:
if (current_data[security].paused==False) \
and not current_data[security].is_st \
and '*' not in current_data[security].name \
and '退' not in current_data[security].name:
g.sel_stock= security
break
if g.sel_stock is not None:
log.info(g.sel_stock)
log.info(get_security_info(g.sel_stock).display_name)
# 调仓
sell_list = set(context.portfolio.positions.keys()) - set([g.sel_stock])
print('sell:',sell_list)
for stock in sell_list:
order_target_value(stock, 0)
else:
for stock in context.portfolio.positions.keys():
order_target_value(stock, 0)
def before_market_open(context):
log.info('函数运行时间(before_market_open):'+context.current_dt.strftime("%Y-%m-%d %H:%M:%S"))
g.not_buy_flg=1
g.not_sell_flg=1
g.last_buy_orderid=None
g.last_sell_orderid=None
g.last_buy_price = 0
g.last_sell_price = 0
g.price_50m={'open':0.000,'close':0.000,'high':0.000,'low':0.000}
g.price_before_close=0.000
# 根据当前时间取得购买价格
def get_buy_price(context):
sel_stock_price=attribute_history(g.sel_stock,1, unit='50m',fields=('open','close','high','low'),skip_paused=True, df=True, fq='pre')
if context.current_dt.time()>=g.strategy_starttime and context.current_dt.time()< g.strategy_endtime:
return sel_stock_price['low'][0]
else:
return sel_stock_price['close'][0]
# 根据当前时间取得卖出价格
def get_sell_price(context):
sel_stock_price=attribute_history(g.sel_stock,1, unit='50m',fields=('open','close','high','low'),skip_paused=True, df=True, fq='pre')
if context.current_dt.time()>=g.strategy_starttime and context.current_dt.time()< g.strategy_endtime:
return sel_stock_price['high'][0]
else:
return sel_stock_price['close'][0]
def market_open(context):
current_data = get_current_data()
if g.sel_stock is None:
return
# 判断到了交易时间开始交易
if context.current_dt.time()>=g.strategy_starttime:
if g.not_buy_flg==1:
now_buy_price = get_buy_price(context)
# 取消
orders=get_open_orders()
for _order in orders.values():
if _order.order_id==g.last_buy_orderid:
cancel_order(_order)
g.last_buy_price=now_buy_price
buy_count=int(context.portfolio.available_cash/now_buy_price/100)*100
if buy_count>=100:
new_order=order(g.sel_stock,buy_count,LimitOrderStyle(now_buy_price))
if new_order is not None:
if str(new_order.status) == 'held':
g.not_sell_flg = 0
else:
g.last_buy_orderid = new_order.order_id
# 卖盘逻辑
if context.current_dt.time()<g.strategy_endtime and g.not_sell_flg==1:
now_sell_price = get_sell_price(context)
orders=get_open_orders()
for _order in orders.values():
if _order.order_id==g.last_sell_orderid:
cancel_order(_order)
if g.sel_stock in context.portfolio.positions \
and context.portfolio.positions[g.sel_stock].closeable_amount>0:
g.last_sell_price=now_sell_price
new_order=order(g.sel_stock,-context.portfolio.positions[g.sel_stock].closeable_amount,LimitOrderStyle(now_sell_price))
if new_order is not None:
if str(new_order.status) == 'held':
g.not_sell_flg = 0
else:
g.last_sell_orderid = new_order.order_id
else:
g.not_sell_flg==0
##收盘后运行函数
def after_market_close(context):
log.info(str('函数运行时间(after_market_close):'+str(context.current_dt.time())))
#得到当天所有成交记录
trades = get_trades()
for _trade in trades.values():
log.info('成交记录:'+str(_trade))
log.info('一天结束')
log.info('##############################################################')
如果,我们把回测时间改成今年的5月份呢?结果会如何?
好像对于十来倍的收益,接近一半的回撤也还行,我们来看看他买了啥。
也就是买之前没戴ST,买之后戴了ST的帽子,五一之后开市,一直跌停到现在,正是由于连续的跌停,导致该策略无法卖出该股票,截止到5月29日,硬生生吃了17个跌停,造成了58%的回撤。
但是,这个策略的bug,依旧不是这里,这里也仅仅是策略独有的缺陷,就是梭哈。那么bug是什么,就是假设我能不废滑点的全部买进。
这里是大多数小火鸡都会遇到的问题,假设我能每一只股票都能不费滑点的全部买进,那么是不是就可以做到无损套利,开启逆天模式呢?当然不是,首先A股是撮合制的,如果你要买进,在接近你的挂单价直接就给你撮合了。另外,不是你委托了就能全部买进去的。
比方,我有100万股挂买一15.21,但是市场只有10万股在15.21成交,在策略中,碰到就默认全部买入,这也是大多数策略会遇到的毛病。
不费滑点的全部买进,才是这个策略最大的bug