```
'''
二八轮动择时买卖小市值
每周五(或者本周的最后一个交易日)临近收盘时,将沪深300指数和中证500指数切换到周线
(20日)状态,分别查看两者过去四周的累计涨幅。如果过去四周两个指数其中任一个获得
正回报,那么就在收盘前买入满足条件的最小市值股票持有,每10日进行一次调仓,此过程
中如果出现2个指数均获得负回报,则清仓,直至下一次切换。
版本:v1.2.5
日期:2016.07.14
作者:Morningstar
'''
from kuanke.user_space_api import *
class trade_stat():
def __init__(self):
self.trade_total_count = 0
self.trade_success_count = 0
self.statis = {'win': [], 'loss': []}
def reset(self):
self.trade_total_count = 0
self.trade_success_count = 0
self.statis = {'win': [], 'loss': []}
# 记录交易次数便于统计胜率
def watch(self, context, stock):
self.trade_total_count += 1
amount = context.portfolio.positions[stock].total_amount
avg_cost = context.portfolio.positions[stock].avg_cost
price = context.portfolio.positions[stock].last_sale_price
current_value = amount * price
cost = amount * avg_cost
percent = round((current_value - cost) / cost * 100, 2)
if current_value > cost:
self.trade_success_count += 1
win = [stock, percent]
self.statis['win'].append(win)
else:
loss = [stock, percent]
self.statis['loss'].append(loss)
def report(self, context):
cash = context.portfolio.cash
totol_value = context.portfolio.portfolio_value
position = 1 - cash/totol_value
log.info("收盘后持仓概况:%s" % str(list(context.portfolio.positions)))
log.info("仓位概况:%.2f" % position)
self.print_win_rate(context.current_dt.strftime("%Y-%m-%d"), context.current_dt.strftime("%Y-%m-%d"), context)
# 打印胜率
def print_win_rate(self, current_date, print_date, context):
if str(current_date) == str(print_date):
win_rate = 0
if 0 result['value']:
result['stock'] = statis[0]
result['value'] = statis[1]
return result
# 统计单次亏损最高的股票
def statis_most_loss_percent(self):
result = {}
for statis in self.statis['loss']:
if {} == result:
result['stock'] = statis[0]
result['value'] = statis[1]
else:
if statis[1] before trading start @ %s", str(context.current_dt))
pass
def after_trading_end(context):
#log.info("==> after trading end @ %s", str(context.current_dt))
g.trade_stat.report(context)
# 得到当前未完成订单
orders = get_open_orders()
for _order in orders.values():
log.info("canceled uncompleted order: %s" %(_order.order_id))
pass
def initialize(context):
log.info("==> initialize @ %s", str(context.current_dt))
# 设置手续费率
set_commission(PerTrade(buy_cost=0.0003, sell_cost=0.0013, min_cost=5))
# 设置基准指数:沪深300指数 '000300.XSHG'
set_benchmark('000300.XSHG')
# 使用真实价格回测(模拟盘推荐如此,回测请注释)
set_option('use_real_price', True)
g.period = 10 # 调仓频率,单位:日
g.day_count = 0 # 调仓日计数器,单位:日
# 配置选股参数
g.selected_stock_count = 100 # 备选股票数目
g.buy_stock_count = 3 # 买入股票数目
# 配置是否根据市盈率选股
# 此回测如果不按pe选股,收益更高,回撤也稍大,个人取舍
g.select_by_pe = True
# is_filter_by_pe为True时有效,下同
g.max_pe = 200
g.min_pe = 2
g.filter_gem = True # 配置是否过滤创业板股票
g.filter_blacklist = False # 配置是否过滤黑名单股票,回测建议不建议,模拟运行时开启
# 输出各类参数
log.info("调仓日频率: %d 日" %(g.period))
log.info("备选股票数目: %d" %(g.selected_stock_count))
log.info("购买股票数目: %d" %(g.buy_stock_count))
log.info("是否根据PE选股: %s" %(g.select_by_pe))
if g.select_by_pe:
log.info("最大PE: %s" %(g.max_pe))
log.info("最小PE: %s" %(g.min_pe))
log.info("是否过滤创业板股票: %s" %(g.filter_gem))
log.info("是否过滤黑名单股票: %s" %(g.filter_blacklist))
if g.filter_blacklist:
log.info("当前股票黑名单:%s" %str(get_blacklist()))
# 加载统计模块
g.trade_stat = trade_stat()
# 每天下午14:52执行
run_daily(do_handle_data, '14:52')
'''
# 按分钟回测
def handle_data(context, data):
# 获得当前时间
hour = context.current_dt.hour
minute = context.current_dt.minute
# 每天下午14:53调仓
if hour == 14 and minute == 53:
'''
def do_handle_data(context):
log.info("调仓日计数 [%d]" %(g.day_count))
# 回看指数前20天的涨幅
hs300 = '000300.XSHG' # 沪深300指数,表示二,大盘股
zz500 = '000905.XSHG' # 中证500指数,表示八,小盘股
gr_hs300 = get_growth_rate(hs300)
gr_zz500 = get_growth_rate(zz500)
log.info("当前沪深300指数的20日涨幅 [%.2f%%]" %(gr_hs300*100))
log.info("当前中证500指数的20日涨幅 [%.2f%%]" %(gr_zz500*100))
# 前20日两指数涨幅均小于0,卖出所有持仓股票
#
# 如果跌停没有卖出,则第二天策略执行时继续根据大盘判定该卖则卖,如果第二天继
# 续跌,还是多吃了一个跌
#
# 前20日若有一个指数涨幅大于0,买入靠前的小市值股票
if gr_hs300 0:
if g.day_count % g.period == 0:
log.info("==> 满足条件进行调仓")
buy_stocks = select_stocks(context)
log.info("选股后可买股票: %s" %(buy_stocks))
adjust_position(context, buy_stocks)
g.day_count += 1
# 获取股票n日以来涨幅,根据当前价计算
# n 默认20日
def get_growth_rate(security, n=20):
lc = get_close_price(security, n)
#c = data[security].close
c = get_close_price(security, 1, '1m')
return (c - lc) / lc;
# 获取前n个单位时间当时的收盘价
def get_close_price(security, n, unit='1d'):
return attribute_history(security, n, unit, ('close'), True)['close'][0]
# 自定义下单,加日志
def order_target_value_(security, value):
if value == 0:
log.debug("Selling out %s" % (security))
else:
log.debug("Order %s to value %f" % (security, value))
return order_target_value(security, value)
# 清空卖出所有持仓
def clear_position(context):
log.info("==> 清仓,卖出所有股票")
for stock in context.portfolio.positions.keys():
g.trade_stat.watch(context, stock)
order_target_value_(stock, 0)
# 过滤停牌、ST类股票及其他具有准备退市的股票
def filter_paused_and_st_stock(stock_list):
current_data = get_current_data()
return [stock for stock in stock_list if not current_data[stock].paused and not current_data[stock].is_st
and 'ST' not in current_data[stock].name and '*' not in current_data[stock].name and '退' not in current_data[stock].name]
# 过滤涨停的股票
def filter_limitup_stock(context, stock_list):
last_prices = history(1, unit='1m', field='close', security_list=stock_list)
current_data = get_current_data()
# 已存在于持仓的股票即使涨停也不过滤,避免此股票再次可买,但因被过滤而导致选择别的股票
return [stock for stock in stock_list if stock in context.portfolio.positions.keys()
or last_prices[stock][-1] current_data[stock].low_limit]
#return [stock for stock in stock_list if last_prices[stock][-1] > current_data[stock].low_limit]
# 过滤黑名单的股票
def filter_blacklist_stock(context, stock_list):
blacklist = get_blacklist()
return [stock for stock in stock_list if stock not in blacklist]
# 过滤掉创业版
def filter_gem_stock(context, stock_list):
return [stock for stock in stock_list if stock[0:3] != '300']
# 过滤股票20日增长率为负的股票
def filter_by_growth_rate(stock_list):
return [stock for stock in stock_list if get_growth_rate(stock) > 0]
# 选取小市值股票
def select_stocks(context):
# 选择备选股票,获取指定数目市值最小的市盈率大于2且小于200的股票
q = None
if g.select_by_pe:
q = query(valuation.code, valuation.market_cap).filter(
valuation.pe_ratio > g.min_pe,
valuation.pe_ratio position_count:
value = context.portfolio.cash / (g.buy_stock_count - position_count)
for stock in buy_stocks:
if context.portfolio.positions[stock].total_amount == 0:
order_target_value_(stock, value)
if len(context.portfolio.positions) == g.buy_stock_count:
break
'''
# 可保证仓位平均分配,但是会减少收益高的仓位
value = context.portfolio.portfolio_value / g.buy_stock_count
for stock in buy_stocks:
#if context.portfolio.positions[stock].total_amount == 0:
order_target_value_(stock, value)
'''
'''
更新:
2016.07.14
v1.2.5
增加跌停股票过滤,多谢@沙米建议,同期提高了不少收益率,提高了胜率,降低了最大单
股亏损,回撤基本不变
v1.2.4
完善日志输出,打印策略配置参数,调整主要日志为中文
2016.07.13
v1.2.3
完善ST及停牌股票的过滤(来自@菜菜午出头),优化日志
2016.07.12
v1.2.2
优化增幅计算代码
v1.2.1
改进黑名单配置,感谢@az的指正,修正了创业板股票判断的bug
v1.2.0
感谢 @zw @沙米 的完善,在其基础上增加了过滤控制,可选择配置市盈率选股、过滤创业板及过滤黑名单,
设置了pe参数,增加了注释,因为配置了pe选股及过滤了创业板,收益有所降低,但是回撤也相应降低了
注:黑名单有时效性,回测已注释
2016.07.02
v1.1.2
修正调仓日因持仓股票涨停被过滤导致本该继续持有的股票被卖出的问题,收益回撤影响不大,提高了单股盈利
说明,当前调仓因股票停牌不能卖出的时候会继续持有,始终保持指定数目的股票持仓
2016.07.01
v1.1.1
根据@zx1967的反馈,调整了选股数目大小(g.selected_stock_cnt = 100,其实可以更大),尽量避免数
目过小而恰巧全部过滤掉以致最终可买股票为空,结果收益更高了,回撤差不多
2016.06.30
v1.1.0
感谢 @莫邪的救赎 的建议,修改为run_daily执行,主要是提高了回测速度,回测时间缩短了近一半
'''
```
2016-07-18