使用 JoinQuant 编写一个量化策略
1.1 节使用一个小例子来说明了量化策略编写的基本框架,这一部分对 JoinQuant 量化策略编写进行更为详细的介绍。
如何使用 JoinQuant 数据
使用 JoinQuant 可以获取行情数据和财务数据。
1. 行情数据
JoinQuant 的 API 文档中,有对能够获取的行情数据和获取方法进行介绍。
通常来说,行情数据是存储在 SecurityUnitData 中,其基本属性有:
- open 时间段开始时的价格
- close 时间段结束时的价格
- low 最低价
- high 最高价
- volume 成交的股票数量
- money 成交的金额
- factor 前复权因子
- high_limit 涨停价
- low_limit 跌停价
- price 这段时间的平均价
- pre_close 前一个单位时间结束时的价格
- paused bool 标示这支股票是否停牌
行情数据的获取方式有四种:
直接在回测中从 data 读取
- 这种方法只能在策略中使用
- 可以获得额外属性,例如 security、returns、isnan、vwap 等
- 只能调用当期时间的数据
- 只能在 handle_data 中使用
使用 attribute_history 获取
- 只能在策略中使用
- 只能取得当前时间前 n 个单位时间的数据
- 只能获取单独一个股票的数据,但是可以同时获得多个字段的数据
- 可以选择是否跳过停牌日期
history 获取
- 只能在策略中使用
- 只能取得当前时间前 n 个单位时间的数据
- 可以同时获得多个股票的数据,但是只能获得相同的一个数据字段
- 不能选择跳过停牌日期
get_current_data 获取
- 仅能获取当前时间数据
- 只能获取涨跌停价、是否停牌、开盘价
- 只能在策略中使用
- 可以同时获得多个股票、多个字段数据
get_price 获取
- 使用较为困难
- 在策略和研究中都能使用
- 可以获取任意指定时间段的数据
- 可以同时获得多个股票的数据、多个字段数据
- 不能跳过停牌日期 get_price 的使用较为复杂,因此会单独列出来进行介绍。
2. 财务数据
JoinQuant 中使用 get_fundamentals 来获取基本面数据,财务数据包括在基本面数据中。 财务数据主要包括有股票财务数据、市值数据等,这些内容可以在帮助文档中查找出来。
get_fundamentals()函数使用方法如下:
get_fundamentals(query(
# 查询内容
valuation.code # 市值表.股票代码
, valuation.market_cap # 市值表.市值
, valuation.pe_ratio # 市值表.动态市盈率
, income.total_operation_revenue # 利润表.营业总收入
).filetr(
# 筛选条件
valuation.market_cap > 1000 # 市值表.市值 > 1000
, valuation.pe_ratio < 10 # 市值表.动态市盈率 < 10
, income.total_operating_revenue > 2e10 # 利润表.营业总收入 > 2e10
).order_by(
# 排序方法
valuation.market_cap.desc() # 按照 市值表.市值 进行降序排列
).limit(
# 数量限制
100
), data = '2015-10-15' # 时间
)如何设置股票池
设置股票池有多种方式,有 get_index_stocks()、get_industry_stocks()以及自行设置股票池:
按 index 设置:
# 获取所有沪深300的股票,设为股票池
stocks = get_index_stocks('0003000.XSHG')
set_universe(stocks)按 industry 设置:
# 获取计算机/互联网行业的成分股,设为股票池
stocks = get_industry_stocks('I64')
set_universe(stocks)自定义设置:
stocks = ['000009.XSHE', '002222.XSHE', '000005.XSHE', '000002.XSHE']
set_universe(stocks)需要注意的是,在 API 文档中 set_universe()函数被称为老函数,因此上述内容是否正确需要验证。
如何买入/卖出股票
卖出买入股票都是使用订单 order 操作的。下订单的方法有四种:order、order_target、order_value、 order_target_value。
- order 买卖一定数量的股票
- order_target 通过买卖,将股票仓位调整至一定数量(股)
- order_value 买卖一定价值量(元)的股票
- order_target_value 通过买卖,将股票仓位调整至一定价值量(元)
除了下单之外,还有别的一些订单相关操作,例如取消订单等:
- cancel_order 取消订单
- get_open_orders 获取当天所有未完成的订单
- get_orders 获取当天所有订单
- get_trades 获取当天的所有成交记录
当下单成功之后,就会返回 order 对象,使用 get_trades 函数,会返回 trade 对象,这两个对象都可以获得订单相关数据:
Order 对象
- add_time 订单添加时间
- is_buy_bool 是买还是卖
- amount 下单数量,始终是正值
- filled 已经成交的股票数量
- security 股票代码
- order_id 订单 ID
- price 平均成交价格
- status 订单状态,包括未成交、部分成交、已撤销、交易所拒绝、全部成交
Trade 对象
- time 交易时间
- amount 交易数量
- price 交易价格
- trade_id 交易记录
如何止盈止损
context 类中,属性 context.portfolio.positions 中包含持有的某个股票的信息,可以使用持有的每支股票的持仓成本 和当前价格实现止盈止损,示例如下:
avg_cost = context.portfolio.position[stock].avg_cost
price = context.portfolio.position[stock].price
# 收益50%止盈
if (price/avg_cost) >= 1.5:
order_target(stock, 0)
# 亏损10%止损
if (price/avg_cost) <= 0.9:
order_target(stock, 0)策略示例
API 文档中提供了多个策略,这几个策略包含了大部分的使用方法:
- 双均线策略 双均线策略是最基础的量化策略,当五日均线高于十日均线时买入,当五日均线低于十日均线时卖出
# 导入模块
import jqdata
# 初始化函数,设定要操作的股票、基准等
def initialize(context):
# 定义一个全局变量,保存要操作的股票(平安银行)
g.security = '000001.XSHE'
# 设定沪深300作为基准
set_benchmark('000300.XSHG')
# 开启动态复权模式,即使用真实价格
set_option('use_real_price', True)
# 每个周期内需要调用的函数
def handle_data(context, data):
security = g.security
# 获取股票收盘价(过去十天的收盘价)
close_price = attribute_history(security, 10, 'ld', ['close'], df = False)
# 取得过去五天的平均价格
ma5 = close_data['close'][-5].mean()
# 取得过去十天的平均价格
ma10 = close_data['close'].mean()
# 取得当前现金
cash = context.portfolio.cash
# 交易,如果当前有余额,且五日均线大于十日均线,则买入
if ma5 > ma10:
order_value(security, cash)
# 记录
log.info('Buying %s' % (security))
# 如果五日均线小于十日均线,并且目前有头寸
elif ma5 < ma10 AND context.portfolio.positions[security].closeable_amount > 0:
order_target(security, 0)
log.info('Selling %s' % (security))
# 绘制五日均线价格
record(ma5 = ma5)
record(ma10 = ma10)正如前面所说,initialize 函数是初始化函数,在整个回测、模拟盘中最开始时执行一次,用于初始化一些全局变量, 如设置基准、设置滑点、交易手续费、股票池等:
def initialize(context):
# 设置沪深300为基准
set_benchmark('000300.XSHG')
# 设置手续费
set_commission(PerTrade(buy_cost = 0.0001, sell_cost = 0.001, min_cost = 5))
# 设置滑点
set_slippage(PriceRelatedSlippage(0.002))- 多股票追涨策略 当股票在当日收盘 30 分钟内涨幅到达 9.5%~9.9%时,进行买入,在第二天开盘时卖出。按照分钟进行回测
# 导入函数库
import jqdata
# 初始化程序
def initialized(context):
# 开启动态复权
set_option('use_real_price', True)
# 设置买入股票数量
g.daily_buy_count = 5
# 设置股票池,来自计算机信息技术板块
g.stocks = get_industry_stocks('I64') + get_industry_stocks('I65')
# 排除重复的股票
g.stocks = set(g.stocks)
# 每天开盘时执行morning_sell_all
run_daily(morning_sell_all, 'open')
# 每天开盘时执行的函数
def morning_sell_all(context):
# 将所有股票卖出
for security in context.protfolio.positions:
# 全部卖出
order_target(security, 0)
log.info('Selling %s' % (security))
# before_trading_start函数,在每日交易开始之前被调用一次
def before_trading_start():
# 今天买入的股票
g.today_bought_stocks = set();
# 得到所有股票昨日收盘价,每天取一次,因此放在before_trading_start中
g.last_df = history(1, 'ld', 'close', g.stocks)
# 周期运行函数
def handle_data(context, data):
# 判断是否在当日最后2小时,只追涨最后两小时中满足追涨条件的
if context.current_dt.hour < 13:
return
# 每天只买这么多
if len(g.today_bought_stocks) >= g.daily_buy_count:
return
# 遍历今天还没有买入的股票
for security in (g.stocks - g.today_bought_stocks):
# 获取当前价格
price = data[security].close
# 获取昨天收盘价
last_close = g.last_df[security][0]
# 如果上一时间点价格已经涨了9.5%~9.9%,且涨停区间大于1元,今天没有买入该股票
if price/last_close > 1.095\
AND price/last_close < 1.099
AND data[security].high_limit - last_close >= 1.0:
# 得到当前资金余额
cash = context.portfolio.cash
# 计算今天还需要买入的股票数量
need_count = g.daily_buy_count - len(g.today_bought_stocks)
# 将现金分成多份
buy_cash = context.portfolio.cash/need_count
# 买入这么多现金的股票
order_value(security, buy_cash)
# 放入今日已买股票的集合
g.today_bought_stocks.add(security)
# 记录这次买入
log.info('Buying %s' % (security))
# 买过了五只股票
if len(g.today_bought_stocks) >= g.daily_buy_count:
break相比前一个策略,多股票追涨策略显然更为复杂,这个策略中,共出现了四个模块,initialize 模块和 handle_daily 模块在前面已经介绍过,initialize 模块对策略进行初始化设置,而 handle_daily 模块则是在每个周期中需要运行的函数 本策略多了一个 morning_sell_all 模块和 before_trading_start 模块。
其中,before_trading_start 模块是每日在开盘之前需要进行的操作,同 handle_daily 一样也是循环执行, 但是该函数只会在交易之前执行,当交易开启之后,就不再执行这个函数,直到第二天交易。
morning_sell_all 模块是用户自定义的模块,用于每日交易之前卖掉所有的股票,这里由于是自己定义的,因此 在 initialize 函数中添加了一句:
run_daily(morning_sell_all, 'open')表示在每日开盘时执行 morning_sell_all,将当前所有的股票都卖掉。