上一篇文章:【新手入门教程】多股票策略
下一篇文章:【新手入门教程】市值轮动策略2.0
学习内容:
- 学会使用run_daily进行周期循环
- 学会取用市值数据、持仓数据、指数成分股数据
- 学会写小市值策略
这一次我们要学写一个小市值轮动策略。
简单小市值轮动策略内容是这样的:
每隔若干个交易日,等金额持有市值排名最小的前几只股票,卖出其他股票。
进一步明确下,把量确定,例如这样:
每隔10个交易日,等金额持有市值排名最小的前5只股票,卖出其他股票。
接下来按之前讲的套路出牌就好了,即初始化加周期循环。(想想之前的内容,这次具体该怎么做)
确定轮动频率,即每隔多少个交易日进行买卖。比如就是10个交易日。
确定持有最小市值股票数,即持有市值排名最小的前多少只股票。比如就是前5只。
则初始化部分代码为:
def initialize(context):
g.stocksnum = 5 # 持有最小市值股票数
g.period = 10 # 轮动频率
答疑与延伸:
- 这也是多股票策略,怎么不确定股票范围了?股票范围我们希望是能在全市场,但它是变化的,有新股上市,也有退市的,所以股票范围不能事先确定好不变,所以要在周期循环的部分不断更新并确定股票范围。
那么用run_daily
的我们的初始化-周期循环的策略框架怎么写呢?
我们的新套路如下:
def initialize(context):
写初始化的代码
run_daily(daily,time='every_bar')
def daily(context):
写周期循环的代码
首先,可以看出run_daily
要写在initialize
里。
其次,需要知道的是,你把什么函数替换掉daily
,谁就会像原来的handle_data
一样进行周期循环。虽然名字不一定是daily
,可以自己改,但不能很聚宽回测引擎已经有的函数重名,比如不能叫handle_data
、initialize
。
答疑与延伸:
- time='every_bar'是什么意思? time这个参数控制循环行为的,当time=’every_bar‘时,循环的行为就和
handle_data
一样,按天回测时就每天开盘时运行,按分钟回测时就每分钟开始时运行。当然time还可以为别的,从而循环的行为会有所不同,从而实现一些handle_data
做不到的事,具体见API原文:定时运行- 推荐以后都用
run_daily
来进行周期循环,聚宽系统上handle_data
将会弃用。
按之前的设定,我们希望策略每十天进行一次循环,进行判断,买入卖出等,即轮动频率为10天。但策略回测只提供了基本的每分钟循环和每日循环,要做到每10天进行一次循环需要写代码编程解决。
怎么编呢?想法很简单,就每日循环好了,第一天正常进行判断买入卖出,之后九天什么都不做,然后,第十一天再正常判断买入卖出,再闲九天,这不就搞定了。
因此,我们需要一个变量,记着现在是策略进行第几天了,如果能被轮动频率10整除余1就干活,进行买卖。否则,就闲着,什么都不做。
所以,至此我们的代码应当是这样写的:
def initialize(context):
g.stocksnum = 5 # 持有最小市值股票数
g.period = 10 # 轮动频率
run_daily(daily,time='every_bar')# 周期循环daily
g.days = 1 # 记录策略进行到第几天,初始为1
def daily(context):
# 判断策略进行天数是否能被轮动频率整除余1
if g.days % g.period == 1:
写进行选股、判断、买入卖出等代码
else:
pass # 什么也不做
g.days = g.days 1 #策略经过天数增加1
答疑与延伸:
g.days=1
为什么要写到initialize
里?因为g.days需要独立在周期循环之外来记录策略运行天数,而且还要在周期循环中使用,所以需要在initialize
里设置为全局变量并赋予初值1。如果写在周期循环里,每次循环都将被重置为1,无法记录运行天数。g.days % g.period
什么意思? 意为求g.days整除g.period后的余数,“%”是取余运算。例如,那么12就是等于2,因为12整除10得1余2,更多的,3等于3,3%5等于3。pass
什么意思? 在python里就是什么也不做的意思。
接下来,我们关注点将在“写进行选股、判断、买入卖出等代码”这个关键代码,其余代码暂时放到视野外。
用context
结构获取策略回测数据中的当前时间;用get_index_stocks
获取指数成分股,从而确定股票范围;用get_fundamentals
来使用财务数据中的市值数据,同时也能获取到相应股票代码。
代码如下:
# 获取当前时间
date=context.current_dt.strftime("%Y-%m-%d")
# 获取上证指数和深证综指的成分股代码并连接,即为全A股市场所有股票的股票代码
scu = get_index_stocks('000001.XSHG') get_index_stocks('399106.XSHE')
# 选出在scu内的股票的股票代码,并按照当前时间市值从小到大排序
df = get_fundamentals(query(
valuation.code # 获取 市值表-股票代码
).filter( # 条件筛选
valuation.code.in_(scu)# 市值表-股票代码在scu中
).order_by(# 排序
valuation.market_cap.asc()# 按市值表-市值 从小到大排序
), date= date
)
# 取出前g.stocksnum名的股票代码,并转成list类型,buylist即为选中的股票的代码列表
buylist =list(df['code'][:g.stocksnum])
get_fundamentals
可用来获取财务数据,用法复杂而强大,但常用的基本可以套用这个公式:(可对比上文代码理解)
get_fundamentals(query(
# 获取的数据,多个数据项用逗号隔开
数据表.数据项,数据表.数据项...
).filter( # 按条件筛选
# 具体条件 多个条件用逗号连接,且要求是要同时满足
数据表.数据项>xx,数据表.数据项<xx,数据表.数据项.in_(scu),...
).order_by( # 排序
# .asc()是从小到大 .desc()是从大到小
数据表.数据项.asc()
), date=日期
)
其中,数据表.数据项怎么找呢?到导航栏-数据-财务数据中找,比如数据表valuation,数据项market_cap,如下图:
get_index_stocks
的用法就比较简单,就是在导航栏-数据-指数数据中找到要用的指数,之后放到括号里。返回的是指数成分股list,所以获取多个指数成分股list后用“ ”连接起来。具体看文档API文档 get_index_stocks。
答疑与延伸:
- 使用更复杂的条件筛选财务数据?get_fundamentals中的与或非
get_fundamentals
的更多介绍API文档 get_fundamentals。list(df['code'][:g.stocksnum])
什么意思? df是取出的按市值从小到大排好序的股票代码,是个dataframe,df['code']
就是选取df中的code列,即股票代码。df['code'][:g.stocksnum]
就是取df中的code列中的前g.stocksnum个。最后list()
是把df['code'][:g.stocksnum]
转成将会用到的list的数据类型。dataframe的用法详情请见:dataframe 专题指南 , pandas库之数据查看、选择
要买的股票已经选好放在buylist里了,只要将资金分好,相应的买入就好了,如果看过之前那篇多股票策略的话,应该很好理解。
特别的是,存在这样的情况,某股票市值是最小前五的而被选入buylist,而且也买入持有了,但过了一段时间,其股票市值不是最小前五了,从而不再当下的buylist里,这时我们应当卖出这个股票。因为我们的策略就是10天为周期地持有市场上市值最小的五只股票。
代码如下:
# 对于每个当下持有的股票进行判断:现在是否已经不在buylist里,如果是则卖出
for stock in context.portfolio.positions:
if stock not in buylist: #如果stock不在buylist
order_target(stock, 0) #调整stock的持仓为0,即卖出
# 将资金分成g.stocksnum份
position_per_stk = context.portfolio.cash/g.stocksnum
# 用position_per_stk大小的g.stocksnum份资金去买buylist中的股票
for stock in buylist:
order_value(stock, position_per_stk)
答疑与延伸:
context.portfolio.positions
什么意思? 是当前策略的持有股票数据是个dict,不妨自己输出看看,参考内容: 回测账户数据——context、Python入门- 数据类型之字典。
至此,我们的策略都已经用代码实现好了,完整代码如下:
def initialize(context):
g.stocksnum = 5 # 持有最小市值股票数
g.period = 10 # 轮动频率
run_daily(daily,time='every_bar')# 周期循环daily
g.days = 1 # 记录策略进行到第几天,初始为1
def daily(context):
# 判断策略进行天数是否能被轮动频率整除余1
if g.days % g.period == 1:
# 获取当前时间
date=context.current_dt.strftime("%Y-%m-%d")
# 获取上证指数和深证综指的成分股代码并连接,即为全A股市场所有股票
scu = get_index_stocks('000001.XSHG') get_index_stocks('399106.XSHE')
# 选出在scu内的股票的股票代码,并按照当前时间市值从小到大排序
df = get_fundamentals(query(
valuation.code,valuation.market_cap
).filter(
valuation.code.in_(scu)
).order_by(
valuation.market_cap.asc()
), date=date
)
# 取出前g.stocksnum名的股票代码,并转成list类型,buylist为选中的股票
buylist =list(df['code'][:g.stocksnum])
# 对于每个当下持有的股票进行判断:现在是否已经不在buylist里,如果是则卖出
for stock in context.portfolio.positions:
if stock not in buylist: #如果stock不在buylist
order_target(stock, 0) #调整stock的持仓为0,即卖出
# 将资金分成g.stocksnum份
position_per_stk = context.portfolio.cash/g.stocksnum
# 用position_per_stk大小的g.stocksnum份资金去买buylist中的股票
for stock in buylist:
order_value(stock, position_per_stk)
else:
pass # 什么也不做
g.days = g.days 1 # 策略经过天数增加1
进行回测,回测结果如图:
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程