【干货分享】从零开始学量化:01因子选股策略

  1. 策略原理及伪代码:
    1.1 策略原理
    始终买入PE最小的1000只股票中市值最小的5只;
    无持仓时直接按照股票池等权买入;
    有持仓时,不在股票池中的股票卖出。

    1.2 策略伪代码
    策略逻辑用伪代码描述如下:

  • -初始化部分逻辑:
    功能说明:生成当天的持仓代码列表,并获取昨天的日线数据,方便计算仓位
    操作步骤:

    1. 获取当天可交易的沪深股票A股代码
    2. 获取指数’SHSE.000985’的所有成份股
    3. 取两者交集,生成股票代码列表
    4. 取出上述代码列表中的PE值
    5. 基于PE值过滤,生成当天的持仓代码列表
      • 剔除PE为负的股票代码
      • 筛选出PE值最小的1000只股票代码,需要先按PE排序
      • 在上述结果中,再挑选出市值最小的5只股票,生成当天的持仓代码列表。
    6. 为了开盘后触发交易,订阅一个指数‘SHSE.000016’的分钟Bar数据
  • -交易部分逻辑:
    功能说明: 因为基于日线数据计算,在开盘收到第一条指数行情Bar数据后,就开始下单交易,完成持仓调整。
    操作步骤:
    1. 查询已有持仓数据
    2. 根据目标持仓对已有持仓进行调整:
    a. 当前没有持仓时,按95%仓位规划,等资金量买入目标持仓中的股票。
    b. 当前有持仓时,检查已有仓位:
    如果已持有股票,不在目标持仓中的,直接卖出。
    如果已持有股票也在目标持仓中,不再重复买。
    基于上述逻辑生成卖出列表和新的买入列表。
    对卖出列表立即执行。
    3. 在卖出列表执行完成,资金回流可用后,执行买入列表。
    - 订阅委托完全成交事件,在每次完全成交事件处理中,从卖出列表中清除相应股票代码
    - 然后检查卖出列表是否已清空,如果是,表明股票已经全部卖出完毕,执行买入列表。 否则,继续等待。

# !/usr/bin/env python
# -*- coding: utf-8 -*-
from gmsdk.api import StrategyBase
'''
请在Strategy中修改个人账号密码和策略ID
'''
class Strategy(StrategyBase):
    def __init__(self, pe_len = 1000, mv_len = 5, *args, **kwargs):
        super(Strategy, self).__init__(*args, **kwargs)
        self.pe_len = pe_len
        self.mv_len = mv_len
        self.buy_dict = {}
        self.sell_dict = {}
        self.is_traded = False

        self.md.subscribe('SHSE.000016.bar.60') # 订阅一个行情,在交易时间触发下单

    def md_init(self):
# region 获取中证全指中当天可交易的股票
        instruments1 = self.get_instruments('SHSE', 1, 1)
        instruments2 = self.get_instruments('SZSE', 1, 1)
        symbol_list1 = set(instrument.symbol for instrument in instruments2 + instruments1 if instrument.symbol[5] not in ['2', '9']) #获取当日可交易的股票,剔除B股
        constituents = self.get_constituents('SHSE.000985')
        symbol_list2 = set(constituent.symbol for constituent in constituents)#获取中证全指成分股(剔除ST、*ST股票,以及上市时间不足3个月等股票后剩余的股票)
        symbol_list = symbol_list1 & symbol_list2
        symbol_list = ','.join(symbol for symbol in symbol_list)
# endregion

# region 选出PE最小的1000只中市值最小的5只
        market_index = self.get_last_market_index(symbol_list)
        data = list(mi for mi in market_index if mi.pe_ratio > 0) # 剔除PE为负的股票
        data = sorted(data, key= lambda mi: mi.pe_ratio)[:self.pe_len] # PE最小的1000只
        data = sorted(data, key= lambda mi: mi.market_value)[:self.mv_len] # 市值最小的5只
# endregion

# region 为了计算仓位,获取昨日dailybar,存入buy_dict
        buy_list = ','.join(d.symbol for d in data)
        dailybars = self.get_last_dailybars(buy_list)
        self.buy_dict = dict((dailybar.sec_id, dailybar) for dailybar in dailybars)
        # endregion

    # 收到第一根Bar后交易
    def on_bar(self, bar):
        print(bar.strendtime[:-6].replace('T', ' '))
        if self.is_traded:
            return
        self.is_traded = True
        self.md_init()

# region 没有持仓时直接open_long
        print(self.buy_dict.keys())
        positions = self.get_positions()
        if len(positions) == 0:
            cash = self.get_cash()
            for b in self.buy_dict.values():
                order = self.open_long(b.exchange, b.sec_id, 0, int(cash.available * 0.95 / len(self.buy_dict) / b.close / 100) * 100)
            return
# endregion

# region 有持仓时结合持仓获取buy_dict,sell_dict
        for p in positions:
            if p.sec_id in self.buy_dict:
                self.buy_dict.pop(p.sec_id)
            else:
                self.sell_dict[p.sec_id] = p
        # endregion

        for p in self.sell_dict.values(): # 先卖出,卖盘成交时再买入,若资金足够也可以直接买入
            self.close_long(p.exchange, p.sec_id, 0, p.volume)

    def on_order_filled(self,order):
        if order.sec_id in self.sell_dict and order.strategy_id == self.strategy_id:
            self.sell_dict.pop(order.sec_id)
            if len(self.sell_dict) == 0:#由于资金每次都开满,等卖盘全部成交资金回流时再买入
                cash = self.get_cash()
                for bar in self.buy_dict.values():
                    self.open_long(bar.exchange, bar.sec_id, 0, int(cash.available * 0.95 / len(self.buy_dict) / bar.close / 100) * 100)

if __name__ == '__main__':
    my_strategy = Strategy(
        username='username', # 请修改账号
        password='password', # 请修改密码
        strategy_id='strategy_id', # 请修改策略ID
        mode=3,
        td_addr='localhost:8001')
        ret = my_strategy.run()
         print('exit code: ', ret)
  1. Python相关函数
    3.1 Python标准函数:

功能 函数原型 参数 返回值
参数名 含义
join 连接字符串数组。将字符串、元组、列表中的元素以指定的字符(分隔符)连接生成一个新的字符串 ‘sep’.join(seq) sep 分隔符。可以为空 返回一个以分隔符sep连接各个元素后生成的字符串
seq 要连接的元素序列、字符串、元组、字典
list 将元组转换为列表。
注:元组与列表是非常类似的,区别在于元组的元素值不能修改,元组是放在括号中,列表是放于方括号中。 list( seq ) seq 要转换为列表的元祖 返回列表
sorted 排序 sorted(iterable, cmp=None, key=None, reverse=False) iterable iteralbe指的是能够一次返回它的一个成员的对象。iterable主要包括3类: 第一类是所有的序列类型,比如list(列表)、str(字符串)、tuple(元组)。 第二类是一些非序列类型,比如dict(字典)、file(文件)。 第三类是你定义的任何包含__iter__()或__getitem__()方法的类的对象。
cmp cmp: 指定一个定制的比较函数,这个函数接收两个参数(iterable的元素),如果第一个参数小于第二个参数,返回一个负数;如果第一个参数等于第二个参数,返回零;如果第一个参数大于第二个参数,返回一个正数。默认值为None。
key key:指定一个接收一个参数的函数,这个函数用于从每个元素中提取一个用于比较的关键字。默认值为None。
reverse reverse:是一个布尔值。如果设置为True,列表元素将被降序排列,默认为升序排列。 通常来说,key和reverse比一个等价的cmp函数处理速度要快。这是因为对于每个列表元素,cmp都会被调用多次,而key和reverse只被调用一次。
3.2 掘金接口函数:

功能 函数原型 参数 返回值
参数名 类型 说明
md_init 初始化行情接口 md.init(“your user name”, “your password”) your user name string 用户名
your password string 密码
get_instruments 提取交易代码。策略类和行情服务类都提供该接口。 get_instruments(exchange, sec_type, is_active) exchange string 交易所代码 Instrument对象列表
sec_type int 代码类型:1.股票,2.基金,3.指数,4.期货,5.ETF
is_active int 当天是否交易:1.是,0.否
get_constituents 提取指数的成分股代码。策略类和行情服务类都提供该接口。 get_constituents(index_symbol) index_symbol string 指数代码 constituent对象列表
get_last_market_index 按时间周期提MarketIndex,按时间升序排列。策略类和行情服务类都提供该接口。 get_last_market_index(symbol_list) symbol_list string 多个品种代码列表,如SHSE.600000,SZSE.000001 MarketIndex对象列表
get_last_dailybars 提取最新1条DailyBar数据,支持单个代码提取或多个代码组合提取。策略类和行情服务类都提供该接口。 get_last_dailybars(symbol_list) symbol string 证券代码, 带交易所代码以确保唯一,如SHSE.600000,同时支持多只代码 DailyBar列表
get_positions 查询当前策略指定symbol(由交易所代码和证券ID组成)和买卖方向的持仓信息。策略类和交易服务类都提供该接口。 get_position(exchange, sec_id, side); exchange string 交易所代码 Position对象,持仓信息
sec_id string 证券代码
side int 买卖方向
get_cash 查询当前策略的资金信息,策略类和交易服务类都提供该接口。 get_cash() 无 Cash对象,当前策略的资金信息
on_bar 响应Bar事件,收到Bar数据后本函数被调用。 on_bar(bar) bar bar bar数据 无
open_long 异步开多仓,以参数指定的symbol、价和量下单。如果价格为0,为市价单,否则为限价单。策略类和交易服务类都提供该接口 open_long(exchange, sec_id, price, volume) exchange string 交易所代码, 如上交所SHSE 委托下单生成的Order对象
sec_id string 证券代码,如浦发银行600000
price float 委托价,如果price=0,为市价单,否则为限价单
volume float 委托量
close_long 异步平多仓接口,以参数指定的exchange, 证券代码sec_id, 价和量下单。如果价格为0,为市价单,否则为限价单。策略类和交易服务类都提供该接口。 close_long(exchange, sec_id, price, volume) exchange string 交易所代码, 如上交所SHSE 委托下单生成的Order对象
sec_id string 证券代码,如浦发银行600000
price float 委托价,如果price=0,为市价单,否则为限价单
volume float 平仓量
to_dict 类型转换函数,将GMSDK内置类型对象转换为dict对象; 使用前从gmsdk引入,即from gmsdk import to_dict。 to_dict(obj) obj object GMSDK内置类型对象 dict对象
4. 金融术语:
市盈率(PE):某种股票每股市价与每股盈利(EPS)的比率。而EPS方面,若按已公布的上年度EPS计算,称为历史市盈率(historical P/E);计算预估市盈率所用的EPS预估值,一般采用市场平均预估(consensus estimates),即追踪公司业绩的机构收集多位分析师的预测所得到的预估平均值或中值。市场广泛谈及市盈率通常指的是静态市盈率,通常用来作为比较不同价格的股票是否被高估或者低估的指标。

市值:市值是指一家上市公司的发行股份按市场价格计算出来的股票总价值,其计算方法为每股股票的市场价格乘以发行总股数。整个股市上所有上市公司的市值总和,即为股票总市值。

2 Likes

这是在哪个量化平台上做的啊?掘金?

是的,掘金平台。

是的掘金平台,我目前就在用这个平台~真的还是很方便的

没人回复自己顶顶吧

没关系,你尽管继续写新的帖子,也不要只做这一个系列嘛。

后续还会有新的干货~需要大家一起发掘呀

66666666666666666666666666666666666

哈哈哈哈~

谢谢分享,正在入门找资料中,学习了。

一起发掘。