Skip to content

使用 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 标示这支股票是否停牌

行情数据的获取方式有四种:

  1. 直接在回测中从 data 读取

    • 这种方法只能在策略中使用
    • 可以获得额外属性,例如 security、returns、isnan、vwap 等
    • 只能调用当期时间的数据
    • 只能在 handle_data 中使用
  2. 使用 attribute_history 获取

    • 只能在策略中使用
    • 只能取得当前时间前 n 个单位时间的数据
    • 只能获取单独一个股票的数据,但是可以同时获得多个字段的数据
    • 可以选择是否跳过停牌日期
  3. history 获取

    • 只能在策略中使用
    • 只能取得当前时间前 n 个单位时间的数据
    • 可以同时获得多个股票的数据,但是只能获得相同的一个数据字段
    • 不能选择跳过停牌日期
  4. get_current_data 获取

    • 仅能获取当前时间数据
    • 只能获取涨跌停价、是否停牌、开盘价
    • 只能在策略中使用
    • 可以同时获得多个股票、多个字段数据
  5. get_price 获取

    • 使用较为困难
    • 在策略和研究中都能使用
    • 可以获取任意指定时间段的数据
    • 可以同时获得多个股票的数据、多个字段数据
    • 不能跳过停牌日期 get_price 的使用较为复杂,因此会单独列出来进行介绍。

2. 财务数据

JoinQuant 中使用 get_fundamentals 来获取基本面数据,财务数据包括在基本面数据中。 财务数据主要包括有股票财务数据、市值数据等,这些内容可以在帮助文档中查找出来。

get_fundamentals()函数使用方法如下:

python
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 设置:

python
# 获取所有沪深300的股票,设为股票池
stocks = get_index_stocks('0003000.XSHG')
set_universe(stocks)

按 industry 设置:

python
# 获取计算机/互联网行业的成分股,设为股票池
stocks = get_industry_stocks('I64')
set_universe(stocks)

自定义设置:

python
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 对象

    1. add_time 订单添加时间
    2. is_buy_bool 是买还是卖
    3. amount 下单数量,始终是正值
    4. filled 已经成交的股票数量
    5. security 股票代码
    6. order_id 订单 ID
    7. price 平均成交价格
    8. status 订单状态,包括未成交、部分成交、已撤销、交易所拒绝、全部成交
  • Trade 对象

    1. time 交易时间
    2. amount 交易数量
    3. price 交易价格
    4. trade_id 交易记录

如何止盈止损

context 类中,属性 context.portfolio.positions 中包含持有的某个股票的信息,可以使用持有的每支股票的持仓成本 和当前价格实现止盈止损,示例如下:

python
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 文档中提供了多个策略,这几个策略包含了大部分的使用方法:

  1. 双均线策略 双均线策略是最基础的量化策略,当五日均线高于十日均线时买入,当五日均线低于十日均线时卖出
python
# 导入模块
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 函数是初始化函数,在整个回测、模拟盘中最开始时执行一次,用于初始化一些全局变量, 如设置基准、设置滑点、交易手续费、股票池等:

python
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))
  1. 多股票追涨策略 当股票在当日收盘 30 分钟内涨幅到达 9.5%~9.9%时,进行买入,在第二天开盘时卖出。按照分钟进行回测
python
# 导入函数库
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 函数中添加了一句:

python
run_daily(morning_sell_all, 'open')

表示在每日开盘时执行 morning_sell_all,将当前所有的股票都卖掉。