用stockquant实现股票量化交易


视频地址 https://www.bilibili.com/video/BV1zK411u7uG?p=2&spm_id_from=pageDriver

1、StockQuant的安装


1)macbook的安装

pip install stockquant-simple  # 依赖库比较少,图形界面取消
conda install -c conda-forge ta-lib  # 安装ta-lib库

2)windows下的安装

2.1 python的安装

用anaconda安装,到清华镜像站,选择anaconda3-5.3.1-windows-x86_64.exe 2018-11-20 . python版本3.7.0。安装的时候把环境变量还有默认解释器的设置需要打勾的地方都选中。

pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple  # 永久配置国内镜像源。执行后,会写入pip.ini文件,把清华pipy网站写入,下次pip会自动从中下载。
pip install stockquant
pip install Ta-Lib  # 失败,解决见下面下载安装文件后安装

python各种版本包下载地址:https://www.lfd.uci.edu/~gohlke/pythonlibs/,到这里下载cp37对应的Ta-Lib 64位版本,文件名后缀是whl。下载好后,进入文件所在的目录,按住shift,点击鼠标右键打开powershell,pip install 文件名.whl (可以输入文件名第一个字母后,按tab键补全)

2.2 测试

进入python命令行

from stockquant.quant import *  # 如果遇到和pandas,numpy有关的报错信息 pip install -U pandas

如果没有错误就ok了。

3) 新建项目(pycharm下)

新建工程,解释器不选择虚拟环境,选已经存在的解释器,选项中system interpreterr(中文是系统解释器)

然后,新建文件config.json,内容如下:

{
  "LOG": {
    "level": "debug",
    "handler": "stream"
  },
  "DINGTALK": "your dingding token",  #钉钉token
  "TUSHARE": "your tushare token",
  "SENDMAIL": {
    "from": "5186817@qq.com",
    "password": "your qq email authorization code",  # 邮箱授权码
    "to": "5186817@qq.com",
    "server": "smtp.qq.com",
    "port": 587
  }
}

钉钉token设置步骤:

点击和搜索框同行的加号按钮--发起群聊--培训群--输入群名称--点击创建,进入群界面,点击群设置--智能群助手--添加机器人--选择自定义--点击添加--输入机器人名字--安全设置选择自定义关键词(比如设置关键词为“交易,”发的消息必须包含“交易”才能推送),点击完成,下一窗口出现webhook,复制后填入配置文件中,点击完成。

tushare设置:

登陆tushare网站,移动鼠标到头像,点击个人主页,再点击接口token标签,复制填入即可。

新建python文件,比如main.py

from stockquant.quant import *

config.loads('config.json')

2、获取股票数据

stockquant说明文档参加 https://gitee.com/usomien/StockQuant

1)获取实时行情信息

tick = Market.tick(symbol='sh600519')  # "sh601003",或者"sz002307",前者是沪市,后者是深市。返回的tick对象有很多属性,开盘价,收盘价,买一到买五等等。
print(tick.symbol, tick.last, tick.timestamp) # 股票代码,当前价格,时间戳

2)获取k线数据

kline = Market.kline(symbol, timeframe, adj=None, start_date=None, end_date=None)
# timeframe:k线周期,区分大小写,支持:"5m", "15m", "30m", "1h", "1d", "1w", "1M,分别为5、15、30分钟、1小时、1天、1周、1月
#adj:复权类型,默认是"3"不复权;前复权:"2";后复权:"1"。已支持分钟线、日线、周线、月线前后复权。 BaoStock提供的是涨跌幅复权算法复权因子
# start_date:开始日期(包含),格式“YYYY-MM-DD”,为空时取2015-01-01;
# end_date:结束日期(包含),格式“YYYY-MM-DD”,为空时取最近一个交易日;
# return:返回一个列表,其中每一条k线数据包含在一个列表中,不同周期返回列表不同。比如日线返回 :日期,开,高,低,收,昨收,成交量,成交额,“3”,换手,“1”,涨跌幅,“0”。
kline = Market.kline('sh600519', '5m')  # 例子

3)上证综指与深圳成指

Market.shanghai_component_index()  # 返回字典,包括当前点数,涨跌点数,涨跌率,成交数量,成交金额
Market.shenzhen_component_index()

4) 其它

Market.stocks_list()  # 股票列表,获取基础信息数据,包括股票代码、名称、区域、行业、上市日期等
Market.today_is_open()  # 今日是否开盘,返回bool值
Market.new_stock()  # 获取新股上市列表

3、交易实时提醒

content = "### 交易:孙文平\n\n"\
    "> **股票名称** {symbol}\n\n"\
    "> **当前价格** {last}\n\n".format(
    symbol=tick.symbol,
    last=tick.last
)
DingTalk.markdown(content)  # markdown格式
DingTalk.text(“交易:。。。。”)
sendmail("交易提醒:sh600519的价格已达到2000美元!")  # 微信邮件提醒:我-设置-通用-辅助功能-qq邮箱提醒-启用

提醒可以加入到循环当中,延时是必须的,防止提醒太频繁,循环体中可以加入判断,比如今日是否开盘,股价大于多少提醒等。

程序运行可以用命令行,python 程序名.py。

4、计算指标并批量筛选股票

from stockquant.quant import *

config.loads('config.json')
stocks_pool = []                    # 筛选到的股票加入此列表,股票池
stocks_list = Market.stocks_list()  # 获取所有股票信息,返回DataFrame格式,ts_code列格式为,000001.SZ,600001.SZ
for item in stocks_list['ts_code']:
    if str(item).endswith('.SH'):
        name = 'sh' + str(item).replace('.SH', '')  # 000001.SH, 转换为sh000001格式
    else:
        name = 'sz' + str(item).replace('.SZ', '')
    kline = Market.kline(name, '1d')
    if len(kline) < 30:
        continue
    ma20 = MA(20, kline)
    ma30 = MA(30, kline)
    if ma20[-1] > ma30[-1] and ma20[-2] <= ma30[-2]:  # 收盘后下载的k线包括当天的,[-1]是当天的ma值。
        stocks_pool.append(name)
print(stocks_pool)

5、编写一个策略自动推送信号

from stockquant.quant import *

class Strategy:

    def __init__(self):
        config.loads('config.json')
        self.amount, self.price = 0, 0  # 持股数量,持股价格 都设置为o

    def begin(self):
        if get_localtime()[11: 16] == '16:00' and Market.today_is_open():  # 如果今天开盘,并且是下午4点
            kline = Market.kline('sh600519', '1d')
            ma20, ma30 = MA(20, kline), MA(30, kline)
            cross_over = ma20[-1] > ma30[-1] and ma20[-2] <= ma30[-2]  # 金叉信号
            cross_down = ma20[-1] < ma30[-1] and ma20[-2] >= ma30[-2]  # 死叉信号
            if cross_over:
                DingTalk.text("交易提醒: sh600519 金叉信号出现,请买入!")
                self.amount, self.price = 1, float(kline[-1][4])  # 第五个是收盘价
            elif cross_down and self.amount != 0:
                DingTalk.text("交易提醒: sh600519 死叉信号出现,请卖出!")
                self.amount, self.price = 0, 0
        if not not_open_time() and self.amount > 0:
            tick = Market.tick('sh600519')
            last = tick.last  # 在交易时间段内获取一下当前的最新价格
            if last <= self.price * 0.9:  # 止损价10%
                DingTalk.text("交易提醒: sh600519 当前价格已经达到止损位,请立即止损!")
                self.amount, self.price = 0, 0

if __name__ == '__main__':
    strategy = Strategy()
    while True:
        try:                    # 为防止出错后停止运行
            strategy.begin()
            sleep(3)            # 延时3秒
        except Exception as e:
            logger.error(e)

6、双均线策略回测

from stockquant.quant import *

class Strategy:

    def __init__(self):
        config.loads('config.json')
        self.asset = 10000                      # 总资金
        self.backtest = BackTest()               # 初始化回测模块
        
        # data = Market.kline(symbol='sh600519', timeframe='1d')     # 从网站获得k线数据
        res = pd.read_csv('600519.csv')  # 从文件读取数据,文件每行格式的前几项,日期,开,高,低,收,昨收,成交量,成交额
        data = res.values.tolist()
        
        self.kline = []                         # 存放递增k线数据的列表
        for k in data:
            self.kline.append(k)
            self.backtest.initialize(self.kline, data)  # k线入口函数,传入递增k线和原始k线
            self.begin()
        plot_asset()    # 回测完成绘制资金曲线图(出错的话,到pycharm设置--工具--python scientific--在工具窗口中显示绘图对勾去掉)

    def begin(self):                            # 策略主体函数
        if CurrentBar(self.kline) < 30:
            return
        # if self.backtest.timestamp < '2015-01-01' or self.backtest.timestamp > '2018-01-01':
        #     return		# 回测自定义时间段
        ma20, ma30 = MA(20, self.kline), MA(30, self.kline)
        cross_over = ma20[-1] > ma30[-1] and ma20[-2] <= ma30[-2]  # 金叉信号
        cross_down = ma20[-1] < ma30[-1] and ma20[-2] >= ma30[-2]  # 死叉信号

        if cross_over and self.backtest.long_quantity == 0:  # 金叉且未持股
            self.backtest.buy(                              # 模拟买入
                price=self.backtest.close,                  # 买入价格为此根k线收盘价
                amount=self.asset/self.backtest.close,      # 买入数量
                long_quantity=self.asset/self.backtest.close,   # 当前持多数量
                long_avg_price=self.backtest.close,             # 当前持多价格
                profit=0,                                       # 利润
                asset=self.asset                                # 总资金
            )
        elif cross_down and self.backtest.long_quantity != 0:    # 如果死叉且当前持股
            profit = (self.backtest.close-self.backtest.long_avg_price) * self.backtest.long_quantity
            self.asset += profit                                # 计算变化后的总资金,
            self.backtest.sell(                                 # 模拟卖出
                price=self.backtest.close,
                amount=self.backtest.long_quantity,
                long_quantity=0,
                long_avg_price=0,
                profit=profit,
                asset=self.asset
            )
        if self.backtest.long_quantity > 0 and self.backtest.low <= self.backtest.long_avg_price * 0.9: # 止损
            profit = (self.backtest.low-self.backtest.long_avg_price) * self.backtest.long_quantity
            self.asset += profit
            self.backtest.sell(
                price=self.backtest.long_avg_price * 0.9,
                amount=self.backtest.long_quantity,
                long_quantity=0,
                long_avg_price=0,
                profit=profit,
                asset=self.asset
            )

if __name__ == '__main__':
    strategy = Strategy()

问题:回测结果是否出错,最大回撤0%?(源码中回撤是与初始资金进行比较,而不是与收入的高点比较)可以看程序运行时生成的回撤.csv文件分析。

7、历史k线及策略信号可视化

plot_signal(data, self.buy_signal, self.sell_signal, self.ma20, self.ma30)
# 模块中没有此方法,可能是收费模块里面才有

8、ETF网格策略设计

确定一个基准线,价格会围绕它上下波动。在基准线的价格上买入50%仓位,在价格高于基准线价格时卖出,低于它的时候买入。基准线上下可以设置几个格子,比如资金少的,可以设置两格,即高于基准线+每格表示的数量,卖出使仓位25%,高于基准线+2倍格子,卖出使仓位0%。这个策略非常适合震荡或者通道形态。

from stockquant.quant import *

class EtfGridStrategy:

    def __init__(self):
        config.loads('config.json')
        self._symbol = 'sh512980'
        self._asset = 10000             # 总资金
        self._amount = 0                # 初始仓位
        self.first_buy_day = None       #买入的日期,买入的日期是不能卖出的
        logger.info("EtfGridStrategy start to run ... Current price is {}",format(Market.tick(self._symbol).last))

        while True:
            try:
                self.do_action()
                sleep(5)
            except:
                logger.error()

    def do_action(self):

        if not not_open_time() and Market.today_is_open():

            price = Market.tick(self._symbol).last
            logger.info("Current price is {}".format(price))

            if self._amount == 0 and 3450 <= price <= 3550:     # 基准线是3500
                first_buy_amount = round(self._asset / 2 / price / 100)
                DingTalk.text("交易提醒:请买入底仓!买入价格:{} 买入数量:{}".format(price, first_buy_amount))
                self._amount = first_buy_amount
                self._asset = first_buy_amount * price
                self.first_buy_day = get_date()

            if self._amount > 0:

                if price <= 3250:
                    buy_amount = round(self._asset / 2 / price / 100)
                    DingTalk.text("交易提醒:请增持仓位!买入价格:{} 买入数量:{}".format(price, buy_amount))
                    self._amount += buy_amount
                    self._asset -= buy_amount * price

                elif price <= 3000:
                    buy_amount = round(self._asset / price / 100)
                    DingTalk.text("交易提醒:请增持仓位!买入价格:{} 买入数量:{}".format(price, buy_amount))
                    self._amount += buy_amount
                    self._asset -= buy_amount * price

                if get_date() != self.first_buy_day:

                    if price >= 3750:
                        sell_amount = round(self._amount / 2)
                        DingTalk.text("交易提醒:请减持仓位!卖出价格:{} 卖出数量:{}".format(price, sell_amount))
                        self._amount -= sell_amount
                        self._asset += sell_amount * price

                    elif price >= 4000:
                        sell_amount = round(self._amount)
                        DingTalk.text("交易提醒:请减持仓位!卖出价格:{} 卖出数量:{}".format(price, sell_amount))
                        self._amount -= sell_amount
                        self._asset += sell_amount * price

if __name__ == '__main__':
    EtfGridStrategy()

9、自动化交易

模块easytrader 参见 https://gitee.com/alexi126/easytrader/blob/master/README.md

https://easytrader.readthedocs.io/zh/master/

下载华泰证券网上交易系统专业版2(官网只有繁体版,到网上搜索)。默认安装到C:\htzqzyb2,文件夹中有xiadan.exe。

交易界面设置:进入交易界面,点击 系统-系统设置-交易设置-默认买入卖出价格都设置为空-卖出后查询股票设置为是-界面设置-下单窗口总在最前面设置为是-界面不操作超时时间设为 0, 客户端不能最小化也不能处于精简模式。

import easytrader     

user = easytrader.use('ht_client')   # 设置交易客户端类型,华泰证券网上交易系统专业版2,不用手动提前登录交易系统
user.prepare(user='0106********', password='166***', comm_password='12****')  # 启动并连接客户端
print(user.balance)  # 获取资金情况
print(user.position)  # 获取持仓
user.buy('162411', price=0.55, amount=100)  # 价格0.55,数量100股
user.sell('162411', price=0.55, amount=100)
print(user.today_trades)  # 查询当日成交
print(user.today_entrusts)  # 查询当日委托

用stockquant 模块实现交易(内部封装了easytrader模块)(需要先登录运行交易系统)

from stockquant.quant import *

class Strategy:

    def __init__(self):
        self.trade = Trade(config_file='config.json', symbol='sh512980', path=r'C:\htzqzyb2\xiadan.exe')
        self.do_action()  

    def do_action(self):
        price = Market.tick('sh512980').ask1_price  # 可以把买入卖出语句加入到“5、编写一个策略”中,实盘交易
        success, error = self.trade.buy(price, 100)
        if error:
            DingTalk.text("交易提醒:失败{}".format(error))
            return
        logger.info("success:{}".format(success))

if __name__ == '__main__':
    Strategy()