中国衍生品领导企业,期货开户首选! 全国客服热线:400-1799-757

策略研究

期货开户-首选中国国际期货,二十余年持续引领中国期货行业发展!

首页 > 量化研究

【量化投资】十行代码成宽客

浏览量:1132 最后更新时间:2020-12-04

  提起量化投资,就不得不提量化投资的标杆——华尔街传奇人物詹姆斯·西蒙斯(James Simons)。

  通过将数学理论巧妙融合到投资的实战之中,西蒙斯成为了投资界中首屈一指的“模型先生”。由其运作的大奖章基金(Medallion)在1989-2009的二十年间,平均年收益率为35%,若算上44%的收益提成,则该基金实际的年化收益率可高达60%,比同期标普500指数年均回报率高出20多个百分点,即使相较金融大鳄索罗斯和股神巴菲特的操盘表现,也要遥遥领先十几个百分点。最为难能可贵的是,纵然是在次贷危机全面爆发的2008年,该基金的投资回报率仍可稳稳保持在80%左右的惊人水准。西蒙斯通过将数学模型和投资策略相结合,逐步走上神坛,开创了由他扛旗的量化时代。

  说回量化投资,量化投资就是利用计算机技术并且采用一定的数学模型去实践投资理念,实现投资策略的过程。

  价值投资和趋势投资(技术分析)是引领过去一个世纪的投资方法,随着计算机技术的发展,已有的投资方法和计算机技术相融合,产生了量化投资。

  价值投资就是实业投资思维在股市上的应用。凡是买股票时把自己当成长期股东的都是价值投资者。这里的长期是指5-10年的股票持有期。价值投资要求回到初心,强调股票投资和实业投资的一致性。股票投资、股权投资、风险投资、天使投资等投资方式的内核都是一致的。

  “趋势”一词就是指事物自身发展运行的一种自然规律 ,它具有重复、持续、有序、有向的特点。简单说,趋势投资就是投资人以投资标的的上涨或下跌周期来作为买卖交易的一种投资方式。当然这种趋势的发展也有自身的原因存在。在我看来,趋势投资就是短线投机、波段操作与中长线投资的完美组合。

  下面教你用10行代码写个量化交易策略——单股票均线策略

  1 确定策略内容与框架

  若昨日收盘价高出过去20日平均价今天开盘买入股票

  若昨日收盘价低于过去20日平均价今天开盘卖出股票

  def initialize(context):

  用来写最开始要做什么的地方

  def handle_data(context,data):

  用来写每天循环要做什么的地方

  复制代码

  几乎所有策略都基于这个基本的策略框架:先初始化,然后循环操作

  1 初始化,即最开始要做的事情,如选定股票,设置变量、参数等等

  2 周期循环:即每个周期要做的事情,如计算指标,买入卖出等,周期可能是分钟,天等,本文策略的周期是一天。当你要做一些盘中短线操作的时候,周期就要调成分钟,先别着急会遇到的。

  initializeinitialize(context)

  初始化方法,在整个回测、模拟实盘中最开始执行一次,用于初始一些全局变量

  参数

  context: Context对象, 存放有当前的账户/股票持仓信息

  返回

  None

  示例

  def initialize(context):

  # g为全局变量

  g.security = "000001.XSHE"

  复制代码

  定时运行函数, 可选

  run_monthly

  run_weekly

  run_daily

  定时运行函数, 可选

  run_monthly

  run_weekly

  run_daily

  复制代码

  参数

  func: 一个函数, 此函数必须接受一个参数, 就是 context

  monthday: 每月的第几个交易日, 可以是负数, 表示倒数第几个交易日。如果超出每月总交易日个数,则取临近的交易日执行。

  weekday: 每周的第几个交易日, 可以是负数, 表示倒数第几个交易日。如果超出每周总交易日个数,则取临近的交易日执行。

  time: 日内执行时间, 具体到分钟, 一个字符串, 可以是具体执行时间, 比如 “10:00”, 或者 “every_bar”, “open”, “before_open” 和 “after_close”, 他们的含义是:

  every_bar: 在每一个 Bar 结束后调用. 按天会在每天的开盘时调用一次,按分钟会在每天的每分钟运行,可以在该时段下单

  open: 开盘的第一分钟(等同于”9:30”),可以在该时段下单

  before_open: 开盘前(等同于”9:20”, 但是请不要使用直接使用此时间, 后续可能会调整)

  after_close: 收盘后(半小时内运行)

  morning: 早上 8:00 运行,可以在该时段下单

  night: 晚上 20:00 运行,可以在该时段下单

  注:当time不等于 every_bar/open/before_open/after_close/morning/night 时, 必须使用分钟级回测

  reference_security: 时间的参照标的。如参照’000001.XSHG’,交易时间为9:30-15:00。如参照’IF1512.CCFX’,2016-01-01之后的交易时间为9:30-15:00,在此之前为 9:15-15:15。

  返回

  None

  示例

  def weekly(context):

  print 'weekly %s %s' % (context.current_dt, context.current_dt.isoweekday())

  def monthly(context):

  print 'monthly %s %s' % (context.current_dt, context.current_dt.month)

  def daily(context):

  print 'daily %s' % context.current_dt

  def initialize(context):

  # 指定每月第一个交易日, 在开盘第一分钟执行

  run_monthly(monthly, 1, 'open')

  # 指定每周倒数第一个交易日, 在开盘前执行, 此函数中不能下单

  run_weekly(weekly, -1, 'before_open')

  # 指定每天收盘后执行, 此函数中不能下单

  run_daily(daily, 'after_close')

  # 指定在每天的10:00运行, 必须选择分钟回测, 否则不会执行

  run_daily(daily, '10:00')

  # 参照股指期货的时间每分钟运行一次, 必须选择分钟回测, 否则不会执行

  run_daily(daily, 'every_bar', reference_security='IF1512.CCFX')

  复制代码

  handle_data, 可选handle_data(context, data)

  该函数每个单位时间会调用一次, 如果按天回测,则每天调用一次,如果按分钟,则每分钟调用一次。

  该函数依据的时间是股票的交易时间,即 9:30 - 15:00. 期货请使用定时运行函数。

  该函数在回测中的非交易日是不会触发的(如回测结束日期为2016年1月5日,则程序在2016年1月1日-3日时,handle_data不会运行,4日继续运行)。

  参数

  context: Context对象, 存放有当前的账户/标的持仓信息

  data: 一个字典(dict), key是股票代码, value是当时的SecurityUnitData 对象. 存放前一个单位时间(按天回测, 是前一天, 按分钟回测, 则是前一分钟) 的数据. 注意:

  为了加速, data 里面的数据是按需获取的, 每次 handle_data 被调用时, data 是空的 dict, 当你使用 data[security] 时该 security 的数据才会被获取.

  data 只在这一个时间点有效, 请不要存起来到下一个 handle_data 再用

  注意, 要获取回测当天的开盘价/是否停牌/涨跌停价, 请使用 get_current_data

  返回

  None

  示例

  def handle_data(context, data):

  order("000001.XSHE",100)

  复制代码

  2 初始化

  我们要写设置要交易的股票的代码,比如 兔宝宝(002043)

  def initialize(context):

  g.security = '002043.XSHE'# 存入兔宝宝的股票代码

  复制代码

  >- "g."是什么?全局变量前都要写"g.",全局变量就是全局都能用的变量,一般变量只能在该函数下使用。如security不加"g.",只能在第一部分即initialize里用,不能在第二部分handle_data里用。

  - "XSHE"是什么? 股票代码使用时要加后缀,深交所股票代码后缀为 ".XSHE ",上交所股票代码后缀为 ".XSHG"。

  3 获取收盘价与均价

  首先,获取昨日股票的收盘价

  # 用法:变量 = data[股票代码].close

  last_price = data[g.security].close# 取得最近日收盘价,命名为last_price

  复制代码

  然后,获取近二十日股票收盘价的平均价

  # 用法:变量 = data[股票代码].mavg(天数,‘close’)

  # 获取近二十日股票收盘价的平均价,命名为average_price

  average_price = data[g.security].mavg(20, 'close')

  复制代码

  SecurityUnitData

  一个单位时间内的股票的数据

  基本属性

  以下属性也能通过history/attribute_history/get_price获取到

  open: 时间段开始时价格

  close: 时间段结束时价格

  low: 最低价

  high: 最高价

  volume: 成交的股票数量

  money: 成交的金额

  factor: 前复权因子, 我们提供的价格都是前复权后的, 但是利用这个值可以算出原始价格, 方法是价格除以factor, 比如:close/factor

  high_limit: 涨停价

  low_limit: 跌停价

  avg: 这段时间的平均价, 等于money/volume

  price: 已经过时, 为了向前兼容, 等同于 avg

  pre_close: 前一个单位时间结束时的价格, 按天则是前一天的收盘价, 按分钟则是前一分钟的结束价格

  paused: bool值, 这只股票是否停牌, 停牌时open/close/low/high/pre_close依然有值,都等于停牌前的收盘价, volume=money=0

  额外的属性和方法

  security: 股票代码, 比如’000001.XSHE’

  returns: 股票在这个单位时间的相对收益比例, 等于 (close-pre_close)/pre_close

  isnan(): 数据是否有效, 当股票未上市或者退市时, 无数据, isnan()返回True

  mavg(days, field='close'): 过去days天的每天收盘价的平均值, 把field设成’avg’(等同于已过时的’price’)则为每天均价的平均价, 下同

  vwap(days): 过去days天的每天均价的加权平均值, 以days=2为例子, 算法是:

  (avg1 * volume1 + avg2 * volume2) / (volume1 + volume2)

  stddev(days): 过去days天的每天收盘价的标准差

  注:mavg/vwap/stddev:都会跳过停牌日期, 如果历史交易天数不足, 则返回nan

  4 判断是否买卖

  数据都获取完,该做买卖判断了

  # 如果昨日收盘价高出二十日平均价, 则买入,否则卖出

  if last_price > average_price:

  买入

  elif last_price < average_price:

  卖出

  复制代码

  问题来了,现在该写买卖下单了,但是拿多少钱去买我们还没有告诉计算机,所以每天还要获取账户里现金量。

  # 用法:变量 = context.portfolio.cashcash = context.portfolio.cash# 取得当前的现金量,命名为cash

  、>- context.portfolio 是什么?

  Context

  subportfolios: 当前单个操作仓位的资金、标的信息,是一个SubPortfolio 的数组

  portfolio: 账户信息,即subportfolios 的汇总信息, Portfolio对象,单个操作仓位时,portfolio 指向 subportfolios[0]

  current_dt: 当前单位时间的开始时间, datetime.datetime对象,

  按天回测时, hour = 9, minute = 30, second = microsecond = 0,

  按分钟回测时, second = microsecond = 0

  previous_date: 前一个交易日, datetime.date对象, 注意, 这是一个日期, 是 date, 而不是 datetime

  universe: 查询set_universe()设定的股票池, 比如: [‘000001.XSHE’, ‘600000.XSHG’]

  run_params: 表示此次运行的参数, 有如下属性

  start_date: 回测/模拟开始日期, datetime.date对象

  end_date: 回测/模拟结束日期, datetime.date对象

  type: 运行方式, 如下三个字符串之一

  ’simple_backtest’: 回测, 通过点击’编译运行’运行

  ‘full_backtest’: 回测, 通过点击’运行回测’运行

  ‘sim_trade’: 模拟交易

  frequency: 运行频率, 如下三个字符串之一

  ’day’

  ‘minute’

  ‘tick’

  我们对此对象也做了和 g 一样的处理:

  可以添加自己的变量, 每次进程关闭时持久保存, 进程重启时恢复.

  以 ‘__’ 开头的变量不会被持久保存

  如果添加的变量与系统的冲突, 将覆盖掉系统变量, 如果想恢复系统变量, 请删除自己的变量. 示例:

  def handle_data(context, data):

  # 执行下面的语句之后, context.portfolio 的整数 1

  context.portfolio = 1

  log.info(context.portfolio)

  # 要恢复系统的变量, 只需要使用下面的语句即可

  del context.portfolio

  # 此时, context.portfolio 将变成账户信息.

  log.info(context.portfolio.portfolio_value)

  复制代码

  我们以后可能会往 context 添加新的变量来支持更多功能, 为了减少不必要的迷惑, 还是建议大家使用 g

  示例

  def handle_data(context, data):

  #获得当前回测相关时间

  year = context.current_dt.year

  month = context.current_dt.month

  day = context.current_dt.day

  hour = context.current_dt.hour

  minute = context.current_dt.minute

  second = context.current_dt.second

  #得到"年-月-日"格式

  date = context.current_dt.strftime("%Y-%m-%d")

  #得到周几

  weekday = context.current_dt.isoweekday()

  # 获取账户的持仓价值

  positions_value = context.portfolio.positions_value

  # 获取仓位subportfolios[0]的可用资金

  available_cash = context.subportfolios[0].available_cash

  # 获取subportfolios[0]中多头仓位的security的持仓成本

  hold_cost = context.subportfolios[0].long_positions[security].hold_cost

  复制代码

  5 买入卖出

  # 用法:order_value(要买入股票股票的股票代码,要多少钱去买)

  order_value(g.security, cash)# 用当前所有资金买入股票

  # 用法:order_target(要买卖股票的股票代码,目标持仓金额)

  order_target(g.security, 0)# 将股票仓位调整到0,即全卖出

  复制代码

  >答疑与延伸:

  >- 为什么没有指定交易价格?此策略是按天回测进行的且使用的较为简单的市价单下单方法,交易价格为开盘价(加上滑点)

  >- 无法交易的情况?涨跌停,停牌,T+1制度等无法交易的情况,系统会自动使下单不成交并在日志中发出警告。

  滑点

  在实战交易中,往往最终成交价和预期价格有一定偏差,因此我们加入了滑点模式来帮助您更好地模拟真实市场的表现。

  您可以通过set_slippage来设置回测具体的滑点参数。

  - 下单方法有哪些?

  6 策略代码写完,进行回测

  把买入卖出的代码写好,策略就写完了,如下

  def initialize(context):#初始化

  g.security = '002043.XSHE'# 股票名:兔宝宝

  def handle_data(context, data):#每日循环

  last_price = data[g.security].close# 取得最近日收盘价

  # 取得过去二十天的平均价格

  average_price = data[g.security].mavg(20, 'close')

  cash = context.portfolio.cash# 取得当前的现金

  # 如果昨日收盘价高出二十日平均价, 则买入,否则卖出。

  if last_price > average_price:

  order_value(g.security, cash)# 用当前所有资金买入股票

  elif last_price < average_price:

  order_target(g.security, 0)# 将股票仓位调整到0,即全卖出

  复制代码

全国统一服务热线

400-1799-757

网上开户服务时间

周一至周五 08:00-17:00