diff --git a/.gitignore b/.gitignore index 0c5cb077..aa69fb9b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ Release/ *.exp *.pdb *.cd +*.o +*.out # Python编译文件 *.pyc @@ -28,8 +30,6 @@ Release/ # 本地持久化文件 *.vn - - # 其他文件 *.dump *.vssettings @@ -41,15 +41,23 @@ Release/ *.temp *.vt *.log - -vn.ctp/build/* -vn.lts/build/* -.idea -.vscode -.gitignore - -vn.trader/ctaAlgo/data/* -vn.trader/build/* -vn.trader/dist/* *.bak +# 编译临时文件夹 +build/ + +# CMake临时文件 +CMakeCache.txt +CMakeFiles/ +build/cmake_install.cmake +build/Makefile + +# 目录 +.idea +.vscode +.python-version +.gitignore + + + + diff --git a/.travis.yml b/.travis.yml index 2c4cb042..1b6f9c53 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,9 +4,10 @@ cache: pip python: - 2.7 - 3.6 -matrix: - allow_failures: - - python: 3.6 +branches: + only: + - master + - dev install: - pip install -r requirements.txt - pip install flake8 # pytest # add another testing frameworks later diff --git a/README-en.md b/README-en.md index fdd3993f..d1b8c184 100644 --- a/README-en.md +++ b/README-en.md @@ -6,92 +6,101 @@ The vn.py project is an open-source quantitative trading framework that is developed by traders, for traders. The project is mainly written in Python and uses C++ for low-layer and performance sensitive infrastructure. -Using the vn.py project, institutional investors and professional traders, such as hedge funds, prop trading firms and investment banks, can easily develop complex trading strategies with the Event Engine Strategy Module, and automatically route their orders to the most desired destinations, including equity, commodity, forex and many other financial markets. - - -### Project Structure - -1. A large number of Broker and Market Data APIs, all in Python (vn.api): - - **International Financial Markets** - - - Interactive Brokers(vn.ib) - - - OANDA(vn.oanda) - - - Shanghai Zhida Futures(vn.shzd) - - **Chinese Futures Market** - - - CTP(vn.ctp) - - - Femas(vn.femas) - - - Kingstar Option(vn.ksotp) - - - XSpeed(vn.xspeed) - - - **Chinese Equity Market** - - - LTS(vn.lts) - - - QDP(vn.qdp) - - - CSHSHLP(vn.cshshlp) - - **Chinese Precious Metal Market** - - - SGIT(vn.sgit) - - - Kingstar Gold(vn.ksgold) - - - **Cryptocurrency Market** - - - OKCOIN(vn.okcoin) - - - Huobi(vn.huobi) - - - Lhang(vn.lhang) - - **Market Data** - - - Datayes(vn.datayes) - -2. Simple but powerful event engine module (vn.event), which can be used for developing complex quantitative trading strategies - -3. RPC framework (vn.rpc) which also supports pushing data from server to client, aimed at implementing distributed trading systems. - -4. Ready to use trading platform (vn.trader), which has integrated all the trading APIs in vn.api, and provides easy to use strategy engines for developing different types of quantitative strategies and trading algorithms. - -5. Tutorials about how to use vn.py to solve real world trading issues. - -6. [Official Website](http://vnpy.org) and [Github Repository](http://www.github.com/vnpy/vnpy) - +Using the vn.py project, institutional investors and professional traders, such as hedge funds, prop trading firms and investment banks, can easily develop complex trading strategies with the Event Engine Strategy Module, and automatically route their orders to the most desired destinations, including equity, commodity, forex, cryptocurrency and many other financial markets. --- ### Quick Start -1. Prepare a computer with Windows 7 (64-bit) installed. +1. Prepare a computer with Windows 7/8/10/Server 2008 (64-bit) installed. -2. Install [Anaconda](http://www.continuum.io/downloads), please make sure you download **Anaconda 4.0.0 Python 2.7 (32-bit)**. +2. Install [Anaconda 5.2.0](http://www.continuum.io/downloads), please make sure you download **Python 2.7 (32-bit)**. -3. Install [MongoDB](https://www.mongodb.org/downloads#production) +3. Install [MongoDB](https://www.mongodb.org/downloads#production), please register MongoDB as Windows Service. -4. Install pymongo, just run "pip install pymongo" in cmd. +4. Install [Visual C++ Redistributable Packages for VS2013 (32-bit)](https://www.microsoft.com/en-gb/download/details.aspx?id=40784). -5. Register MongoDB as Windows Service and start it. +5. Run **install.bat** to install vn.py and all dependencies. -6. Install [Visual C++ Redistributable Packages for VS2013 (32-bit)](https://www.microsoft.com/en-gb/download/details.aspx?id=40784). +6. Go to folder **examples/CryptoTrader/** and edit those **ABC_connect.json** files with your exchange config. Taking BitMEX_connect.json as an example: + * apiKey: the API Key of your account provided by BitMEX + * secretKey: the Secret Key of your account provide by BitMEX + * sessionCount: number of sessions and threads you would like to use for RESTFul request + * symbols: symbols of contract you would like to receive data update from Websocket API -7. Click the "Download ZIP" button on this page to download the project source code, assume you unzip to C:\vnpy. +7. Change the "language" setting in **VT_setting.json** to "english" (otherwise you will see the GUI in Chinese). -8. Install the IB TWS software and configure it to allow trading API connection. +8. Start CryptoTrader by running "python run.py", connect to BitMEX and then you are ready to trade! -9. Use sublime text or any other text editors you like, to change related details in C:\vnpy\vn.trader\gateway\ibGateway\IB_connect.json to your data. +### Project Structure -10. Start IB TWS software and run C:\vnpy\vn.trader\vtMain.py, enjoy trading! +1. A large number of Broker and Market Data APIs, all in Python (vnpy.api): + + **Cryptocurrency Market** + + - BitMEX (bitmex) + + - OKEX(okex) + + - Huobi Pro(huobi) + + - Binance(binance) + + - Bitfinex (bitfinex) + + - Coinbase Pro (coinbase) + + - FCoin (fcoin) + + - BigOne (bigone) + + - LBank(lbank) + + - CCXT (ccxt) + + **International Financial Markets** + + - Interactive Brokers(ib) + + - Shanghai Zhida Futures(shzd) + + - Futu Securities (futu) + + **Chinese Futures Market** + + - CTP(ctp) + + - Femas(femas) + + - Kingstar Option(ksotp) + + - XSpeed(xspeed) + + **Chinese Equity Market** + + - LTS(lts) + + - QDP(qdp) + + - CSHSHLP(cshshlp) + + - XSpeed Securities (sec) + + **Chinese Precious Metal Market** + + - SGIT(sgit) + + - Kingstar Gold(ksgold) + + +2. Simple but powerful event engine module (vnpy.event), which can be used for developing complex quantitative trading strategies + +3. RPC framework (vnpy.rpc) which also supports pushing data from server to client, aimed at implementing distributed trading systems. + +4. Ready to use trading platform (vnpy.trader), which has integrated all the trading APIs in vnpy.api, and provides easy to use strategy engines for developing different types of quantitative strategies and trading algorithms. + +5. Examples about how to use vn.py framework for solving real world trading issues (vnpy.examples). + +6. [Official Forum](http://www.vnpie.org) and [Github Repository](http://www.github.com/vnpy/vnpy) --- ### Contact diff --git a/README.md b/README.md index 85cb7be9..e57e2566 100644 --- a/README.md +++ b/README.md @@ -9,57 +9,91 @@ vn.py是基于Python的开源量化交易程序开发框架,起源于国内私募的自主量化交易系统。2015年初项目启动时只是单纯的交易API接口的Python封装。随着业内关注度的上升和社区不断的贡献,目前已经成长为一套全功能的交易程序开发框架,用户群体也日渐多样化,包括私募基金、券商自营和资管、期货资管和子公司、高校研究机构和专业个人投资者等。 +2018年中启动代号为vn.crypto的数字货币量化交易系统开发计划,目前已完成第一阶段的开发,提供针对数字货币交易所原生的REST/WebSocket API的高性能的Python接口封装设计、以及适合7x24小时长时间交易需求的算法交易AlgoTrading模块、面向币圈交易的前端应用示例CryptoTrader等,后续会进一步完善打造功能全面的数字货币量化交易平台。 + --- ### 项目结构 -1. 丰富的Python交易API接口(vnpy.api),基本覆盖了国内外所有常规交易品种(股票、期货、期权、外汇、外盘、比特币),具体包括: +1. 丰富的Python交易API接口(vnpy.api),基本覆盖了国内外所有常规交易品种(股票、期货、期权、外汇、外盘、数字货币),具体包括: - - CTP(ctp) + - 传统金融 - - 飞马(femas) + - CTP(ctp) - - 中泰证券XTP(xtp) + - 飞马(femas) - - 中信证券期权(cshshlp) + - 中泰证券XTP(xtp) - - 金仕达黄金(ksgold) + - 中信证券期权(cshshlp) - - 金仕达期权(ksotp) + - 金仕达黄金(ksgold) - - 飞鼠(sgit) + - 金仕达期权(ksotp) - - 飞创(xspeed) + - 飞鼠(sgit) - - QDP(qdp) + - 飞创(xspeed) - - 上海直达期货(shzd) + - 飞创证券(sec) - - Interactive Brokers(ib) + - QDP(qdp) - - OANDA(oanda) + - 上海直达期货(shzd) - - 福汇(fxcm) + - Interactive Brokers(ib) - - OKCOIN(okcoin) + - 福汇(fxcm) - - 火币(huobi) - - LBank(lbank) + - 数字货币 + + - OKEX(okex) + + - 火币(huobi) + + - 币安(binance) + + - BitMEX (bitmex) + + - Bitfinex (bitfinex) + + - Coinbase Pro (coinbase) + + - FCoin (fcoin) + + - BigOne (bigone) + + - LBank(lbank) + + - CCXT (ccxt) + 2. 简洁易用的事件驱动引擎(vnpy.event),作为事件驱动型交易程序的核心 3. 支持服务器端数据推送的RPC框架(vnpy.rpc),用于实现多进程分布式架构的交易系统 -4. 开箱即用的实盘交易平台框架(vnpy.trader),整合了多种交易接口,并针对具体策略算法和功能开发提供了简洁易用的API,用于快速构建交易员所需的量化交易程序,应用举例: +4. 开箱即用的量化交易平台(vnpy.trader),整合了多种交易接口,并针对具体策略算法和功能开发提供了简洁易用的API,用于快速构建交易员所需的量化交易程序,应用举例: * 同时登录多个交易接口,在一套界面上监控多种市场的行情和多种资产账户的资金、持仓、委托、成交情况 * 支持跨市场套利(CTP期货和XTP证券)、境内外套利(CTP期货和IB外盘)、多市场数据整合实时预测走势(CTP的股指期货数据、IB的外盘A50数据、Wind的行业指数数据)等策略应用 - * CTA策略引擎模块,在保持易用性的同时,允许用户针对CTA类策略运行过程中委托的报撤行为进行细粒度控制(降低交易滑点、实现高频策略) + * CtaStrategy,CTA策略引擎模块,在保持易用性的同时,允许用户针对CTA类策略运行过程中委托的报撤行为进行细粒度控制(降低交易滑点、实现高频策略) - * 实盘行情记录,支持Tick和K线数据的落地,用于策略开发回测以及实盘运行初始化 + * SpreadTrading,价差交易模块,根据用户的配置自动实现价差组合的深度行情以及持仓变化计算,同时内置的交易算法SniperAlgo可以满足大部分到价成交策略的需求,用户也可以基于AlgoTemplate开发更复杂的价差算法 + + * OptionMaster,期权交易模块,强大的期权投资组合管理功能,结合基于Cython开发的高效期权定价模型,支持毫秒级别的整体希腊值持仓风险计算,用户可以基于期权交易引擎OmEngine快速开发各类复杂期权交易应用 + + * AlgoTrading,算法交易模块,提供多种常用的智能交易算法:TWAP、Sniper、BestLimit、Iceberg、Arbitrage等等,支持数据库配置保存、CSV文件加载启动以及RPC跨进程算法交易服务 + + * RiskManager,前端风控模块,负责在交易系统将任何交易请求发出到柜台前的一系列标准检查操作,支持用户自定义风控规则的扩展 + + * DataRecorder,实盘行情记录,支持Tick和K线数据的落地,用于策略开发回测以及实盘运行初始化 + + * RpcService,RPC跨进程调用服务,基于MainEngineProxy组件,用户可以如同开发单一进程应用搬开发多进程架构的复杂交易应用 + + * RtdService,EXCEL RTD服务组件,通过pyxll模块提供EXCEL表格系统对VnTrader系统内所有数据的访问和功能调用(未完成) 5. 数据相关的API接口(vnpy.data),用于构建和更新历史行情数据库,目前包括: @@ -73,7 +107,7 @@ vn.py是基于Python的开源量化交易程序开发框架,起源于国内私 8. vn.py项目的Docker镜像(docker),目前尚未完成 -9. [官网](http://vnpy.org)和[知乎专栏](http://zhuanlan.zhihu.com/vn-py),内容包括vn.py项目的开发教程和Python在量化交易领域的应用研究等内容 +9. [官方论坛](http://www.vnpie.com)和[知乎专栏](http://zhuanlan.zhihu.com/vn-py),内容包括vn.py项目的开发教程和Python在量化交易领域的应用研究等内容 10. 官方交流QQ群262656087,管理较严格(定期清除长期潜水的成员) @@ -86,7 +120,7 @@ vn.py是基于Python的开源量化交易程序开发框架,起源于国内私 1. 支持的操作系统:Windows 7/8/10/Server 2008 2. 安装[MongoDB](https://www.mongodb.org/downloads#production),并[将MongoDB配置为系统服务](https://docs.mongodb.com/manual/tutorial/install-mongodb-on-windows/#configure-a-windows-service-for-mongodb-community-edition) -3. 安装[Anaconda](http://www.continuum.io/downloads),**注意必须是Python 2.7 32位版本** +3. 安装[Anaconda 5.2.0](http://www.continuum.io/downloads),**注意必须是Python 2.7 32位版本** 4. 安装[Visual C++ Redistributable Packages for VS2013 x86版本](https://support.microsoft.com/en-us/help/3138367/update-for-visual-c-2013-and-visual-c-redistributable-package) **Ubuntu** @@ -125,14 +159,8 @@ sudo /home/vnpy/anaconda2/bin/conda install -c quantopian ta-lib=0.4.9 ``` # encoding: UTF-8 -# 重载sys模块,设置默认字符串编码方式为utf8 import sys reload(sys) -sys.setdefaultencoding('utf8') - -# 判断操作系统 -import platform -system = platform.system() # vn.trader模块 from vnpy.event import EventEngine @@ -141,18 +169,11 @@ from vnpy.trader.uiQt import createQApp from vnpy.trader.uiMainWindow import MainWindow # 加载底层接口 -from vnpy.trader.gateway import (ctpGateway, oandaGateway, ibGateway, - tkproGateway) - -if system == 'Windows': - from vnpy.trader.gateway import (femasGateway, xspeedGateway, - futuGateway, secGateway) - -if system == 'Linux': - from vnpy.trader.gateway import xtpGateway +from vnpy.trader.gateway import ctpGateway, ibGateway # 加载上层应用 -from vnpy.trader.app import (riskManager, ctaStrategy, spreadTrading) +from vnpy.trader.app import (riskManager, ctaStrategy, + spreadTrading, algoTrading) #---------------------------------------------------------------------- @@ -160,37 +181,27 @@ def main(): """主程序入口""" # 创建Qt应用对象 qApp = createQApp() - + # 创建事件引擎 ee = EventEngine() - + # 创建主引擎 me = MainEngine(ee) - + # 添加交易接口 me.addGateway(ctpGateway) - me.addGateway(tkproGateway) - me.addGateway(oandaGateway) me.addGateway(ibGateway) - - if system == 'Windows': - me.addGateway(femasGateway) - me.addGateway(xspeedGateway) - me.addGateway(secGateway) - me.addGateway(futuGateway) - - if system == 'Linux': - me.addGateway(xtpGateway) - + # 添加上层应用 me.addApp(riskManager) me.addApp(ctaStrategy) me.addApp(spreadTrading) - + me.addApp(algoTrading) + # 创建主窗口 mw = MainWindow(me, ee) mw.showMaximized() - + # 在主线程中启动Qt事件循环 sys.exit(qApp.exec_()) @@ -252,6 +263,7 @@ vn.py使用github托管其源代码,如果希望贡献代码请使用github的 捐赠方式:支付宝3216630132@qq.com(*晓优) + 计划长期维护一份捐赠清单,所以请在留言中注明是项目捐赠以及捐赠人的名字(当然想匿名的用户就随意了)。 diff --git a/beta/api/okex/__init__.py b/beta/api/okex/__init__.py deleted file mode 100644 index a0b1b632..00000000 --- a/beta/api/okex/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# encoding: UTF-8 - -from __future__ import absolute_import -from .vnokex import OkexSpotApi, OkexFuturesApi, CONTRACT_SYMBOL, SPOT_CURRENCY \ No newline at end of file diff --git a/beta/api/okex/test.py b/beta/api/okex/test.py deleted file mode 100644 index 2e20cb82..00000000 --- a/beta/api/okex/test.py +++ /dev/null @@ -1,52 +0,0 @@ -# encoding: UTF-8 - -from __future__ import absolute_import -from .vnokex import * - -# 在OkCoin网站申请这两个Key,分别对应用户名和密码 -apiKey = '你的accessKey' -secretKey = '你的secretKey' - -# 创建API对象 -api = OkexSpotApi() - -api.connect(apiKey, secretKey, True) - -sleep(3) - -#api.login() -api.subscribeSpotTicker("bch_btc") -api.subscribeSpotDepth("bch_btc") -api.subscribeSpotDepth("bch_btc", 5) -api.subscribeSpotDeals("bch_btc") -api.subscribeSpotKlines("bch_btc","30min") - -#api.spotTrade("etc_usdt","sell", "50" , "0.01") -#api.spotCancelOrder("etc_btc","44274138") -#api.spotUserInfo() -#api.spotOrderInfo("etc_btc", 44284731) - -# api = OkexFuturesApi() -# api.connect(apiKey, secretKey, True) - -# sleep(3) -#api.subsribeFutureTicker("btc","this_week") -#api.subscribeFutureKline("btc","this_week", "30min") -#api.subscribeFutureDepth("btc","this_week") -#api.subscribeFutureDepth("btc","this_week", 5) -#api.subscribeFutureTrades("btc","this_week") -#api.subscribeFutureIndex("btc") -#api.subscribeFutureForecast_price("btc") - -#api.login() -#api.futureTrade( "etc_usd", "this_week" ,"1" , 20 , 1 , _match_price = '0' , _lever_rate = '10') # 14245727693 -#api.futureCancelOrder("etc_usd","14245727693" , "this_week") -#api.futureUserInfo() -#api.futureOrderInfo("etc_usd" , "14245727693" , "this_week" , '1', '1' , '10') -# api.subscribeFutureTrades() - -''' -合约账户信息、 持仓信息等,在登录后都会自动推送。。。官方文档这样写的,还没实际验证过 -''' - -input() \ No newline at end of file diff --git a/beta/gateway/okexGateway/OKEX_connect.json b/beta/gateway/okexGateway/OKEX_connect.json deleted file mode 100644 index a0224e4c..00000000 --- a/beta/gateway/okexGateway/OKEX_connect.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "apiKey": "你的apiKey", - "secretKey": "你的secretKey", - "trace": false, - "leverage": 10 -} \ No newline at end of file diff --git a/beta/gateway/okexGateway/okexGateway.py b/beta/gateway/okexGateway/okexGateway.py deleted file mode 100644 index ba8deb48..00000000 --- a/beta/gateway/okexGateway/okexGateway.py +++ /dev/null @@ -1,965 +0,0 @@ -# encoding: UTF-8 - -''' -vnpy.api.okex的gateway接入 - -注意: -1. 目前仅支持USD现货交易 -''' -from __future__ import print_function - -import os -import json -from datetime import datetime -from time import sleep -from copy import copy -from threading import Condition -from Queue import Queue -from threading import Thread -from time import sleep - -from vnpy.api.okex import OkexSpotApi, CONTRACT_SYMBOL, SPOT_CURRENCY -from vnpy.trader.vtGateway import * -from vnpy.trader.vtFunction import getJsonPath - -# 价格类型映射 -# 买卖类型: 限价单(buy/sell) 市价单(buy_market/sell_market) -priceTypeMap = {} -priceTypeMap['buy'] = (DIRECTION_LONG, PRICETYPE_LIMITPRICE) -priceTypeMap['buy_market'] = (DIRECTION_LONG, PRICETYPE_MARKETPRICE) -priceTypeMap['sell'] = (DIRECTION_SHORT, PRICETYPE_LIMITPRICE) -priceTypeMap['sell_market'] = (DIRECTION_SHORT, PRICETYPE_MARKETPRICE) -priceTypeMapReverse = {v: k for k, v in priceTypeMap.items()} - -# 委托状态印射 -statusMap = {} -statusMap[-1] = STATUS_CANCELLED -statusMap[0] = STATUS_NOTTRADED -statusMap[1] = STATUS_PARTTRADED -statusMap[2] = STATUS_ALLTRADED -statusMap[4] = STATUS_UNKNOWN - - -okex_all_symbol_pairs = ['ref_usdt', 'soc_usdt', 'light_usdt', 'avt_usdt', 'of_usdt', 'brd_usdt', 'ast_usdt', 'int_usdt', 'zrx_usdt', 'ctr_usdt', 'dgd_usdt', 'aidoc_usdt', 'wtc_usdt', 'swftc_usdt', 'wrc_usdt', 'sub_usdt', 'dna_usdt', 'knc_usdt', 'kcash_usdt', 'mdt_usdt', 'theta_usdt', 'ppt_usdt', 'utk_usdt', 'qvt_usdt', 'salt_usdt', 'la_usdt', 'itc_usdt', 'fair_usdt', 'yee_usdt', '1st_usdt', 'fun_usdt', 'iost_usdt', 'mkr_usdt', 'tio_usdt', 'req_usdt', 'ubtc_usdt', 'icx_usdt', 'tct_usdt', 'san_usdt', 'lrc_usdt', 'icn_usdt', 'cvc_usdt', 'eth_usdt', 'poe_usdt', 'xlm_usdt', 'iota_usdt', 'eos_usdt', 'nuls_usdt', 'mot_usdt', 'neo_usdt', 'gnx_usdt', 'dgb_usdt', 'evx_usdt', 'ltc_usdt', 'mda_usdt', 'etc_usdt', 'dpy_usdt', 'tnb_usdt', 'nas_usdt', 'btc_usdt', 'smt_usdt', 'ssc_usdt', 'oax_usdt', 'yoyo_usdt', 'snc_usdt', 'sngls_usdt', 'bch_usdt', 'mana_usdt', 'mof_usdt', 'mco_usdt', 'vib_usdt', 'topc_usdt', 'pra_usdt', 'bnt_usdt', 'xmr_usdt', 'edo_usdt', 'snt_usdt', 'eng_usdt', 'stc_usdt', 'qtum_usdt', 'key_usdt', 'ins_usdt', 'rnt_usdt', 'bcd_usdt', 'amm_usdt', 'lend_usdt', 'btm_usdt', 'elf_usdt', 'xuc_usdt', 'cag_usdt', 'snm_usdt', 'act_usdt', 'dash_usdt', 'zec_usdt', 'storj_usdt', 'pay_usdt', 'vee_usdt', 'show_usdt', 'trx_usdt', 'atl_usdt', 'ark_usdt', 'ost_usdt', 'gnt_usdt', 'dat_usdt', 'rcn_usdt', 'qun_usdt', 'mth_usdt', 'rct_usdt', 'read_usdt', 'gas_usdt', 'btg_usdt', 'mtl_usdt', 'cmt_usdt', 'xrp_usdt', 'spf_usdt', 'aac_usdt', 'can_usdt', 'omg_usdt', 'hsr_usdt', 'link_usdt', 'dnt_usdt', 'true_usdt', 'ukg_usdt', 'xem_usdt', 'ngc_usdt', 'lev_usdt', 'rdn_usdt', 'ace_usdt', 'ipc_usdt', 'ugc_usdt', 'viu_usdt', 'mag_usdt', 'hot_usdt', 'pst_usdt', -'ref_btc', 'soc_btc', 'light_btc', 'avt_btc', 'of_btc', 'brd_btc', 'ast_btc', 'int_btc', 'zrx_btc', 'ctr_btc', 'dgd_btc', 'aidoc_btc', 'wtc_btc', 'swftc_btc', 'wrc_btc', 'sub_btc', 'dna_btc', 'knc_btc', 'kcash_btc', 'mdt_btc', 'theta_btc', 'ppt_btc', 'utk_btc', 'qvt_btc', 'salt_btc', 'la_btc', 'itc_btc', 'fair_btc', 'yee_btc', '1st_btc', 'fun_btc', 'iost_btc', 'mkr_btc', 'tio_btc', 'req_btc', 'ubtc_btc', 'icx_btc', 'tct_btc', 'san_btc', 'lrc_btc', 'icn_btc', 'cvc_btc', 'eth_btc', 'poe_btc', 'xlm_btc', 'iota_btc', 'eos_btc', 'nuls_btc', 'mot_btc', 'neo_btc', 'gnx_btc', 'dgb_btc', 'evx_btc', 'ltc_btc', 'mda_btc', 'etc_btc', 'dpy_btc', 'tnb_btc', 'nas_btc', 'btc_btc', 'smt_btc', 'ssc_btc', 'oax_btc', 'yoyo_btc', 'snc_btc', 'sngls_btc', 'bch_btc', 'mana_btc', 'mof_btc', 'mco_btc', 'vib_btc', 'topc_btc', 'pra_btc', 'bnt_btc', 'xmr_btc', 'edo_btc', 'snt_btc', 'eng_btc', 'stc_btc', 'qtum_btc', 'key_btc', 'ins_btc', 'rnt_btc', 'bcd_btc', 'amm_btc', 'lend_btc', 'btm_btc', 'elf_btc', 'xuc_btc', 'cag_btc', 'snm_btc', 'act_btc', 'dash_btc', 'zec_btc', 'storj_btc', 'pay_btc', 'vee_btc', 'show_btc', 'trx_btc', 'atl_btc', 'ark_btc', 'ost_btc', 'gnt_btc', 'dat_btc', 'rcn_btc', 'qun_btc', 'mth_btc', 'rct_btc', 'read_btc', 'gas_btc', 'btg_btc', 'mtl_btc', 'cmt_btc', 'xrp_btc', 'spf_btc', 'aac_btc', 'can_btc', 'omg_btc', 'hsr_btc', 'link_btc', 'dnt_btc', 'true_btc', 'ukg_btc', 'xem_btc', 'ngc_btc', 'lev_btc', 'rdn_btc', 'ace_btc', 'ipc_btc', 'ugc_btc', 'viu_btc', 'mag_btc', 'hot_btc', 'pst_btc'] - - -######################################################################## -class OkexGateway(VtGateway): - """OKEX交易接口""" - - #---------------------------------------------------------------------- - def __init__(self, eventEngine, gatewayName='OKEX'): - """Constructor""" - super(OkexGateway, self).__init__(eventEngine, gatewayName) - - self.api_spot = SpotApi(self) - # self.api_contract = Api_contract(self) - - self.leverage = 0 - self.connected = False - - self.fileName = self.gatewayName + '_connect.json' - self.filePath = getJsonPath(self.fileName, __file__) - - #---------------------------------------------------------------------- - def connect(self): - """连接""" - # 载入json文件 - try: - f = file(self.filePath) - except IOError: - log = VtLogData() - log.gatewayName = self.gatewayName - log.logContent = u'读取连接配置出错,请检查' - self.onLog(log) - return - - # 解析json文件 - setting = json.load(f) - try: - apiKey = str(setting['apiKey']) - secretKey = str(setting['secretKey']) - trace = setting['trace'] - leverage = setting['leverage'] - except KeyError: - log = VtLogData() - log.gatewayName = self.gatewayName - log.logContent = u'连接配置缺少字段,请检查' - self.onLog(log) - return - - # 初始化接口 - self.leverage = leverage - - self.api_spot.active = True - self.api_spot.connect(apiKey, secretKey, trace) - - log = VtLogData() - log.gatewayName = self.gatewayName - log.logContent = u'接口初始化成功' - self.onLog(log) - - # 启动查询 - # self.initQuery() - # self.startQuery() - - #---------------------------------------------------------------------- - def subscribe(self, subscribeReq): - """订阅行情""" - self.api_spot.subscribe(subscribeReq) - - #---------------------------------------------------------------------- - def sendOrder(self, orderReq): - """发单""" - return self.api_spot.spotSendOrder(orderReq) - - #---------------------------------------------------------------------- - def cancelOrder(self, cancelOrderReq): - """撤单""" - self.api_spot.spotCancel(cancelOrderReq) - - #---------------------------------------------------------------------- - def qryAccount(self): - """查询账户资金""" - self.api_spot.spotUserInfo() - - #---------------------------------------------------------------------- - def qryOrderInfo(self): - self.api_spot.spotAllOrders() - - #---------------------------------------------------------------------- - def qryPosition(self): - """查询持仓""" - pass - - #---------------------------------------------------------------------- - def close(self): - """关闭""" - self.api_spot.active = False - self.api_spot.close() - - #---------------------------------------------------------------------- - def initQuery(self): - """初始化连续查询""" - if self.qryEnabled: - # 需要循环的查询函数列表 - #self.qryFunctionList = [self.qryAccount, self.qryOrderInfo] - self.qryFunctionList = [ self.qryOrderInfo] - #self.qryFunctionList = [] - - self.qryCount = 0 # 查询触发倒计时 - self.qryTrigger = 2 # 查询触发点 - self.qryNextFunction = 0 # 上次运行的查询函数索引 - - self.startQuery() - - #---------------------------------------------------------------------- - def query(self, event): - """注册到事件处理引擎上的查询函数""" - self.qryCount += 1 - - if self.qryCount > self.qryTrigger: - # 清空倒计时 - self.qryCount = 0 - - # 执行查询函数 - function = self.qryFunctionList[self.qryNextFunction] - function() - - # 计算下次查询函数的索引,如果超过了列表长度,则重新设为0 - self.qryNextFunction += 1 - if self.qryNextFunction == len(self.qryFunctionList): - self.qryNextFunction = 0 - - #---------------------------------------------------------------------- - def startQuery(self): - """启动连续查询""" - self.eventEngine.register(EVENT_TIMER, self.query) - - #---------------------------------------------------------------------- - def setQryEnabled(self, qryEnabled): - """设置是否要启动循环查询""" - self.qryEnabled = qryEnabled - - -######################################################################## -class SpotApi(OkexSpotApi): - """okex的API实现""" - - #---------------------------------------------------------------------- - def __init__(self, gateway): - """Constructor""" - super(SpotApi, self).__init__() - - self.gateway = gateway # gateway对象 - self.gatewayName = gateway.gatewayName # gateway对象名称 - self.active = False # 若为True则会在断线后自动重连 - - self.cbDict = {} - self.tickDict = {} - self.orderDict = {} - - self.channelSymbolMap = {} - - self.localNo = 0 # 本地委托号 - self.localNoQueue = Queue() # 未收到系统委托号的本地委托号队列 - self.localNoDict = {} # key为本地委托号,value为系统委托号 - self.orderIdDict = {} # key为系统委托号,value为本地委托号 - self.cancelDict = {} # key为本地委托号,value为撤单请求 - - self.recordOrderId_BefVolume = {} # 记录的之前处理的量 - - self.cache_some_order = {} - self.tradeID = 0 - - self.registerSymbolPairArray = set([]) - - self.initCallback() - - ''' - 登录后,每次订单执行撤销后又这样的 推送。。不知道干啥的。先过滤掉了 - {u'binary': 1, u'product': u'spot', u'type': u'order', u'base': u'etc' -, u'quote': u'usdt', u'data': {u'status': -1, u'orderType': 0, u'price': u'25.4050', u'modifyTime': -1512288275000L, u'userId': 6548935, u'createTime': 1512288275000L, u'source': 0, u'quoteSize': u'0.0 -0000000', u'executedValue': u'0.00000000', u'id': 62877909, u'filledSize': u'0.00000000', u'side': 1 -, u'size': u'0.01000000'}} - ''' - #---------------------------------------------------------------------- - def onMessage(self, ws, evt): - """信息推送""" - # print evt - - data = self.readData(evt)[0] - try: - channel = data['channel'] - except Exception as ex: - channel = None - if channel == None: - return - # try: - if channel == "addChannel" and 'data' in data: - channel = data['data']["channel"] - if channel != "addChannel" and 'future' not in channel and channel != 'login': - - # print channel - callback = self.cbDict[channel] - callback(data) - - # if 'depth' not in channel and 'ticker' not in channel and 'deals' not in channel and 'userinfo' not in channel and 'future' not in channel: - # print data - - # except Exception,ex: - # print "Error in callback cbDict ", channel - - #print self.cbDict - - #---------------------------------------------------------------------- - def onError(self, ws, evt): - """错误推送""" - error = VtErrorData() - error.gatewayName = self.gatewayName - error.errorMsg = str(evt) - self.gateway.onError(error) - - #---------------------------------------------------------------------- - def onError(self, data): - error = VtErrorData() - error.gatewayName = self.gatewayName - error.errorMsg = str(data["data"]["error_code"]) - self.gateway.onError(error) - - #---------------------------------------------------------------------- - def onClose(self, ws): - """接口断开""" - # 如果尚未连上,则忽略该次断开提示 - if not self.gateway.connected: - return - - self.gateway.connected = False - self.writeLog(u'服务器连接断开') - - # 重新连接 - if self.active: - def reconnect(): - while not self.gateway.connected: - self.writeLog(u'等待10秒后重新连接') - sleep(10) - if not self.gateway.connected: - self.reconnect() - - t = Thread(target=reconnect) - t.start() - #---------------------------------------------------------------------- - def subscribe(self, subscribeReq): - symbol_pair_gateway = subscribeReq.symbol - arr = symbol_pair_gateway.split('.') - symbol_pair = arr[0] - - if symbol_pair not in self.registerSymbolPairArray: - self.registerSymbolPairArray.add(symbol_pair) - self.subscribeSingleSymbol(symbol_pair) - - self.spotOrderInfo(symbol_pair, '-1') - - #---------------------------------------------------------------------- - def subscribeSingleSymbol(self, symbol): - if symbol in okex_all_symbol_pairs: - self.subscribeSpotTicker(symbol) - self.subscribeSpotDepth5(symbol) - #self.subscribeSpotDeals(symbol) - - #---------------------------------------------------------------------- - def spotAllOrders(self): - print(spotAllOrders) - for symbol in registerSymbolPairArray: - if symbol in okex_all_symbol_pairs: - self.spotOrderInfo(symbol, '-1') - - for orderId in self.orderIdDict.keys(): - order = self.orderDict.get(orderId, None) - if order != None: - symbol_pair = (order.symbol.split('.'))[0] - self.spotOrderInfo(symbol_pair, orderId) - - #---------------------------------------------------------------------- - def onOpen(self, ws): - """连接成功""" - self.gateway.connected = True - self.writeLog(u'服务器连接成功') - - self.login() - # 连接后查询账户和委托数据 - self.spotUserInfo() - - self.subscribeSingleSymbol("etc_usdt") - for symbol in okex_all_symbol_pairs: - # self.subscribeSpotTicker(symbol) - # self.subscribeSpotDepth5(symbol) - # self.subscribeSpotDeals(symbol) - - #Ticker数据 - self.channelSymbolMap["ok_sub_spot_%s_ticker" % symbol] = symbol - #盘口的深度 - self.channelSymbolMap["ok_sub_spot_%s_depth_5" % symbol] = symbol - #所有人的交易数据 - self.channelSymbolMap["ok_sub_spot_%s_deals" % symbol] = symbol - - contract = VtContractData() - contract.gatewayName = self.gatewayName - contract.symbol = symbol - contract.exchange = EXCHANGE_OKEX - contract.vtSymbol = '.'.join([contract.symbol, contract.exchange]) - contract.name = u'OKEX现货%s' % symbol - contract.size = 0.00001 - contract.priceTick = 0.00001 - contract.productClass = PRODUCT_SPOT - self.gateway.onContract(contract) - - ''' - [{ - "channel":"ok_sub_spot_bch_btc_deals", - "data":[["1001","2463.86","0.052","16:34:07","ask"]] - }] - ''' - #---------------------------------------------------------------------- - def onSpotSubDeals(self, data): - if 'data' not in data: - return - rawData = data["data"] - - # print rawData - - - #---------------------------------------------------------------------- - def writeLog(self, content): - """快速记录日志""" - log = VtLogData() - log.gatewayName = self.gatewayName - log.logContent = content - self.gateway.onLog(log) - - #---------------------------------------------------------------------- - def initCallback(self): - """初始化回调函数""" - # USD_SPOT - for symbol_pair in okex_all_symbol_pairs: - self.cbDict["ok_sub_spot_%s_ticker" % symbol_pair] = self.onTicker - self.cbDict["ok_sub_spot_%s_depth_5" % symbol_pair] = self.onDepth - self.cbDict["ok_sub_spot_%s_deals" % symbol_pair] = self.onSpotSubDeals - - self.cbDict["ok_sub_spot_%s_order" % symbol_pair] = self.onSpotSubOrder - self.cbDict["ok_sub_spot_%s_balance" % symbol_pair] = self.onSpotBalance - - self.cbDict['ok_spot_userinfo'] = self.onSpotUserInfo - self.cbDict['ok_spot_orderinfo'] = self.onSpotOrderInfo - - # 下面这两个好像废弃了 - #self.cbDict['ok_sub_spot_userinfo'] = self.onSpotSubUserInfo - #self.cbDict['ok_sub_spot_trades'] = self.onSpotSubTrades - - self.cbDict['ok_spot_order'] = self.onSpotOrder - self.cbDict['ok_spot_cancel_order'] = self.onSpotCancelOrder - - ''' - [ - { - "binary": 0, - "channel": "ok_sub_spot_bch_btc_ticker", - "data": { - "high": "10000", - "vol": "185.03743858", - "last": "111", - "low": "0.00000001", - "buy": "115", - "change": "101", - "sell": "115", - "dayLow": "0.00000001", - "dayHigh": "10000", - "timestamp": 1500444626000 - } - } - ] - ''' - #---------------------------------------------------------------------- - def onTicker(self, data): - """""" - if 'data' not in data: - return - - channel = data['channel'] - if channel == 'addChannel': - return - try: - symbol = self.channelSymbolMap[channel] - - if symbol not in self.tickDict: - tick = VtTickData() - tick.exchange = EXCHANGE_OKEX - tick.symbol = '.'.join([symbol, tick.exchange]) - tick.vtSymbol = '.'.join([symbol, tick.exchange]) - - tick.gatewayName = self.gatewayName - self.tickDict[symbol] = tick - else: - tick = self.tickDict[symbol] - - rawData = data['data'] - tick.highPrice = float(rawData['high']) - tick.lowPrice = float(rawData['low']) - tick.lastPrice = float(rawData['last']) - tick.volume = float(rawData['vol'].replace(',', '')) - # tick.date, tick.time = self.generateDateTime(rawData['timestamp']) - - # print "ticker", tick.date, tick.time - # newtick = copy(tick) - # self.gateway.onTick(newtick) - except Exception as ex: - print("Error in onTicker ", channel) - - #---------------------------------------------------------------------- - def onDepth(self, data): - """""" - if 'data' not in data: - return - try: - channel = data['channel'] - symbol = self.channelSymbolMap[channel] - except Exception as ex: - symbol = None - - if symbol == None: - return - - if symbol not in self.tickDict: - tick = VtTickData() - tick.symbol = symbol - tick.vtSymbol = symbol - tick.gatewayName = self.gatewayName - self.tickDict[symbol] = tick - else: - tick = self.tickDict[symbol] - - if 'data' not in data: - return - rawData = data['data'] - - - tick.bidPrice1, tick.bidVolume1 = rawData['bids'][0] - tick.bidPrice2, tick.bidVolume2 = rawData['bids'][1] - tick.bidPrice3, tick.bidVolume3 = rawData['bids'][2] - tick.bidPrice4, tick.bidVolume4 = rawData['bids'][3] - tick.bidPrice5, tick.bidVolume5 = rawData['bids'][4] - - tick.askPrice1, tick.askVolume1 = rawData['asks'][-1] - tick.askPrice2, tick.askVolume2 = rawData['asks'][-2] - tick.askPrice3, tick.askVolume3 = rawData['asks'][-3] - tick.askPrice4, tick.askVolume4 = rawData['asks'][-4] - tick.askPrice5, tick.askVolume5 = rawData['asks'][-5] - - tick.date, tick.time = self.generateDateTime(rawData['timestamp']) - # print "Depth", tick.date, tick.time - - newtick = copy(tick) - self.gateway.onTick(newtick) - - ''' - [ - { - "base": "bch", - "binary": 0, - "channel": "ok_sub_spot_bch_btc_balance", - "data": { - "info": { - "free": { - "btc": 5814.850605790395 - }, - "freezed": { - "btc": 7341 - } - } - }, - "product": "spot", - "quote": "btc", - "type": "order" - } - ] - ''' - def onSpotBalance(self, data): - """交易发生金额变动之后会触发这个函数""" - # print data - - rawData = data['data'] - info = rawData['info'] - - for symbol in info["freezed"].keys(): - pos = VtPositionData() - pos.gatewayName = self.gatewayName - pos.symbol = symbol + "." + EXCHANGE_OKEX - pos.vtSymbol = symbol + "." + EXCHANGE_OKEX - pos.direction = DIRECTION_NET - pos.frozen = float(info['freezed'][symbol]) - pos.position = pos.frozen + float(info['free'][symbol]) - - self.gateway.onPosition(pos) - - ''' - [{"binary":0,"channel":"ok_spot_userinfo","data":{"result":true,"info":{"funds":{"borrow":{"dgd":"0" -,"bcd":"0","bcc":"0","bch":"0","hsr":"0","xuc":"0","omg":"0","eos":"0","qtum":"0","btc":"0","act":"0 -","bcs":"0","btg":"0","etc":"0","eth":"0","usdt":"0","gas":"0","zec":"0","neo":"0","ltc":"0","bt1":" -0","bt2":"0","iota":"0","pay":"0","storj":"0","gnt":"0","snt":"0","dash":"0"},"free":{"dgd":"0","bcd -":"0","bcc":"0","bch":"0","hsr":"0","xuc":"3","omg":"0","eos":"0","qtum":"0","btc":"0.00266884258369 -","act":"0","bcs":"0","btg":"0","etc":"7.9909635","eth":"0","usdt":"0","gas":"0","zec":"0","neo":"0" -,"ltc":"0","bt1":"0","bt2":"0","iota":"0","pay":"0","storj":"0","gnt":"0","snt":"0","dash":"0"},"fre -ezed":{"dgd":"0","bcd":"0","bcc":"0","bch":"0","hsr":"0","xuc":"0","omg":"0","eos":"0","qtum":"0","b -tc":"0","act":"0","bcs":"0","btg":"0","etc":"0","eth":"0","usdt":"0","gas":"0","zec":"0","neo":"0"," -ltc":"0","bt1":"0","bt2":"0","iota":"0","pay":"0","storj":"0","gnt":"0","snt":"0","dash":"0"}}}}}] -{u'binary': 0, u'data': {u'info': {u'funds': {u'freezed': {u'zec': u'0', u'usdt': u'0', u'btg': u'0' -, u'btc': u'0', u'bt1': u'0', u'neo': u'0', u'pay': u'0', u'storj': u'0', u'iota': u'0', u'omg': u'0 -', u'dgd': u'0', u'bt2': u'0', u'xuc': u'0', u'gas': u'0', u'hsr': u'0', u'snt': u'0', u'dash': u'0' -, u'bch': u'0', u'gnt': u'0', u'bcd': u'0', u'qtum': u'0', u'bcc': u'0', u'eos': u'0', u'etc': u'0', - u'act': u'0', u'eth': u'0', u'ltc': u'0', u'bcs': u'0'}, u'borrow': {u'zec': u'0', u'usdt': u'0', u -'btg': u'0', u'btc': u'0', u'bt1': u'0', u'neo': u'0', u'pay': u'0', u'storj': u'0', u'iota': u'0', -u'omg': u'0', u'dgd': u'0', u'bt2': u'0', u'xuc': u'0', u'gas': u'0', u'hsr': u'0', u'snt': u'0', u' -dash': u'0', u'bch': u'0', u'gnt': u'0', u'bcd': u'0', u'qtum': u'0', u'bcc': u'0', u'eos': u'0', u' -etc': u'0', u'act': u'0', u'eth': u'0', u'ltc': u'0', u'bcs': u'0'}, u'free': {u'zec': u'0', u'usdt' -: u'0', u'btg': u'0', u'btc': u'0.00266884258369', u'bt1': u'0', u'neo': u'0', u'pay': u'0', u'storj -': u'0', u'iota': u'0', u'omg': u'0', u'dgd': u'0', u'bt2': u'0', u'xuc': u'3', u'gas': u'0', u'hsr' -: u'0', u'snt': u'0', u'dash': u'0', u'bch': u'0', u'gnt': u'0', u'bcd': u'0', u'qtum': u'0', u'bcc' -: u'0', u'eos': u'0', u'etc': u'7.9909635', u'act': u'0', u'eth': u'0', u'ltc': u'0', u'bcs': u'0'}} -}, u'result': True}, u'channel': u'ok_spot_userinfo'} - ''' - #---------------------------------------------------------------------- - def onSpotUserInfo(self, data): - """现货账户资金推送""" - rawData = data['data'] - info = rawData['info'] - funds = rawData['info']['funds'] - - # 持仓信息 - for symbol in ['btc', 'ltc','eth', self.currency]: - #for symbol in : - if symbol in funds['free']: - pos = VtPositionData() - pos.gatewayName = self.gatewayName - - pos.symbol = symbol + "." + EXCHANGE_OKEX - pos.vtSymbol = symbol + "." + EXCHANGE_OKEX - pos.vtPositionName = symbol - pos.direction = DIRECTION_NET - - pos.frozen = float(funds['freezed'][symbol]) - pos.position = pos.frozen + float(funds['free'][symbol]) - - self.gateway.onPosition(pos) - - # 账户资金 - account = VtAccountData() - account.gatewayName = self.gatewayName - account.accountID = self.gatewayName - account.vtAccountID = account.accountID - account.balance = 0.0 - #account.balance = float(funds['asset']['net']) - self.gateway.onAccount(account) - - #---------------------------------------------------------------------- - # 这个 API 现在文档没找到。。 好像废弃了 - def onSpotSubUserInfo(self, data): - """现货账户资金推送""" - if 'data' not in data: - return - - rawData = data['data'] - info = rawData['info'] - - # 持仓信息 - #for symbol in ['btc', 'ltc','eth', self.currency]: - for symbol in SPOT_CURRENCY: - if symbol in info['free']: - pos = VtPositionData() - pos.gatewayName = self.gatewayName - - pos.symbol = symbol + "." + EXCHANGE_OKEX - pos.vtSymbol = symbol + "." + EXCHANGE_OKEX - pos.vtPositionName = symbol - pos.direction = DIRECTION_NET - - pos.frozen = float(info['freezed'][symbol]) - pos.position = pos.frozen + float(info['free'][symbol]) - - self.gateway.onPosition(pos) - - ''' - 交易数据 - [ - { - "base": "bch", - "binary": 0, - "channel": "ok_sub_spot_bch_btc_order", - "data": { - "symbol": "bch_btc", - "tradeAmount": "1.00000000", - "createdDate": "1504530228987", - "orderId": 6191, - "completedTradeAmount": "0.00000000", - "averagePrice": "0", - "tradePrice": "0.00000000", - "tradeType": "buy", - "status": 0, - "tradeUnitPrice": "113.00000000" - }, - "product": "spot", - "quote": "btc", - "type": "balance" - } - ] - - {u'binary': 0, u'data': {u'orderId': 62870564, u'status': 0, u'tradeType': u'sell', u'tradeUnitPrice -': u'25.3500', u'symbol': u'etc_usdt', u'tradePrice': u'0.0000', u'createdDate': u'1512287172393', u -'averagePrice': u'0', u'tradeAmount': u'0.01000000', u'completedTradeAmount': u'0.00000000'}, u'chan -nel': u'ok_sub_spot_etc_usdt_order'} - ''' - #---------------------------------------------------------------------- - def onSpotSubOrder(self, data): - """交易数据""" - if 'data' not in data: - return - - rawData = data["data"] - - # 本地和系统委托号 - orderId = str(rawData['orderId']) - - # 这时候出现None, 情况是 已经发出了单子,但是系统这里还没建立 索引 - # 先这样返回试一下 - # 因为 发完单,订单变化是先推送的。。导致不清楚他的localID - # 现在的处理方式是, 先缓存这里的信息,等到出现了 localID,再来处理这一段 - localNo = self.orderIdDict.get(orderId, None) - if localNo == None: - arr = self.cache_some_order.get(orderId, None) - if arr == None: - arr = [] - arr.append(data) - self.cache_some_order[orderId] = arr - else: - arr.append(data) - return - - # 委托信息 - if orderId not in self.orderDict: - order = VtOrderData() - order.gatewayName = self.gatewayName - - order.symbol = '.'.join([rawData['symbol'], EXCHANGE_OKEX]) - #order.symbol = spotSymbolMap[rawData['symbol']] - order.vtSymbol = order.symbol - - order.orderID = localNo - order.vtOrderID = '.'.join([self.gatewayName, order.orderID]) - - order.price = float(rawData['tradeUnitPrice']) - order.totalVolume = float(rawData['tradeAmount']) - order.direction, priceType = priceTypeMap[rawData['tradeType']] - - self.orderDict[orderId] = order - else: - order = self.orderDict[orderId] - - order.tradedVolume = float(rawData['completedTradeAmount']) - order.status = statusMap[rawData['status']] - - self.gateway.onOrder(copy(order)) - - - bef_volume = self.recordOrderId_BefVolume.get(orderId, 0.0 ) - now_volume = float(rawData['completedTradeAmount']) - bef_volume - - if now_volume > 0.000001: - trade = VtTradeData() - trade.gatewayName = self.gatewayName - - trade.symbol = order.symbol - trade.vtSymbol = order.symbol - - self.tradeID += 1 - trade.tradeID = str(self.tradeID) - trade.vtTradeID = '.'.join([self.gatewayName, trade.tradeID]) - - trade.orderID = localNo - trade.vtOrderID = '.'.join([self.gatewayName, trade.orderID]) - - trade.price = float(rawData['tradeUnitPrice']) - trade.volume = float(now_volume) - - trade.direction, priceType = priceTypeMap[rawData['tradeType']] - - trade.tradeTime = datetime.now().strftime('%H:%M:%S') - - self.gateway.onTrade(trade) - - """ - 原来的OK coin方式,不过数据一直没有 所以换一种方式 - # 成交信息 - if 'sigTradeAmount' in rawData and float(rawData['sigTradeAmount'])>0: - trade = VtTradeData() - trade.gatewayName = self.gatewayName - - trade.symbol = spotSymbolMap[rawData['symbol']] - trade.vtSymbol = order.symbol - - trade.tradeID = str(rawData['id']) - trade.vtTradeID = '.'.join([self.gatewayName, trade.tradeID]) - - trade.orderID = localNo - trade.vtOrderID = '.'.join([self.gatewayName, trade.orderID]) - - trade.price = float(rawData['sigTradePrice']) - trade.volume = float(rawData['sigTradeAmount']) - - trade.direction, priceType = priceTypeMap[rawData['tradeType']] - - trade.tradeTime = datetime.now().strftime('%H:%M:%S') - - self.gateway.onTrade(trade) - """ - ''' - [ - { - "binary": 0, - "channel": "ok_spot_orderinfo", - "data": { - "result": true, - "orders": [ - { - "symbol": "bch_btc", - "amount": "0.10000000", - "price": "1.00000000", - "avg_price": 0, - "create_date": 1504529828000, - "type": "buy", - "deal_amount": 0, - "order_id": 6189, - "status": -1 - } - ] - } - } - ] - ''' - #---------------------------------------------------------------------- - def onSpotOrderInfo(self, data): - """委托信息查询回调""" - if "error_code" in data.keys(): - print(data) - return - rawData = data['data'] - for d in rawData['orders']: - self.localNo += 1 - localNo = str(self.localNo) - orderId = str(d['order_id']) - - self.localNoDict[localNo] = orderId - self.orderIdDict[orderId] = localNo - - if orderId not in self.orderDict: - order = VtOrderData() - order.gatewayName = self.gatewayName - - #order.symbol = spotSymbolMap[d['symbol']] - order.symbol = '.'.join([d["symbol"], EXCHANGE_OKEX]) - order.vtSymbol = order.symbol - - order.orderID = localNo - order.vtOrderID = '.'.join([self.gatewayName, order.orderID]) - - order.price = d['price'] - order.totalVolume = d['amount'] - order.direction, priceType = priceTypeMap[d['type']] - - self.orderDict[orderId] = order - else: - order = self.orderDict[orderId] - - order.tradedVolume = d['deal_amount'] - order.status = statusMap[d['status']] - - self.gateway.onOrder(copy(order)) - - ''' - [ - { - "binary": 0, - "channel": "ok_spot_order", - "data": { - "result": true, - "order_id": 6189 - } - } - ] - ''' - def onSpotOrder(self, data): - rawData = data['data'] - if 'error_code' in rawData.keys(): - print(data) - return - - orderId = str(rawData['order_id']) - - # 尽管websocket接口的委托号返回是异步的,但经过测试是 - # 符合先发现回的规律,因此这里通过queue获取之前发送的 - # 本地委托号,并把它和推送的系统委托号进行映射 - - # localNo = self.orderIdDict.get(orderId, None) - # if localNo == None: - - localNo = self.localNoQueue.get_nowait() - - self.localNoDict[localNo] = orderId - self.orderIdDict[orderId] = localNo - - # print orderId, self.cache_some_order - if orderId in self.cache_some_order.keys(): - arr = self.cache_some_order[orderId] - for d in arr: - self.onSpotSubOrder(d) - - # 处理完就删除掉这里 - del self.cache_some_order[orderId] - - # 检查是否有系统委托号返回前就发出的撤单请求,若有则进 - # 行撤单操作 - if localNo in self.cancelDict: - req = self.cancelDict[localNo] - self.spotCancel(req) - del self.cancelDict[localNo] - - - ''' - [ - { - "binary": 0, - "channel": "ok_spot_cancel_order", - "data": { - "result": true, - "order_id": "125433027" - } - } - ] - ''' - #---------------------------------------------------------------------- - def onSpotCancelOrder(self, data): - """撤单回报""" - if 'data' not in data: - return - - if 'error' in data["data"].keys(): - self.onError(data) - return - - rawData = data['data'] - orderId = str(rawData['order_id']) - - localNo = self.orderIdDict[orderId] - - if orderId not in self.orderDict: - order = VtOrderData() - order.gatewayName = self.gatewayName - - order.symbol = '.'.join([rawData['symbol'], EXCHANGE_OKEX]) - order.vtSymbol = order.symbol - - order.orderID = localNo - order.vtOrderID = '.'.join([self.gatewayName, order.orderID]) - - self.orderDict[orderId] = order - else: - order = self.orderDict[orderId] - - order.status = STATUS_CANCELLED - self.gateway.onOrder(order) - - del self.orderDict[orderId] - del self.orderIdDict[orderId] - del self.localNoDict[localNo] - - - if orderId in self.cache_some_order.keys(): - del self.cache_some_order[orderId] - - #---------------------------------------------------------------------- - def spotSendOrder(self, req): - """发单""" - #symbol = spotSymbolMapReverse[req.symbol][:4] - symbol = (req.symbol.split('.'))[0] - type_ = priceTypeMapReverse[(req.direction, req.priceType)] - - self.spotTrade(symbol, type_, str(req.price), str(req.volume)) - - # 本地委托号加1,并将对应字符串保存到队列中,返回基于本地委托号的vtOrderID - self.localNo += 1 - self.localNoQueue.put(str(self.localNo)) - vtOrderID = '.'.join([self.gatewayName, str(self.localNo)]) - return vtOrderID - - #---------------------------------------------------------------------- - def spotCancel(self, req): - """撤单""" - #symbol = spotSymbolMapReverse[req.symbol][:4] - symbol = (req.symbol.split('.'))[0] - localNo = req.orderID - - if localNo in self.localNoDict: - orderID = self.localNoDict[localNo] - self.spotCancelOrder(symbol, orderID) - else: - # 如果在系统委托号返回前客户就发送了撤单请求,则保存 - # 在cancelDict字典中,等待返回后执行撤单任务 - self.cancelDict[localNo] = req - - #---------------------------------------------------------------------- - def generateDateTime(self, s): - """生成时间""" - dt = datetime.fromtimestamp(float(s)/1e3) - time = dt.strftime("%H:%M:%S.%f") - date = dt.strftime("%Y%m%d") - return date, time diff --git a/examples/JaqsService/CTP_connect.json b/beta/quantos/JaqsService/CTP_connect.json similarity index 100% rename from examples/JaqsService/CTP_connect.json rename to beta/quantos/JaqsService/CTP_connect.json diff --git a/examples/JaqsService/JS_setting.json b/beta/quantos/JaqsService/JS_setting.json similarity index 100% rename from examples/JaqsService/JS_setting.json rename to beta/quantos/JaqsService/JS_setting.json diff --git a/examples/JaqsService/VT_setting.json b/beta/quantos/JaqsService/VT_setting.json similarity index 100% rename from examples/JaqsService/VT_setting.json rename to beta/quantos/JaqsService/VT_setting.json diff --git a/examples/JaqsService/run.py b/beta/quantos/JaqsService/run.py similarity index 100% rename from examples/JaqsService/run.py rename to beta/quantos/JaqsService/run.py diff --git a/examples/JaqsService/runUI.py b/beta/quantos/JaqsService/runUI.py similarity index 100% rename from examples/JaqsService/runUI.py rename to beta/quantos/JaqsService/runUI.py diff --git a/examples/QuantosDataService/README.md b/beta/quantos/QuantosDataService/README.md similarity index 100% rename from examples/QuantosDataService/README.md rename to beta/quantos/QuantosDataService/README.md diff --git a/examples/QuantosDataService/config.json b/beta/quantos/QuantosDataService/config.json similarity index 100% rename from examples/QuantosDataService/config.json rename to beta/quantos/QuantosDataService/config.json diff --git a/examples/QuantosDataService/dataService.py b/beta/quantos/QuantosDataService/dataService.py similarity index 85% rename from examples/QuantosDataService/dataService.py rename to beta/quantos/QuantosDataService/dataService.py index 730cb2e2..89f6726d 100644 --- a/examples/QuantosDataService/dataService.py +++ b/beta/quantos/QuantosDataService/dataService.py @@ -62,7 +62,6 @@ def generateVtBar(row): bar.low = row['low'] bar.close = row['close'] bar.volume = row['volume'] - bar.date = str(row['date']) bar.time = str(row['time']).rjust(6, '0') @@ -70,6 +69,19 @@ def generateVtBar(row): hour=bar.time[0:2] minute=bar.time[2:4] sec=bar.time[4:6] + + # ------------------------------add by yzl :start + # print(row.date, type(row.date), row.time, type(row.time))# add by yzl to show the date type and value + # 20180328 < type'long' > 93400 < type'long' > + # 最佳改进方法: 构建一个datetime,然后滞后一分钟,不能简单0:00:00处理,日期减一,弊端:处理量太大 + # 改进2:找出 0:00,此时日期回退一天 + if int(hour) == 0 and int(minute) == 0: + temp_date = datetime(int(bar.date[:4]), int(bar.date[4:6]), int(bar.date[6:])).date() + temp_date = temp_date - timedelta(days=1) + bar.date = temp_date.strftime("%Y%m%d") + + # -------------------------------add by yzl :end + if minute=="00": minute="59" @@ -81,6 +93,8 @@ def generateVtBar(row): else: minute=str(int(minute)-1).rjust(2,'0') bar.time=hour+minute+sec + + bar.datetime = datetime.strptime(' '.join([bar.date, bar.time]), '%Y%m%d %H%M%S') diff --git a/examples/QuantosDataService/downloadData.py b/beta/quantos/QuantosDataService/downloadData.py similarity index 100% rename from examples/QuantosDataService/downloadData.py rename to beta/quantos/QuantosDataService/downloadData.py diff --git a/examples/QuantosDataService/runService.py b/beta/quantos/QuantosDataService/runService.py similarity index 100% rename from examples/QuantosDataService/runService.py rename to beta/quantos/QuantosDataService/runService.py diff --git a/vnpy/trader/app/jaqsService/JS_setting.json b/beta/quantos/app/jaqsService/JS_setting.json similarity index 100% rename from vnpy/trader/app/jaqsService/JS_setting.json rename to beta/quantos/app/jaqsService/JS_setting.json diff --git a/vnpy/trader/app/jaqsService/__init__.py b/beta/quantos/app/jaqsService/__init__.py similarity index 56% rename from vnpy/trader/app/jaqsService/__init__.py rename to beta/quantos/app/jaqsService/__init__.py index f0bf3d26..4f7abcdd 100644 --- a/vnpy/trader/app/jaqsService/__init__.py +++ b/beta/quantos/app/jaqsService/__init__.py @@ -1,7 +1,8 @@ # encoding: UTF-8 -from jsEngine import JsEngine -from uiJsWidget import JsEngineManager +from __future__ import absolute_import +from .jsEngine import JsEngine +from .uiJsWidget import JsEngineManager appName = 'JaqsService' appDisplayName = u'Jaqs服务' diff --git a/vnpy/trader/app/jaqsService/jrpc_server.py b/beta/quantos/app/jaqsService/jrpc_server.py similarity index 91% rename from vnpy/trader/app/jaqsService/jrpc_server.py rename to beta/quantos/app/jaqsService/jrpc_server.py index d49037c1..a80edda3 100644 --- a/vnpy/trader/app/jaqsService/jrpc_server.py +++ b/beta/quantos/app/jaqsService/jrpc_server.py @@ -1,3 +1,4 @@ +from __future__ import print_function import zmq import Queue import threading @@ -108,11 +109,11 @@ class JRpcServer : #client_addr_map[client_id] = identity self._on_data_arrived(identity, data) - except zmq.error.Again, e: + except zmq.error.Again as e: #print "RECV timeout: ", e pass - except Exception, e: - print("_recv_run:", e) + except Exception as e: + print(("_recv_run:", e)) def _callback_run(self): while not self._should_close: @@ -120,12 +121,12 @@ class JRpcServer : r = self._callback_queue.get(timeout = 1) if r : r() - except Queue.Empty, e: + except Queue.Empty as e: pass - except Exception, e: + except Exception as e: traceback.print_exc(e) - print "_callback_run", type(e), e + print("_callback_run", type(e), e) def _async_call(self, func): self._callback_queue.put( func ) @@ -164,12 +165,12 @@ class JRpcServer : #print "RECV", msg if not msg: - print "wrong message format" + print("wrong message format") return - method = msg['method'] if msg.has_key('method') else None + method = msg['method'] if 'method' in msg else None - call_id = msg['id'] if msg.has_key('id') and msg['id'] else None + call_id = msg['id'] if 'id' in msg and msg['id'] else None if call_id and method: if method == ".sys.heartbeat": @@ -183,8 +184,8 @@ class JRpcServer : if self.on_call : self._async_call( lambda : self.on_call(identity, msg)) - except Exception, e: - print( "_on_data_arrived:", e) + except Exception as e: + print(( "_on_data_arrived:", e)) pass diff --git a/vnpy/trader/app/jaqsService/js.ico b/beta/quantos/app/jaqsService/js.ico similarity index 100% rename from vnpy/trader/app/jaqsService/js.ico rename to beta/quantos/app/jaqsService/js.ico diff --git a/vnpy/trader/app/jaqsService/jsEngine.py b/beta/quantos/app/jaqsService/jsEngine.py similarity index 99% rename from vnpy/trader/app/jaqsService/jsEngine.py rename to beta/quantos/app/jaqsService/jsEngine.py index 8a072b57..15c6265c 100644 --- a/vnpy/trader/app/jaqsService/jsEngine.py +++ b/beta/quantos/app/jaqsService/jsEngine.py @@ -1,9 +1,10 @@ # encoding: UTF-8 +from __future__ import absolute_import import json from collections import defaultdict -import jrpc_server +from . import jrpc_server from vnpy.event import Event from vnpy.trader.vtFunction import getJsonPath diff --git a/vnpy/trader/app/jaqsService/service.py b/beta/quantos/app/jaqsService/service.py similarity index 91% rename from vnpy/trader/app/jaqsService/service.py rename to beta/quantos/app/jaqsService/service.py index 39bf6485..38cbae97 100644 --- a/vnpy/trader/app/jaqsService/service.py +++ b/beta/quantos/app/jaqsService/service.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- -import jrpc_server +from __future__ import print_function +from __future__ import absolute_import +from . import jrpc_server import time import pandas as pd from qdata.database import DatabaseConn @@ -15,7 +17,7 @@ db = None def on_call(client_id, req): if req['method'] != '.sys.heartbeat': - print "on_call", req + print("on_call", req) if req['method'] == 'auth.login': server.send_rsp(client_id, req, result = { "username" : "fixme", "name": "fixme" }) @@ -25,7 +27,7 @@ def on_call(client_id, req): server.send_rsp(client_id, req, error=[-1, "unknown method"]) return - if not req.has_key('params'): + if 'params' not in req: server.send_rsp(client_id, req, error=[-1, "missing params"]) return @@ -55,7 +57,7 @@ def run(): server = jrpc_server.JRpcServer() server.on_call = on_call addr = "tcp://%s:%s"%(st.HOST, st.PORT) - print "listen at " + addr + print("listen at " + addr) server.listen(addr) while True: diff --git a/vnpy/trader/app/jaqsService/uiJsWidget.py b/beta/quantos/app/jaqsService/uiJsWidget.py similarity index 100% rename from vnpy/trader/app/jaqsService/uiJsWidget.py rename to beta/quantos/app/jaqsService/uiJsWidget.py diff --git a/vnpy/trader/gateway/tkproGateway/DataApi/LICENSE b/beta/quantos/tkproGateway/DataApi/LICENSE similarity index 100% rename from vnpy/trader/gateway/tkproGateway/DataApi/LICENSE rename to beta/quantos/tkproGateway/DataApi/LICENSE diff --git a/vnpy/trader/gateway/tkproGateway/DataApi/README.md b/beta/quantos/tkproGateway/DataApi/README.md similarity index 100% rename from vnpy/trader/gateway/tkproGateway/DataApi/README.md rename to beta/quantos/tkproGateway/DataApi/README.md diff --git a/vnpy/trader/gateway/tkproGateway/DataApi/__init__.py b/beta/quantos/tkproGateway/DataApi/__init__.py similarity index 100% rename from vnpy/trader/gateway/tkproGateway/DataApi/__init__.py rename to beta/quantos/tkproGateway/DataApi/__init__.py diff --git a/vnpy/trader/gateway/tkproGateway/DataApi/data_api.py b/beta/quantos/tkproGateway/DataApi/data_api.py similarity index 100% rename from vnpy/trader/gateway/tkproGateway/DataApi/data_api.py rename to beta/quantos/tkproGateway/DataApi/data_api.py diff --git a/vnpy/trader/gateway/tkproGateway/DataApi/jrpc_py.py b/beta/quantos/tkproGateway/DataApi/jrpc_py.py similarity index 100% rename from vnpy/trader/gateway/tkproGateway/DataApi/jrpc_py.py rename to beta/quantos/tkproGateway/DataApi/jrpc_py.py diff --git a/vnpy/trader/gateway/tkproGateway/DataApi/utils.py b/beta/quantos/tkproGateway/DataApi/utils.py similarity index 100% rename from vnpy/trader/gateway/tkproGateway/DataApi/utils.py rename to beta/quantos/tkproGateway/DataApi/utils.py diff --git a/examples/VnTrader/TKPRO_connect.json b/beta/quantos/tkproGateway/TKPRO_connect.json similarity index 100% rename from examples/VnTrader/TKPRO_connect.json rename to beta/quantos/tkproGateway/TKPRO_connect.json diff --git a/vnpy/trader/gateway/tkproGateway/TradeApi/LICENSE b/beta/quantos/tkproGateway/TradeApi/LICENSE similarity index 100% rename from vnpy/trader/gateway/tkproGateway/TradeApi/LICENSE rename to beta/quantos/tkproGateway/TradeApi/LICENSE diff --git a/vnpy/trader/gateway/tkproGateway/TradeApi/README.md b/beta/quantos/tkproGateway/TradeApi/README.md similarity index 100% rename from vnpy/trader/gateway/tkproGateway/TradeApi/README.md rename to beta/quantos/tkproGateway/TradeApi/README.md diff --git a/vnpy/trader/gateway/tkproGateway/TradeApi/__init__.py b/beta/quantos/tkproGateway/TradeApi/__init__.py similarity index 100% rename from vnpy/trader/gateway/tkproGateway/TradeApi/__init__.py rename to beta/quantos/tkproGateway/TradeApi/__init__.py diff --git a/vnpy/trader/gateway/tkproGateway/TradeApi/jrpc_py.py b/beta/quantos/tkproGateway/TradeApi/jrpc_py.py similarity index 100% rename from vnpy/trader/gateway/tkproGateway/TradeApi/jrpc_py.py rename to beta/quantos/tkproGateway/TradeApi/jrpc_py.py diff --git a/vnpy/trader/gateway/tkproGateway/TradeApi/trade_api.py b/beta/quantos/tkproGateway/TradeApi/trade_api.py similarity index 100% rename from vnpy/trader/gateway/tkproGateway/TradeApi/trade_api.py rename to beta/quantos/tkproGateway/TradeApi/trade_api.py diff --git a/vnpy/trader/gateway/tkproGateway/TradeApi/utils.py b/beta/quantos/tkproGateway/TradeApi/utils.py similarity index 100% rename from vnpy/trader/gateway/tkproGateway/TradeApi/utils.py rename to beta/quantos/tkproGateway/TradeApi/utils.py diff --git a/vnpy/trader/gateway/tkproGateway/__init__.py b/beta/quantos/tkproGateway/__init__.py similarity index 100% rename from vnpy/trader/gateway/tkproGateway/__init__.py rename to beta/quantos/tkproGateway/__init__.py diff --git a/vnpy/trader/gateway/tkproGateway/tkproGateway.py b/beta/quantos/tkproGateway/tkproGateway.py similarity index 99% rename from vnpy/trader/gateway/tkproGateway/tkproGateway.py rename to beta/quantos/tkproGateway/tkproGateway.py index 85d406cc..ddd53e4a 100644 --- a/vnpy/trader/gateway/tkproGateway/tkproGateway.py +++ b/beta/quantos/tkproGateway/tkproGateway.py @@ -570,7 +570,7 @@ class TkproDataApi(object): tick.lowerLimit = data['limit_down'] self.gateway.onTick(tick) - except Exception, e: + except Exception as e: self.writeLog(u'行情更新失败,错误信息:%s' % str(e)) #---------------------------------------------------------------------- diff --git a/examples/CoinapiDataService/config.json b/examples/CoinapiDataService/config.json new file mode 100644 index 00000000..5cbd2e67 --- /dev/null +++ b/examples/CoinapiDataService/config.json @@ -0,0 +1,11 @@ +{ + "MONGO_HOST": "localhost", + "MONGO_PORT": 27017, + + "APIKEY": "", + + "SYMBOLS": [ + "BINANCE_SPOT_BTC_USDT", + "BINANCE_SPOT_ETH_USDT" + ] +} \ No newline at end of file diff --git a/examples/CoinapiDataService/dataService.py b/examples/CoinapiDataService/dataService.py new file mode 100644 index 00000000..50279b75 --- /dev/null +++ b/examples/CoinapiDataService/dataService.py @@ -0,0 +1,103 @@ +# encoding: UTF-8 + +from __future__ import print_function + +import json +import time +import datetime + +import requests +from pymongo import MongoClient, ASCENDING + +from vnpy.trader.vtObject import VtBarData +from vnpy.trader.app.ctaStrategy.ctaBase import MINUTE_DB_NAME + + +# 加载配置 +config = open('config.json') +setting = json.load(config) + +MONGO_HOST = setting['MONGO_HOST'] +MONGO_PORT = setting['MONGO_PORT'] +APIKEY = setting['APIKEY'] +SYMBOLS = setting['SYMBOLS'] + +mc = MongoClient(MONGO_HOST, MONGO_PORT) # Mongo连接 +db = mc[MINUTE_DB_NAME] # 数据库 +headers = {'X-CoinAPI-Key': APIKEY} + + + +#---------------------------------------------------------------------- +def generateVtBar(symbol, d): + """生成K线""" + bar = VtBarData() + + bar.symbol = symbol + bar.vtSymbol = symbol + bar.datetime = datetime.datetime.strptime(d['time_open'], '%Y-%m-%dT%H:%M:%S.%f0Z') + bar.date = bar.datetime.strftime('%Y%m%d') + bar.time = bar.datetime.strftime('%H:%M:%S') + bar.open = d['price_open'] + bar.high = d['price_high'] + bar.low = d['price_low'] + bar.close = d['price_close'] + bar.volume = d['volume_traded'] + + return bar + +#---------------------------------------------------------------------- +def downMinuteBarBySymbol(symbol, period, start, end): + """下载某一合约的分钟线数据""" + startTime = time.time() + + cl = db[symbol] # 集合 + cl.ensure_index([('datetime', ASCENDING)], unique=True) # 添加索引 + + startDt = datetime.datetime.strptime(start, '%Y%m%d') + endDt = datetime.datetime.strptime(end, '%Y%m%d') + + url = 'https://rest.coinapi.io/v1/ohlcv/%s/history' %symbol + params = { + 'period_id': period, + 'time_start': startDt.strftime('%Y-%m-%dT%H:%M:%S.%f0Z'), + 'time_end': endDt.strftime('%Y-%m-%dT%H:%M:%S.%f0Z'), + 'limit': 10000 + } + resp = requests.get(url, headers=headers, params=params) + + if resp.status_code != 200: + print(u'%s数据下载失败' %symbol) + return + + l = resp.json() + + for d in l: + bar = generateVtBar(symbol, d) + d = bar.__dict__ + flt = {'datetime': bar.datetime} + cl.replace_one(flt, d, True) + + endTime = time.time() + cost = (endTime - startTime) * 1000 + + print(u'合约%s数据下载完成%s - %s,耗时%s毫秒' %(symbol, l[0]['time_period_start'], + l[-1]['time_period_end'], cost)) + +#---------------------------------------------------------------------- +def downloadAllMinuteBar(start, end): + """下载所有配置中的合约的分钟线数据""" + print('-' * 50) + print(u'开始下载合约分钟线数据') + print('-' * 50) + + for symbol in SYMBOLS: + downMinuteBarBySymbol(symbol, '1MIN', start, end) + time.sleep(1) + + print('-' * 50) + print(u'合约分钟线数据下载完成') + print('-' * 50) + + + \ No newline at end of file diff --git a/examples/CoinapiDataService/downloadData.py b/examples/CoinapiDataService/downloadData.py new file mode 100644 index 00000000..797d4dbf --- /dev/null +++ b/examples/CoinapiDataService/downloadData.py @@ -0,0 +1,11 @@ +# encoding: UTF-8 + +""" +立即下载数据到数据库中,用于手动执行更新操作。 +""" + +from dataService import * + + +if __name__ == '__main__': + downMinuteBarBySymbol('BINANCE_SPOT_BTC_USDT', '1MIN', '20180725', '20180726') \ No newline at end of file diff --git a/examples/CoinapiDataService/runService.py b/examples/CoinapiDataService/runService.py new file mode 100644 index 00000000..4826e810 --- /dev/null +++ b/examples/CoinapiDataService/runService.py @@ -0,0 +1,33 @@ +# encoding: UTF-8 + +""" +定时服务,可无人值守运行,实现每日自动下载更新历史行情数据到数据库中。 +""" +from __future__ import print_function + +from dataService import * + + +if __name__ == '__main__': + taskCompletedDate = None + + taskTime = datetime.time(hour=22, minute=0) + + # 进入主循环 + while True: + t = datetime.datetime.now() + + # 每天到达任务下载时间后,执行数据下载的操作 + if t.time() > taskTime and (taskCompletedDate is None or t.date() != taskCompletedDate): + end = t.strftime('%Y%m%d') + start = (t - datetime.timedelta(1)).strftime('%Y%m%d') + + # 下载过去24小时的K线数据 + downloadAllMinuteBar(start, end) + + # 更新任务完成的日期 + taskCompletedDate = t.date() + else: + print(u'当前时间%s,任务定时%s' %(t, taskTime)) + + time.sleep(60) \ No newline at end of file diff --git a/examples/CryptoTrader/BIGONE_connect.json b/examples/CryptoTrader/BIGONE_connect.json new file mode 100644 index 00000000..87aab6bd --- /dev/null +++ b/examples/CryptoTrader/BIGONE_connect.json @@ -0,0 +1,5 @@ +{ + "apiKey": "", + "apiSecret": "", + "symbols": ["BTC-USDT", "ETH-USDT", "EOS-USDT"] +} \ No newline at end of file diff --git a/examples/CryptoTrader/BINANCE_connect.json b/examples/CryptoTrader/BINANCE_connect.json new file mode 100644 index 00000000..01d370e4 --- /dev/null +++ b/examples/CryptoTrader/BINANCE_connect.json @@ -0,0 +1,5 @@ +{ + "apiKey": "", + "secretKey": "", + "symbols": ["BTCUSDT", "ETHUSDT", "ETHBTC"] +} \ No newline at end of file diff --git a/examples/CryptoTrader/BITFINEX_connect.json b/examples/CryptoTrader/BITFINEX_connect.json new file mode 100644 index 00000000..ae25ff9c --- /dev/null +++ b/examples/CryptoTrader/BITFINEX_connect.json @@ -0,0 +1,5 @@ +{ + "apiKey": "", + "secretKey": "", + "symbols": ["BTCUSD", "ETHUSD", "ETHBTC"] +} \ No newline at end of file diff --git a/examples/CryptoTrader/BITMEX_connect.json b/examples/CryptoTrader/BITMEX_connect.json new file mode 100644 index 00000000..4aef1612 --- /dev/null +++ b/examples/CryptoTrader/BITMEX_connect.json @@ -0,0 +1,6 @@ +{ + "apiKey": "", + "apiSecret": "", + "sessionCount": 3, + "symbols": ["XBTUSD", "EOSM18", "XRPM18"] +} \ No newline at end of file diff --git a/examples/CryptoTrader/CCXT_connect.json b/examples/CryptoTrader/CCXT_connect.json new file mode 100644 index 00000000..cb367091 --- /dev/null +++ b/examples/CryptoTrader/CCXT_connect.json @@ -0,0 +1,6 @@ +{ + "exchange": "huobipro", + "apiKey": "", + "apiSecret": "", + "symbols": ["THETA/BTC", "BTC/USDT", "ETH/USDT"] +} \ No newline at end of file diff --git a/examples/CryptoTrader/COINBASE_connect.json b/examples/CryptoTrader/COINBASE_connect.json new file mode 100644 index 00000000..9926112b --- /dev/null +++ b/examples/CryptoTrader/COINBASE_connect.json @@ -0,0 +1,7 @@ +{ + "apiKey": "", + "secretKey": "", + "passphrase": "", + "sessionCount": 10, + "symbols": ["ETH-USD"] +} \ No newline at end of file diff --git a/examples/CryptoTrader/FCOIN_connect.json b/examples/CryptoTrader/FCOIN_connect.json new file mode 100644 index 00000000..00d51e2a --- /dev/null +++ b/examples/CryptoTrader/FCOIN_connect.json @@ -0,0 +1,5 @@ +{ + "apiKey": "", + "apiSecret": "", + "symbols": ["ethusdt"] +} \ No newline at end of file diff --git a/examples/CryptoTrader/HUOBI_connect.json b/examples/CryptoTrader/HUOBI_connect.json new file mode 100644 index 00000000..8255a46f --- /dev/null +++ b/examples/CryptoTrader/HUOBI_connect.json @@ -0,0 +1,6 @@ +{ + "exchange": "huobi", + "accessKey": "", + "secretKey": "", + "symbols": ["btcusdt","ethusdt","ethbtc"] +} \ No newline at end of file diff --git a/examples/CryptoTrader/LBANK_connect.json b/examples/CryptoTrader/LBANK_connect.json new file mode 100644 index 00000000..59286a19 --- /dev/null +++ b/examples/CryptoTrader/LBANK_connect.json @@ -0,0 +1,5 @@ +{ + "apiKey": "", + "secretKey": "", + "symbols": ["eth_usdt", "sc_btc", "btc_usdt"] +} diff --git a/examples/CryptoTrader/OKEX_connect.json b/examples/CryptoTrader/OKEX_connect.json new file mode 100644 index 00000000..5c77f027 --- /dev/null +++ b/examples/CryptoTrader/OKEX_connect.json @@ -0,0 +1,6 @@ +{ + "apiKey": "", + "secretKey": "", + "trace": false, + "symbols": ["eth_btc", "btc_usdt", "eth_usdt"] +} \ No newline at end of file diff --git a/examples/CryptoTrader/VT_setting.json b/examples/CryptoTrader/VT_setting.json new file mode 100644 index 00000000..40495eca --- /dev/null +++ b/examples/CryptoTrader/VT_setting.json @@ -0,0 +1,20 @@ +{ + "fontFamily": "微软雅黑", + "fontSize": 12, + + "mongoHost": "localhost", + "mongoPort": 27017, + "mongoLogging": true, + + "darkStyle": true, + "language": "chinese", + + "logActive": false, + "logLevel": "debug", + "logConsole": true, + "logFile": true, + + "tdPenalty": [], + + "maxDecimal": 10 +} \ No newline at end of file diff --git a/examples/CryptoTrader/algoBasket.csv b/examples/CryptoTrader/algoBasket.csv new file mode 100644 index 00000000..dfecd5b2 --- /dev/null +++ b/examples/CryptoTrader/algoBasket.csv @@ -0,0 +1,3 @@ +templateName,settingName,vtSymbol,direction,targetPrice,totalVolume,time,interval,priceLevel,minVolume +TWAP,BUY_BTC_TWAP,BTCUSD.BITFINEX,多,4000,10,200,10,3,1 +TWAP,SELL_BTC_TWAP,BTCUSD.BITFINEX,空,9000,10,200,10,3,1 diff --git a/examples/CryptoTrader/run.py b/examples/CryptoTrader/run.py new file mode 100644 index 00000000..ab6bdb77 --- /dev/null +++ b/examples/CryptoTrader/run.py @@ -0,0 +1,70 @@ +# encoding: UTF-8 + +# 重载sys模块,设置默认字符串编码方式为utf8 +try: + reload # Python 2 +except NameError: # Python 3 + from importlib import reload +import sys +reload(sys) +sys.setdefaultencoding('utf8') + +# 判断操作系统 +import platform +system = platform.system() + +# vn.trader模块 +from vnpy.event import EventEngine +from vnpy.trader.vtEngine import MainEngine +from vnpy.trader.uiQt import createQApp + +# 加载底层接口 +from vnpy.trader.gateway import (huobiGateway, okexGateway, + binanceGateway, bitfinexGateway, + bitmexGateway, fcoinGateway, + bigoneGateway, lbankGateway, + coinbaseGateway, ccxtGateway) + +# 加载上层应用 +from vnpy.trader.app import (algoTrading) + +# 当前目录组件 +from uiCryptoWindow import MainWindow + +#---------------------------------------------------------------------- +def main(): + """主程序入口""" + # 创建Qt应用对象 + qApp = createQApp() + + # 创建事件引擎 + ee = EventEngine() + + # 创建主引擎 + me = MainEngine(ee) + + # 添加交易接口 + me.addGateway(ccxtGateway) + me.addGateway(coinbaseGateway) + me.addGateway(lbankGateway) + me.addGateway(bigoneGateway) + me.addGateway(fcoinGateway) + me.addGateway(bitmexGateway) + me.addGateway(huobiGateway) + me.addGateway(okexGateway) + me.addGateway(binanceGateway) + me.addGateway(bitfinexGateway) + + # 添加上层应用 + me.addApp(algoTrading) + + # 创建主窗口 + mw = MainWindow(me, ee) + mw.showMaximized() + + # 在主线程中启动Qt事件循环 + sys.exit(qApp.exec_()) + + +if __name__ == '__main__': + main() diff --git a/examples/CryptoTrader/uiCryptoWidget.py b/examples/CryptoTrader/uiCryptoWidget.py new file mode 100644 index 00000000..c4b8e08a --- /dev/null +++ b/examples/CryptoTrader/uiCryptoWidget.py @@ -0,0 +1,1282 @@ +# encoding: UTF-8 + +import json +import csv +import os +import platform +from collections import OrderedDict + +from six import text_type + +from vnpy.event import * +from vnpy.trader import vtText +from vnpy.trader.vtEvent import * +from vnpy.trader.vtConstant import * +from vnpy.trader.vtFunction import * +from vnpy.trader.vtGateway import * +from vnpy.trader.uiQt import QtGui, QtWidgets, QtCore, BASIC_FONT + + +COLOR_RED = QtGui.QColor('red') +COLOR_GREEN = QtGui.QColor('green') + + +######################################################################## +class BasicCell(QtWidgets.QTableWidgetItem): + """基础的单元格""" + + #---------------------------------------------------------------------- + def __init__(self, text=None, mainEngine=None): + """Constructor""" + super(BasicCell, self).__init__() + self.data = None + + self.setTextAlignment(QtCore.Qt.AlignCenter) + + if text: + self.setContent(text) + + #---------------------------------------------------------------------- + def setContent(self, text): + """设置内容""" + if text == '0' or text == '0.0': + self.setText('') + else: + self.setText(text) + + +######################################################################## +class NumCell(BasicCell): + """用来显示数字的单元格""" + + #---------------------------------------------------------------------- + def __init__(self, text=None, mainEngine=None): + """Constructor""" + super(NumCell, self).__init__(text) + + #---------------------------------------------------------------------- + def setContent(self, text): + """设置内容""" + # 考虑到NumCell主要用来显示OrderID和TradeID之类的整数字段, + # 这里的数据转化方式使用int类型。但是由于部分交易接口的委托 + # 号和成交号可能不是纯数字的形式,因此补充了一个try...except + try: + num = int(text) + self.setData(QtCore.Qt.DisplayRole, num) + except ValueError: + self.setText(text) + + +######################################################################## +class DirectionCell(BasicCell): + """用来显示买卖方向的单元格""" + + #---------------------------------------------------------------------- + def __init__(self, text=None, mainEngine=None): + """Constructor""" + super(DirectionCell, self).__init__(text) + + #---------------------------------------------------------------------- + def setContent(self, text): + """设置内容""" + if text == DIRECTION_LONG or text == DIRECTION_NET: + self.setForeground(QtGui.QColor('red')) + elif text == DIRECTION_SHORT: + self.setForeground(QtGui.QColor('green')) + self.setText(text) + + +######################################################################## +class NameCell(BasicCell): + """用来显示合约中文的单元格""" + + #---------------------------------------------------------------------- + def __init__(self, text=None, mainEngine=None): + """Constructor""" + super(NameCell, self).__init__() + + self.mainEngine = mainEngine + self.data = None + + if text: + self.setContent(text) + + #---------------------------------------------------------------------- + def setContent(self, text): + """设置内容""" + if self.mainEngine: + # 首先尝试正常获取合约对象 + contract = self.mainEngine.getContract(text) + + # 如果能读取合约信息 + if contract: + self.setText(contract.name) + + +######################################################################## +class BidCell(BasicCell): + """买价单元格""" + + #---------------------------------------------------------------------- + def __init__(self, text=None, mainEngine=None): + """Constructor""" + super(BidCell, self).__init__(text) + + self.setForeground(QtGui.QColor('black')) + self.setBackground(QtGui.QColor(255,174,201)) + + #---------------------------------------------------------------------- + def setContent(self, text): + """设置内容""" + self.setText(text) + + +######################################################################## +class AskCell(BasicCell): + """卖价单元格""" + + #---------------------------------------------------------------------- + def __init__(self, text=None, mainEngine=None): + """Constructor""" + super(AskCell, self).__init__(text) + + self.setForeground(QtGui.QColor('black')) + self.setBackground(QtGui.QColor(160,255,160)) + + #---------------------------------------------------------------------- + def setContent(self, text): + """设置内容""" + self.setText(text) + + +######################################################################## +class PnlCell(BasicCell): + """显示盈亏的单元格""" + + #---------------------------------------------------------------------- + def __init__(self, text=None, mainEngine=None): + """Constructor""" + super(PnlCell, self).__init__() + self.data = None + self.color = '' + if text: + self.setContent(text) + + #---------------------------------------------------------------------- + def setContent(self, text): + """设置内容""" + self.setText(text) + + try: + value = float(text) + if value >= 0 and self.color != 'red': + self.color = 'red' + self.setForeground(COLOR_RED) + elif value < 0 and self.color != 'green': + self.color = 'green' + self.setForeground(COLOR_GREEN) + except ValueError: + pass + + +######################################################################## +class BasicMonitor(QtWidgets.QTableWidget): + """ + 基础监控 + + headerDict中的值对应的字典格式如下 + {'chinese': u'中文名', 'cellType': BasicCell} + + """ + signal = QtCore.Signal(type(Event())) + + #---------------------------------------------------------------------- + def __init__(self, mainEngine=None, eventEngine=None, parent=None): + """Constructor""" + super(BasicMonitor, self).__init__(parent) + + self.mainEngine = mainEngine + self.eventEngine = eventEngine + + # 保存表头标签用 + self.headerDict = OrderedDict() # 有序字典,key是英文名,value是对应的配置字典 + self.headerList = [] # 对应self.headerDict.keys() + + # 保存相关数据用 + self.dataDict = {} # 字典,key是字段对应的数据,value是保存相关单元格的字典 + self.dataKey = '' # 字典键对应的数据字段 + + # 监控的事件类型 + self.eventType = '' + + # 列宽调整状态(只在第一次更新数据时调整一次列宽) + self.columnResized = False + + # 字体 + self.font = None + + # 保存数据对象到单元格 + self.saveData = False + + # 默认不允许根据表头进行排序,需要的组件可以开启 + self.sorting = False + + # 默认表头可调整 + self.resizeMode = QtWidgets.QHeaderView.Interactive + + # 初始化右键菜单 + self.initMenu() + + #---------------------------------------------------------------------- + def setHeaderDict(self, headerDict): + """设置表头有序字典""" + self.headerDict = headerDict + self.headerList = headerDict.keys() + + #---------------------------------------------------------------------- + def setDataKey(self, dataKey): + """设置数据字典的键""" + self.dataKey = dataKey + + #---------------------------------------------------------------------- + def setEventType(self, eventType): + """设置监控的事件类型""" + self.eventType = eventType + + #---------------------------------------------------------------------- + def setFont(self, font): + """设置字体""" + self.font = font + + #---------------------------------------------------------------------- + def setSaveData(self, saveData): + """设置是否要保存数据到单元格""" + self.saveData = saveData + + #---------------------------------------------------------------------- + def initTable(self): + """初始化表格""" + # 设置表格的列数 + col = len(self.headerDict) + self.setColumnCount(col) + + # 设置列表头 + labels = [d['chinese'] for d in self.headerDict.values()] + self.setHorizontalHeaderLabels(labels) + + # 关闭左边的垂直表头 + self.verticalHeader().setVisible(False) + + # 设为不可编辑 + self.setEditTriggers(self.NoEditTriggers) + + # 设为行交替颜色 + self.setAlternatingRowColors(True) + + # 设置允许排序 + self.setSortingEnabled(self.sorting) + + # 设为表头拉伸 + self.horizontalHeader().setSectionResizeMode(self.resizeMode) + + #---------------------------------------------------------------------- + def registerEvent(self): + """注册GUI更新相关的事件监听""" + self.signal.connect(self.updateEvent) + self.eventEngine.register(self.eventType, self.signal.emit) + + #---------------------------------------------------------------------- + def updateEvent(self, event): + """收到事件更新""" + data = event.dict_['data'] + self.updateData(data) + + #---------------------------------------------------------------------- + def updateData(self, data): + """将数据更新到表格中""" + # 如果允许了排序功能,则插入数据前必须关闭,否则插入新的数据会变乱 + if self.sorting: + self.setSortingEnabled(False) + + # 如果设置了dataKey,则采用存量更新模式 + if self.dataKey: + key = data.__getattribute__(self.dataKey) + # 如果键在数据字典中不存在,则先插入新的一行,并创建对应单元格 + if key not in self.dataDict: + self.insertRow(0) + d = {} + for n, header in enumerate(self.headerList): + content = safeUnicode(data.__getattribute__(header)) + cellType = self.headerDict[header]['cellType'] + cell = cellType(content, self.mainEngine) + + if self.font: + cell.setFont(self.font) # 如果设置了特殊字体,则进行单元格设置 + + if self.saveData: # 如果设置了保存数据对象,则进行对象保存 + cell.data = data + + self.setItem(0, n, cell) + d[header] = cell + self.dataDict[key] = d + # 否则如果已经存在,则直接更新相关单元格 + else: + d = self.dataDict[key] + for header in self.headerList: + content = safeUnicode(data.__getattribute__(header)) + cell = d[header] + cell.setContent(content) + + if self.saveData: # 如果设置了保存数据对象,则进行对象保存 + cell.data = data + # 否则采用增量更新模式 + else: + self.insertRow(0) + for n, header in enumerate(self.headerList): + content = safeUnicode(data.__getattribute__(header)) + cellType = self.headerDict[header]['cellType'] + cell = cellType(content, self.mainEngine) + + if self.font: + cell.setFont(self.font) + + if self.saveData: + cell.data = data + + self.setItem(0, n, cell) + + ## 调整列宽 + #if not self.columnResized: + #self.resizeColumns() + #self.columnResized = True + + # 重新打开排序 + if self.sorting: + self.setSortingEnabled(True) + + #---------------------------------------------------------------------- + def resizeColumns(self): + """调整各列的大小""" + self.horizontalHeader().resizeSections(QtWidgets.QHeaderView.ResizeToContents) + + #---------------------------------------------------------------------- + def setSorting(self, sorting): + """设置是否允许根据表头排序""" + self.sorting = sorting + + #---------------------------------------------------------------------- + def setResizeMode(self, mode): + """""" + self.resizeMode = mode + + #---------------------------------------------------------------------- + def saveToCsv(self): + """保存表格内容到CSV文件""" + # 先隐藏右键菜单 + self.menu.close() + + # 获取想要保存的文件名 + path, fileType = QtWidgets.QFileDialog.getSaveFileName(self, vtText.SAVE_DATA, '', 'CSV(*.csv)') + + try: + #if not path.isEmpty(): + if path: + with open(unicode(path), 'wb') as f: + writer = csv.writer(f) + + # 保存标签 + headers = [header.encode('gbk') for header in self.headerList] + writer.writerow(headers) + + # 保存每行内容 + for row in range(self.rowCount()): + rowdata = [] + for column in range(self.columnCount()): + item = self.item(row, column) + if item is not None: + rowdata.append( + text_type(item.text()).encode('gbk')) + else: + rowdata.append('') + writer.writerow(rowdata) + except IOError: + pass + + #---------------------------------------------------------------------- + def initMenu(self): + """初始化右键菜单""" + self.menu = QtWidgets.QMenu(self) + + resizeAction = QtWidgets.QAction(vtText.RESIZE_COLUMNS, self) + resizeAction.triggered.connect(self.resizeColumns) + + saveAction = QtWidgets.QAction(vtText.SAVE_DATA, self) + saveAction.triggered.connect(self.saveToCsv) + + self.menu.addAction(resizeAction) + self.menu.addAction(saveAction) + + #---------------------------------------------------------------------- + def contextMenuEvent(self, event): + """右键点击事件""" + self.menu.popup(QtGui.QCursor.pos()) + + +######################################################################## +class MarketMonitor(BasicMonitor): + """市场监控组件""" + + #---------------------------------------------------------------------- + def __init__(self, mainEngine, eventEngine, parent=None): + """Constructor""" + super(MarketMonitor, self).__init__(mainEngine, eventEngine, parent) + + # 设置表头有序字典 + d = OrderedDict() + d['gatewayName'] = {'chinese':vtText.GATEWAY, 'cellType':BasicCell} + d['symbol'] = {'chinese':vtText.CONTRACT_SYMBOL, 'cellType':BasicCell} + d['lastPrice'] = {'chinese':vtText.LAST_PRICE, 'cellType':BasicCell} + d['volume'] = {'chinese':vtText.VOLUME, 'cellType':BasicCell} + d['openPrice'] = {'chinese':vtText.OPEN_PRICE, 'cellType':BasicCell} + d['highPrice'] = {'chinese':vtText.HIGH_PRICE, 'cellType':BasicCell} + d['lowPrice'] = {'chinese':vtText.LOW_PRICE, 'cellType':BasicCell} + d['bidPrice1'] = {'chinese':vtText.BID_PRICE_1, 'cellType':BidCell} + d['bidVolume1'] = {'chinese':vtText.BID_VOLUME_1, 'cellType':BidCell} + d['askPrice1'] = {'chinese':vtText.ASK_PRICE_1, 'cellType':AskCell} + d['askVolume1'] = {'chinese':vtText.ASK_VOLUME_1, 'cellType':AskCell} + d['time'] = {'chinese':vtText.TIME, 'cellType':BasicCell} + self.setHeaderDict(d) + + self.setDataKey('vtSymbol') + self.setEventType(EVENT_TICK) + self.setFont(BASIC_FONT) + self.setSorting(False) + self.setResizeMode(QtWidgets.QHeaderView.Stretch) + + self.initTable() + self.registerEvent() + + self.setFixedHeight(400) + + +######################################################################## +class LogMonitor(BasicMonitor): + """日志监控""" + signalError = QtCore.Signal(type(Event())) + + #---------------------------------------------------------------------- + def __init__(self, mainEngine, eventEngine, parent=None): + """Constructor""" + super(LogMonitor, self).__init__(mainEngine, eventEngine, parent) + + d = OrderedDict() + d['gatewayName'] = {'chinese':vtText.GATEWAY, 'cellType':BasicCell} + d['logTime'] = {'chinese':vtText.TIME, 'cellType':BasicCell} + d['logContent'] = {'chinese':vtText.CONTENT, 'cellType':BasicCell} + self.setHeaderDict(d) + + self.setEventType(EVENT_LOG) + self.setFont(BASIC_FONT) + self.initTable() + self.registerEvent() + + self.signalError.connect(self.processErrorEvent) + self.eventEngine.register(EVENT_ERROR, self.signalError.emit) + + self.horizontalHeader().setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch) + self.setFixedHeight(200) + + #---------------------------------------------------------------------- + def processErrorEvent(self, event): + """""" + error = event.dict_['data'] + logContent = u'发生错误,错误代码:%s,错误信息:%s' %(error.errorID, error.errorMsg) + + self.insertRow(0) + cellLogTime = BasicCell(error.errorTime) + cellLogContent = BasicCell(logContent) + cellGatewayName = BasicCell(error.gatewayName) + + self.setItem(0, 0, cellGatewayName) + self.setItem(0, 1, cellLogTime) + self.setItem(0, 2, cellLogContent) + + + +######################################################################## +class TradeMonitor(BasicMonitor): + """成交监控""" + + #---------------------------------------------------------------------- + def __init__(self, mainEngine, eventEngine, parent=None): + """Constructor""" + super(TradeMonitor, self).__init__(mainEngine, eventEngine, parent) + + d = OrderedDict() + d['gatewayName'] = {'chinese':vtText.GATEWAY, 'cellType':BasicCell} + d['tradeID'] = {'chinese':vtText.TRADE_ID, 'cellType':NumCell} + d['orderID'] = {'chinese':vtText.ORDER_ID, 'cellType':NumCell} + d['symbol'] = {'chinese':vtText.CONTRACT_SYMBOL, 'cellType':BasicCell} + d['direction'] = {'chinese':vtText.DIRECTION, 'cellType':DirectionCell} + d['price'] = {'chinese':vtText.PRICE, 'cellType':BasicCell} + d['volume'] = {'chinese':vtText.VOLUME, 'cellType':BasicCell} + d['tradeTime'] = {'chinese':vtText.TRADE_TIME, 'cellType':BasicCell} + self.setHeaderDict(d) + + self.setEventType(EVENT_TRADE) + self.setFont(BASIC_FONT) + self.setSorting(True) + self.setResizeMode(QtWidgets.QHeaderView.Stretch) + + self.initTable() + self.registerEvent() + + self.setFixedHeight(200) + + +######################################################################## +class OrderMonitor(BasicMonitor): + """委托监控""" + + #---------------------------------------------------------------------- + def __init__(self, mainEngine, eventEngine, parent=None): + """Constructor""" + super(OrderMonitor, self).__init__(mainEngine, eventEngine, parent) + + self.mainEngine = mainEngine + + d = OrderedDict() + d['gatewayName'] = {'chinese':vtText.GATEWAY, 'cellType':BasicCell} + d['orderID'] = {'chinese':vtText.ORDER_ID, 'cellType':NumCell} + d['symbol'] = {'chinese':vtText.CONTRACT_SYMBOL, 'cellType':BasicCell} + d['direction'] = {'chinese':vtText.DIRECTION, 'cellType':DirectionCell} + d['price'] = {'chinese':vtText.PRICE, 'cellType':BasicCell} + d['totalVolume'] = {'chinese':vtText.ORDER_VOLUME, 'cellType':BasicCell} + d['tradedVolume'] = {'chinese':vtText.TRADED_VOLUME, 'cellType':BasicCell} + d['status'] = {'chinese':vtText.ORDER_STATUS, 'cellType':BasicCell} + d['orderTime'] = {'chinese':vtText.ORDER_TIME, 'cellType':BasicCell} + self.setHeaderDict(d) + + self.setDataKey('vtOrderID') + self.setEventType(EVENT_ORDER) + self.setFont(BASIC_FONT) + self.setSaveData(True) + self.setSorting(True) + self.setResizeMode(QtWidgets.QHeaderView.Stretch) + + self.initTable() + self.registerEvent() + self.connectSignal() + + #---------------------------------------------------------------------- + def connectSignal(self): + """连接信号""" + # 双击单元格撤单 + self.itemDoubleClicked.connect(self.cancelOrder) + + #---------------------------------------------------------------------- + def cancelOrder(self, cell): + """根据单元格的数据撤单""" + order = cell.data + + req = VtCancelOrderReq() + req.symbol = order.symbol + req.exchange = order.exchange + req.frontID = order.frontID + req.sessionID = order.sessionID + req.orderID = order.orderID + self.mainEngine.cancelOrder(req, order.gatewayName) + + +######################################################################## +class PositionMonitor(BasicMonitor): + """持仓监控""" + #---------------------------------------------------------------------- + def __init__(self, mainEngine, eventEngine, parent=None): + """Constructor""" + super(PositionMonitor, self).__init__(mainEngine, eventEngine, parent) + + d = OrderedDict() + d['gatewayName'] = {'chinese':vtText.GATEWAY, 'cellType':BasicCell} + d['symbol'] = {'chinese':vtText.CONTRACT_SYMBOL, 'cellType':BasicCell} + d['direction'] = {'chinese':vtText.DIRECTION, 'cellType':DirectionCell} + d['position'] = {'chinese':vtText.POSITION, 'cellType':BasicCell} + d['frozen'] = {'chinese':vtText.FROZEN, 'cellType':BasicCell} + d['price'] = {'chinese':vtText.PRICE, 'cellType':BasicCell} + self.setHeaderDict(d) + + self.setDataKey('vtPositionName') + self.setEventType(EVENT_POSITION) + self.setFont(BASIC_FONT) + self.setSaveData(True) + self.setResizeMode(QtWidgets.QHeaderView.Stretch) + + self.initTable() + self.registerEvent() + + +######################################################################## +class AccountMonitor(BasicMonitor): + """账户监控""" + + #---------------------------------------------------------------------- + def __init__(self, mainEngine, eventEngine, parent=None): + """Constructor""" + super(AccountMonitor, self).__init__(mainEngine, eventEngine, parent) + + d = OrderedDict() + d['gatewayName'] = {'chinese':vtText.GATEWAY, 'cellType':BasicCell} + d['accountID'] = {'chinese':vtText.ACCOUNT_ID, 'cellType':BasicCell} + d['balance'] = {'chinese':vtText.BALANCE, 'cellType':BasicCell} + d['available'] = {'chinese':vtText.AVAILABLE, 'cellType':BasicCell} + self.setHeaderDict(d) + + self.setDataKey('vtAccountID') + self.setEventType(EVENT_ACCOUNT) + self.setFont(BASIC_FONT) + self.setSaveData(True) + self.setResizeMode(QtWidgets.QHeaderView.Stretch) + + self.initTable() + self.registerEvent() + + #---------------------------------------------------------------------- + def updateData(self, data): + """更新数据""" + super(AccountMonitor, self).updateData(data) + + # 如果该委托已完成,则隐藏该行 + vtAccountID = data.vtAccountID + cellDict = self.dataDict[vtAccountID] + cell = cellDict['balance'] + row = self.row(cell) + + if data.balance == 0: + self.hideRow(row) + else: + self.showRow(row) + + +######################################################################## +class DepthMonitor(QtWidgets.QTableWidget): + """报价深度监控""" + + #---------------------------------------------------------------------- + def __init__(self, mainEngine, eventEngine): + """Constructor""" + super(DepthMonitor, self).__init__() + + self.mainEngine = mainEngine + + self.contractSize = 1 # 合约乘数 + self.cellDict = {} + + self.initUi() + + #---------------------------------------------------------------------- + def initUi(self): + """""" + labels = [u'价格', + u'数量', + u'总额'] + + self.setColumnCount(len(labels)) + self.setHorizontalHeaderLabels(labels) + self.setRowCount(11) + self.verticalHeader().setVisible(False) + self.setEditTriggers(self.NoEditTriggers) + self.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch) + self.verticalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch) + + left = QtCore.Qt.AlignLeft + right = QtCore.Qt.AlignRight + + # 单元格 + askColor = 'green' + bidColor = 'red' + lastColor = 'orange' + + self.addCell('askPrice5', 0, 0, askColor) + self.addCell('askPrice4', 1, 0, askColor) + self.addCell('askPrice3', 2, 0, askColor) + self.addCell('askPrice2', 3, 0, askColor) + self.addCell('askPrice1', 4, 0, askColor) + self.addCell('lastPrice', 5, 0, lastColor) + self.addCell('bidPrice1', 6, 0, bidColor) + self.addCell('bidPrice2', 7, 0, bidColor) + self.addCell('bidPrice3', 8, 0, bidColor) + self.addCell('bidPrice4', 9, 0, bidColor) + self.addCell('bidPrice5', 10, 0, bidColor) + + self.addCell('askVolume5', 0, 1, askColor) + self.addCell('askVolume4', 1, 1, askColor) + self.addCell('askVolume3', 2, 1, askColor) + self.addCell('askVolume2', 3, 1, askColor) + self.addCell('askVolume1', 4, 1, askColor) + self.addCell('todayChange', 5, 1, lastColor) + self.addCell('bidVolume1', 6, 1, bidColor) + self.addCell('bidVolume2', 7, 1, bidColor) + self.addCell('bidVolume3', 8, 1, bidColor) + self.addCell('bidVolume4', 9, 1, bidColor) + self.addCell('bidVolume5', 10, 1, bidColor) + + self.addCell('askValue5', 0, 2, askColor) + self.addCell('askValue4', 1, 2, askColor) + self.addCell('askValue3', 2, 2, askColor) + self.addCell('askValue2', 3, 2, askColor) + self.addCell('askValue1', 4, 2, askColor) + self.addCell('bidValue1', 6, 2, bidColor) + self.addCell('bidValue2', 7, 2, bidColor) + self.addCell('bidValue3', 8, 2, bidColor) + self.addCell('bidValue4', 9, 2, bidColor) + self.addCell('bidValue5', 10, 2, bidColor) + + #---------------------------------------------------------------------- + def addCell(self, name, row, col, color, alignment=None): + """新增单元格""" + cell = QtWidgets.QTableWidgetItem() + self.setItem(row, col, cell) + self.cellDict[name] = cell + + if color: + cell.setForeground(QtGui.QColor(color)) + + if alignment: + cell.setTextAlignment(alignment) + else: + cell.setTextAlignment(QtCore.Qt.AlignCenter) + + #---------------------------------------------------------------------- + def updateCell(self, name, value, decimals=None, data=None): + """更新单元格""" + if decimals is not None: + text = '%.*f' %(decimals, value) + else: + text = '%s' %value + + cell = self.cellDict[name] + cell.setText(text) + + if data: + cell.price = data + + #---------------------------------------------------------------------- + def updateTick(self, tick): + """更新Tick""" + valueDecimals = 2 + + # bid + self.updateCell('bidPrice1', tick.bidPrice1, data=tick.bidPrice1) + self.updateCell('bidPrice2', tick.bidPrice2, data=tick.bidPrice2) + self.updateCell('bidPrice3', tick.bidPrice3, data=tick.bidPrice3) + self.updateCell('bidPrice4', tick.bidPrice4, data=tick.bidPrice4) + self.updateCell('bidPrice5', tick.bidPrice5, data=tick.bidPrice5) + + self.updateCell('bidVolume1', tick.bidVolume1, data=tick.bidPrice1) + self.updateCell('bidVolume2', tick.bidVolume2, data=tick.bidPrice2) + self.updateCell('bidVolume3', tick.bidVolume3, data=tick.bidPrice3) + self.updateCell('bidVolume4', tick.bidVolume4, data=tick.bidPrice4) + self.updateCell('bidVolume5', tick.bidVolume5, data=tick.bidPrice5) + + self.updateCell('bidValue1', tick.bidPrice1*tick.bidVolume1*self.contractSize, valueDecimals, data=tick.bidPrice1) + self.updateCell('bidValue2', tick.bidPrice2*tick.bidVolume2*self.contractSize, valueDecimals, data=tick.bidPrice2) + self.updateCell('bidValue3', tick.bidPrice3*tick.bidVolume3*self.contractSize, valueDecimals, data=tick.bidPrice3) + self.updateCell('bidValue4', tick.bidPrice4*tick.bidVolume4*self.contractSize, valueDecimals, data=tick.bidPrice4) + self.updateCell('bidValue5', tick.bidPrice5*tick.bidVolume5*self.contractSize, valueDecimals, data=tick.bidPrice5) + + # ask + self.updateCell('askPrice1', tick.askPrice1, data=tick.askPrice1) + self.updateCell('askPrice2', tick.askPrice2, data=tick.askPrice2) + self.updateCell('askPrice3', tick.askPrice3, data=tick.askPrice3) + self.updateCell('askPrice4', tick.askPrice4, data=tick.askPrice4) + self.updateCell('askPrice5', tick.askPrice5, data=tick.askPrice5) + + self.updateCell('askVolume1', tick.askVolume1, data=tick.askPrice1) + self.updateCell('askVolume2', tick.askVolume2, data=tick.askPrice2) + self.updateCell('askVolume3', tick.askVolume3, data=tick.askPrice3) + self.updateCell('askVolume4', tick.askVolume4, data=tick.askPrice4) + self.updateCell('askVolume5', tick.askVolume5, data=tick.askPrice5) + + self.updateCell('askValue1', tick.askPrice1*tick.askVolume1*self.contractSize, valueDecimals, data=tick.askPrice1) + self.updateCell('askValue2', tick.askPrice2*tick.askVolume2*self.contractSize, valueDecimals, data=tick.askPrice2) + self.updateCell('askValue3', tick.askPrice3*tick.askVolume3*self.contractSize, valueDecimals, data=tick.askPrice3) + self.updateCell('askValue4', tick.askPrice4*tick.askVolume4*self.contractSize, valueDecimals, data=tick.askPrice4) + self.updateCell('askValue5', tick.askPrice5*tick.askVolume5*self.contractSize, valueDecimals, data=tick.askPrice5) + + # today + self.updateCell('lastPrice', tick.lastPrice) + + if tick.openPrice: + todayChange = tick.lastPrice/tick.openPrice - 1 + else: + todayChange = 0 + + self.updateCell('todayChange', ('%.2f%%' %(todayChange*100))) + + #---------------------------------------------------------------------- + def updateVtSymbol(self, vtSymbol): + """更换显示行情标的""" + for cell in self.cellDict.values(): + cell.setText('') + + contract = self.mainEngine.getContract(vtSymbol) + if contract: + self.contractSize = contract.size + else: + self.contractSize = 1 + + +######################################################################## +class TradingWidget(QtWidgets.QFrame): + """简单交易组件""" + signal = QtCore.Signal(type(Event())) + + directionList = [DIRECTION_LONG, + DIRECTION_SHORT] + + priceTypeList = [PRICETYPE_LIMITPRICE, + PRICETYPE_MARKETPRICE] + + gatewayList = [''] + + #---------------------------------------------------------------------- + def __init__(self, mainEngine, eventEngine, parent=None): + """Constructor""" + super(TradingWidget, self).__init__(parent) + self.mainEngine = mainEngine + self.eventEngine = eventEngine + + self.vtSymbol = '' + + # 添加交易接口 + l = mainEngine.getAllGatewayDetails() + gatewayNameList = [d['gatewayName'] for d in l] + self.gatewayList.extend(gatewayNameList) + + self.initUi() + self.registerEvent() + + #---------------------------------------------------------------------- + def initUi(self): + """初始化界面""" + self.setWindowTitle(vtText.TRADING) + self.setFixedHeight(400) + self.setFixedWidth(600) + self.setFrameShape(self.Box) # 设置边框 + self.setLineWidth(1) + + # 左边部分 + labelPriceType = QtWidgets.QLabel(vtText.PRICE_TYPE) + labelSymbol = QtWidgets.QLabel(u'VT代码') + labelPrice = QtWidgets.QLabel(vtText.PRICE) + labelVolume = QtWidgets.QLabel(u'数量') + + self.comboPriceType = QtWidgets.QComboBox() + self.comboPriceType.addItems(self.priceTypeList) + + self.lineSymbol = QtWidgets.QLineEdit() + + validator = QtGui.QDoubleValidator() + validator.setBottom(0) + + self.linePrice = QtWidgets.QLineEdit() + self.linePrice.setValidator(validator) + + self.lineVolume = QtWidgets.QLineEdit() + self.lineVolume.setValidator(validator) + + gridLeft = QtWidgets.QGridLayout() + gridLeft.addWidget(labelPriceType, 0, 0) + gridLeft.addWidget(labelSymbol, 1, 0) + gridLeft.addWidget(labelPrice, 2, 0) + gridLeft.addWidget(labelVolume, 3, 0) + + gridLeft.addWidget(self.comboPriceType, 0, 1) + gridLeft.addWidget(self.lineSymbol, 1, 1) + gridLeft.addWidget(self.linePrice, 2, 1) + gridLeft.addWidget(self.lineVolume, 3, 1) + + # 右边部分 + self.depthMonitor = DepthMonitor(self.mainEngine, self.eventEngine) + + # 发单按钮 + buttonBuy = QtWidgets.QPushButton(u'买入') + buttonSell = QtWidgets.QPushButton(u'卖出') + buttonCancelAll = QtWidgets.QPushButton(vtText.CANCEL_ALL) + + size = buttonBuy.sizeHint() + buttonBuy.setMinimumHeight(size.height()*2) + buttonSell.setMinimumHeight(size.height()*2) + buttonCancelAll.setMinimumHeight(size.height()*2) + + buttonBuy.clicked.connect(self.sendBuyOrder) + buttonSell.clicked.connect(self.sendSellOrder) + buttonCancelAll.clicked.connect(self.cancelAll) + + buttonBuy.setStyleSheet('color:white;background-color:red') + buttonSell.setStyleSheet('color:white;background-color:green') + buttonCancelAll.setStyleSheet('color:black;background-color:yellow') + + gridButton = QtWidgets.QGridLayout() + gridButton.addWidget(buttonBuy, 0, 0) + gridButton.addWidget(buttonSell, 0, 1) + gridButton.addWidget(buttonCancelAll, 1, 0, 1, 2) + + # 整合布局 + vbox = QtWidgets.QVBoxLayout() + vbox.addLayout(gridLeft) + vbox.addLayout(gridButton) + + hbox = QtWidgets.QHBoxLayout() + hbox.addLayout(vbox) + hbox.addWidget(self.depthMonitor) + + self.setLayout(hbox) + + # 关联更新 + self.lineSymbol.returnPressed.connect(self.updateSymbol) + self.depthMonitor.itemDoubleClicked.connect(self.updatePrice) + + #---------------------------------------------------------------------- + def updateSymbol(self): + """合约变化""" + self.vtSymbol = str(self.lineSymbol.text()) + contract = self.mainEngine.getContract(self.vtSymbol) + + if not contract: + return + + # 清空价格数量 + self.linePrice.clear() + self.lineVolume.clear() + + self.depthMonitor.updateVtSymbol(self.vtSymbol) + + # 订阅合约 + req = VtSubscribeReq() + req.symbol = contract.symbol + self.mainEngine.subscribe(req, contract.gatewayName) + + #---------------------------------------------------------------------- + def updateTick(self, event): + """更新行情""" + tick = event.dict_['data'] + if tick.vtSymbol != self.vtSymbol: + return + self.depthMonitor.updateTick(tick) + + #---------------------------------------------------------------------- + def registerEvent(self): + """注册事件监听""" + self.signal.connect(self.updateTick) + self.eventEngine.register(EVENT_TICK, self.signal.emit) + + #---------------------------------------------------------------------- + def updatePrice(self, cell): + """""" + try: + price = cell.price + except AttributeError: + return + self.linePrice.setText(str(price)) + + #---------------------------------------------------------------------- + def sendOrder(self, direction): + """发单""" + vtSymbol = str(self.lineSymbol.text()) + contract = self.mainEngine.getContract(vtSymbol) + if not contract: + return + + # 获取价格 + priceText = self.linePrice.text() + if not priceText: + return + price = float(priceText) + + # 获取数量 + volumeText = self.lineVolume.text() + if not volumeText: + return + + if '.' in volumeText: + volume = float(volumeText) + else: + volume = int(volumeText) + + # 委托 + req = VtOrderReq() + req.symbol = contract.symbol + req.price = price + req.volume = volume + req.direction = direction + req.priceType = text_type(self.comboPriceType.currentText()) + + self.mainEngine.sendOrder(req, contract.gatewayName) + + #---------------------------------------------------------------------- + def sendBuyOrder(self): + """""" + self.sendOrder(DIRECTION_LONG) + + #---------------------------------------------------------------------- + def sendSellOrder(self): + """""" + self.sendOrder(DIRECTION_SHORT) + + #---------------------------------------------------------------------- + def cancelAll(self): + """一键撤销所有委托""" + l = self.mainEngine.getAllWorkingOrders() + for order in l: + req = VtCancelOrderReq() + req.symbol = order.symbol + req.exchange = order.exchange + req.frontID = order.frontID + req.sessionID = order.sessionID + req.orderID = order.orderID + self.mainEngine.cancelOrder(req, order.gatewayName) + + +######################################################################## +class ContractMonitor(BasicMonitor): + """合约查询""" + + #---------------------------------------------------------------------- + def __init__(self, mainEngine, parent=None): + """Constructor""" + super(ContractMonitor, self).__init__(parent=parent) + + self.mainEngine = mainEngine + + d = OrderedDict() + d['symbol'] = {'chinese':vtText.CONTRACT_SYMBOL, 'cellType':BasicCell} + d['exchange'] = {'chinese':vtText.EXCHANGE, 'cellType':BasicCell} + d['vtSymbol'] = {'chinese':vtText.VT_SYMBOL, 'cellType':BasicCell} + d['productClass'] = {'chinese':vtText.PRODUCT_CLASS, 'cellType':BasicCell} + d['size'] = {'chinese':vtText.CONTRACT_SIZE, 'cellType':BasicCell} + d['priceTick'] = {'chinese':vtText.PRICE_TICK, 'cellType':BasicCell} + self.setHeaderDict(d) + + # 过滤显示用的字符串 + self.filterContent = EMPTY_STRING + + self.initUi() + + #---------------------------------------------------------------------- + def initUi(self): + """初始化界面""" + self.setMinimumSize(800, 800) + self.setFont(BASIC_FONT) + self.setResizeMode(QtWidgets.QHeaderView.Stretch) + self.initTable() + self.addMenuAction() + + #---------------------------------------------------------------------- + def showAllContracts(self): + """显示所有合约数据""" + l = self.mainEngine.getAllContracts() + d = {'.'.join([contract.exchange, contract.symbol]):contract for contract in l} + l2 = list(d.keys()) + l2.sort(reverse=True) + + self.setRowCount(len(l2)) + row = 0 + + for key in l2: + # 如果设置了过滤信息且合约代码中不含过滤信息,则不显示 + if self.filterContent and self.filterContent not in key: + continue + + contract = d[key] + + for n, header in enumerate(self.headerList): + content = safeUnicode(contract.__getattribute__(header)) + cellType = self.headerDict[header]['cellType'] + cell = cellType(content) + + if self.font: + cell.setFont(self.font) # 如果设置了特殊字体,则进行单元格设置 + + self.setItem(row, n, cell) + + row = row + 1 + + #---------------------------------------------------------------------- + def refresh(self): + """刷新""" + self.menu.close() # 关闭菜单 + self.clearContents() + self.setRowCount(0) + self.showAllContracts() + + #---------------------------------------------------------------------- + def addMenuAction(self): + """增加右键菜单内容""" + refreshAction = QtWidgets.QAction(vtText.REFRESH, self) + refreshAction.triggered.connect(self.refresh) + + self.menu.addAction(refreshAction) + + #---------------------------------------------------------------------- + def show(self): + """显示""" + super(ContractMonitor, self).show() + self.refresh() + + #---------------------------------------------------------------------- + def setFilterContent(self, content): + """设置过滤字符串""" + self.filterContent = content + + +######################################################################## +class ContractManager(QtWidgets.QWidget): + """合约管理组件""" + + #---------------------------------------------------------------------- + def __init__(self, mainEngine, parent=None): + """Constructor""" + super(ContractManager, self).__init__(parent=parent) + + self.mainEngine = mainEngine + + self.initUi() + + #---------------------------------------------------------------------- + def initUi(self): + """初始化界面""" + self.setWindowTitle(vtText.CONTRACT_SEARCH) + + self.lineFilter = QtWidgets.QLineEdit() + self.buttonFilter = QtWidgets.QPushButton(vtText.SEARCH) + self.buttonFilter.clicked.connect(self.filterContract) + self.monitor = ContractMonitor(self.mainEngine) + self.monitor.refresh() + + hbox = QtWidgets.QHBoxLayout() + hbox.addWidget(self.lineFilter) + hbox.addWidget(self.buttonFilter) + hbox.addStretch() + + vbox = QtWidgets.QVBoxLayout() + vbox.addLayout(hbox) + vbox.addWidget(self.monitor) + + self.setLayout(vbox) + + #---------------------------------------------------------------------- + def filterContract(self): + """显示过滤后的合约""" + content = str(self.lineFilter.text()) + self.monitor.setFilterContent(content) + self.monitor.refresh() + + +######################################################################## +class WorkingOrderMonitor(OrderMonitor): + """活动委托监控""" + STATUS_COMPLETED = [STATUS_ALLTRADED, STATUS_CANCELLED, STATUS_REJECTED] + + #---------------------------------------------------------------------- + def __init__(self, mainEngine, eventEngine, parent=None): + """Constructor""" + super(WorkingOrderMonitor, self).__init__(mainEngine, eventEngine, parent) + + #---------------------------------------------------------------------- + def updateData(self, data): + """更新数据""" + super(WorkingOrderMonitor, self).updateData(data) + + # 如果该委托已完成,则隐藏该行 + if data.status in self.STATUS_COMPLETED: + vtOrderID = data.vtOrderID + cellDict = self.dataDict[vtOrderID] + cell = cellDict['status'] + row = self.row(cell) + self.hideRow(row) + + +######################################################################## +class SettingEditor(QtWidgets.QWidget): + """配置编辑器""" + + #---------------------------------------------------------------------- + def __init__(self, mainEngine, parent=None): + """Constructor""" + super(SettingEditor, self).__init__(parent) + + self.mainEngine = mainEngine + self.currentFileName = '' + + self.initUi() + + #---------------------------------------------------------------------- + def initUi(self): + """初始化界面""" + self.setWindowTitle(vtText.EDIT_SETTING) + + self.comboFileName = QtWidgets.QComboBox() + self.comboFileName.addItems(jsonPathDict.keys()) + + buttonLoad = QtWidgets.QPushButton(vtText.LOAD) + buttonSave = QtWidgets.QPushButton(vtText.SAVE) + buttonLoad.clicked.connect(self.loadSetting) + buttonSave.clicked.connect(self.saveSetting) + + self.editSetting = QtWidgets.QTextEdit() + self.labelPath = QtWidgets.QLabel() + + hbox = QtWidgets.QHBoxLayout() + hbox.addWidget(self.comboFileName) + hbox.addWidget(buttonLoad) + hbox.addWidget(buttonSave) + hbox.addStretch() + + vbox = QtWidgets.QVBoxLayout() + vbox.addLayout(hbox) + vbox.addWidget(self.editSetting) + vbox.addWidget(self.labelPath) + + self.setLayout(vbox) + + #---------------------------------------------------------------------- + def loadSetting(self): + """加载配置""" + self.currentFileName = str(self.comboFileName.currentText()) + filePath = jsonPathDict[self.currentFileName] + self.labelPath.setText(filePath) + + with open(filePath) as f: + self.editSetting.clear() + + for line in f: + line = line.replace('\n', '') # 移除换行符号 + line = line.decode('UTF-8') + self.editSetting.append(line) + + #---------------------------------------------------------------------- + def saveSetting(self): + """保存配置""" + if not self.currentFileName: + return + + filePath = jsonPathDict[self.currentFileName] + + with open(filePath, 'w') as f: + content = self.editSetting.toPlainText() + content = content.encode('UTF-8') + f.write(content) + + #---------------------------------------------------------------------- + def show(self): + """显示""" + # 更新配置文件下拉框 + self.comboFileName.clear() + self.comboFileName.addItems(jsonPathDict.keys()) + + # 显示界面 + super(SettingEditor, self).show() + + + diff --git a/examples/CryptoTrader/uiCryptoWindow.py b/examples/CryptoTrader/uiCryptoWindow.py new file mode 100644 index 00000000..b491563e --- /dev/null +++ b/examples/CryptoTrader/uiCryptoWindow.py @@ -0,0 +1,333 @@ +# encoding: UTF-8 + +import psutil +import traceback + +from vnpy.trader.vtFunction import loadIconPath +from vnpy.trader.vtGlobal import globalSetting + +from uiCryptoWidget import * + + +######################################################################## +class MainWindow(QtWidgets.QMainWindow): + """主窗口""" + + signalStatusBar = QtCore.Signal(type(Event())) + + #---------------------------------------------------------------------- + def __init__(self, mainEngine, eventEngine): + """Constructor""" + super(MainWindow, self).__init__() + + self.mainEngine = mainEngine + self.eventEngine = eventEngine + + l = self.mainEngine.getAllGatewayDetails() + self.gatewayNameList = [d['gatewayName'] for d in l] + + self.widgetDict = {} # 用来保存子窗口的字典 + + # 获取主引擎中的上层应用信息 + self.appDetailList = self.mainEngine.getAllAppDetails() + + self.initUi() + self.loadWindowSettings('custom') + + #---------------------------------------------------------------------- + def initUi(self): + """初始化界面""" + self.setWindowTitle('VnTrader') + self.initCentral() + self.initMenu() + self.initStatusBar() + + #---------------------------------------------------------------------- + def initCentral(self): + """初始化中心区域""" + widgetTradingW, dockTradingW = self.createDock(TradingWidget, vtText.TRADING, QtCore.Qt.RightDockWidgetArea) + widgetMarketM, dockMarketM = self.createDock(MarketMonitor, vtText.MARKET_DATA, QtCore.Qt.LeftDockWidgetArea) + + widgetOrderM, dockOrderM = self.createDock(OrderMonitor, vtText.ORDER, QtCore.Qt.LeftDockWidgetArea) + widgetWorkingOrderM, dockWorkingOrderM = self.createDock(WorkingOrderMonitor, vtText.WORKING_ORDER, QtCore.Qt.LeftDockWidgetArea) + widgetTradeM, dockTradeM = self.createDock(TradeMonitor, vtText.TRADE, QtCore.Qt.LeftDockWidgetArea) + + widgetAccountM, dockAccountM = self.createDock(AccountMonitor, vtText.ACCOUNT, QtCore.Qt.RightDockWidgetArea) + widgetPositionM, dockPositionM = self.createDock(PositionMonitor, vtText.POSITION, QtCore.Qt.RightDockWidgetArea) + widgetLogM, dockLogM = self.createDock(LogMonitor, vtText.LOG, QtCore.Qt.RightDockWidgetArea) + + self.tabifyDockWidget(dockOrderM, dockWorkingOrderM) + self.tabifyDockWidget(dockPositionM, dockAccountM) + + # 保存默认设置 + self.saveWindowSettings('default') + + #---------------------------------------------------------------------- + def initMenu(self): + """初始化菜单""" + # 创建菜单 + menubar = self.menuBar() + + # 设计为只显示存在的接口 + gatewayDetails = self.mainEngine.getAllGatewayDetails() + + sysMenu = menubar.addMenu(vtText.SYSTEM) + + for d in gatewayDetails: + if d['gatewayType'] == GATEWAYTYPE_FUTURES: + self.addConnectAction(sysMenu, d['gatewayName'], d['gatewayDisplayName']) + sysMenu.addSeparator() + + for d in gatewayDetails: + if d['gatewayType'] == GATEWAYTYPE_EQUITY: + self.addConnectAction(sysMenu, d['gatewayName'], d['gatewayDisplayName']) + sysMenu.addSeparator() + + for d in gatewayDetails: + if d['gatewayType'] == GATEWAYTYPE_INTERNATIONAL: + self.addConnectAction(sysMenu, d['gatewayName'], d['gatewayDisplayName']) + sysMenu.addSeparator() + + for d in gatewayDetails: + if d['gatewayType'] == GATEWAYTYPE_BTC: + self.addConnectAction(sysMenu, d['gatewayName'], d['gatewayDisplayName']) + sysMenu.addSeparator() + + for d in gatewayDetails: + if d['gatewayType'] == GATEWAYTYPE_DATA: + self.addConnectAction(sysMenu, d['gatewayName'], d['gatewayDisplayName']) + + sysMenu.addSeparator() + sysMenu.addAction(self.createAction(vtText.CONNECT_DATABASE, self.mainEngine.dbConnect, loadIconPath('database.ico'))) + sysMenu.addSeparator() + sysMenu.addAction(self.createAction(vtText.EXIT, self.close, loadIconPath('exit.ico'))) + + # 功能应用 + appMenu = menubar.addMenu(vtText.APPLICATION) + + for appDetail in self.appDetailList: + # 如果没有应用界面,则不添加菜单按钮 + if not appDetail['appWidget']: + continue + + function = self.createOpenAppFunction(appDetail) + action = self.createAction(appDetail['appDisplayName'], function, loadIconPath(appDetail['appIco'])) + appMenu.addAction(action) + + # 帮助 + helpMenu = menubar.addMenu(vtText.HELP) + helpMenu.addAction(self.createAction(vtText.CONTRACT_SEARCH, self.openContract, loadIconPath('contract.ico'))) + helpMenu.addAction(self.createAction(vtText.EDIT_SETTING, self.openSettingEditor, loadIconPath('editor.ico'))) + helpMenu.addSeparator() + helpMenu.addAction(self.createAction(vtText.RESTORE, self.restoreWindow, loadIconPath('restore.ico'))) + helpMenu.addAction(self.createAction(vtText.ABOUT, self.openAbout, loadIconPath('about.ico'))) + helpMenu.addSeparator() + helpMenu.addAction(self.createAction(vtText.TEST, self.test, loadIconPath('test.ico'))) + + #---------------------------------------------------------------------- + def initStatusBar(self): + """初始化状态栏""" + self.statusLabel = QtWidgets.QLabel() + self.statusLabel.setAlignment(QtCore.Qt.AlignLeft) + + self.statusBar().addPermanentWidget(self.statusLabel) + self.statusLabel.setText(self.getCpuMemory()) + + self.sbCount = 0 + self.sbTrigger = 10 # 10秒刷新一次 + self.signalStatusBar.connect(self.updateStatusBar) + self.eventEngine.register(EVENT_TIMER, self.signalStatusBar.emit) + + #---------------------------------------------------------------------- + def updateStatusBar(self, event): + """在状态栏更新CPU和内存信息""" + self.sbCount += 1 + + if self.sbCount == self.sbTrigger: + self.sbCount = 0 + self.statusLabel.setText(self.getCpuMemory()) + + #---------------------------------------------------------------------- + def getCpuMemory(self): + """获取CPU和内存状态信息""" + cpuPercent = psutil.cpu_percent() + memoryPercent = psutil.virtual_memory().percent + return vtText.CPU_MEMORY_INFO.format(cpu=cpuPercent, memory=memoryPercent) + + #---------------------------------------------------------------------- + def addConnectAction(self, menu, gatewayName, displayName=''): + """增加连接功能""" + if gatewayName not in self.gatewayNameList: + return + + def connect(): + self.mainEngine.connect(gatewayName) + + if not displayName: + displayName = gatewayName + + actionName = vtText.CONNECT + displayName + connectAction = self.createAction(actionName, connect, + loadIconPath('connect.ico')) + menu.addAction(connectAction) + + #---------------------------------------------------------------------- + def createAction(self, actionName, function, iconPath=''): + """创建操作功能""" + action = QtWidgets.QAction(actionName, self) + action.triggered.connect(function) + + if iconPath: + icon = QtGui.QIcon(iconPath) + action.setIcon(icon) + + return action + + #---------------------------------------------------------------------- + def createOpenAppFunction(self, appDetail): + """创建打开应用UI的函数""" + def openAppFunction(): + appName = appDetail['appName'] + try: + self.widgetDict[appName].show() + except KeyError: + appEngine = self.mainEngine.getApp(appName) + self.widgetDict[appName] = appDetail['appWidget'](appEngine, self.eventEngine) + self.widgetDict[appName].show() + + return openAppFunction + + #---------------------------------------------------------------------- + def test(self): + """测试按钮用的函数""" + # 有需要使用手动触发的测试函数可以写在这里 + pass + + #---------------------------------------------------------------------- + def openAbout(self): + """打开关于""" + try: + self.widgetDict['aboutW'].show() + except KeyError: + self.widgetDict['aboutW'] = AboutWidget(self) + self.widgetDict['aboutW'].show() + + #---------------------------------------------------------------------- + def openContract(self): + """打开合约查询""" + try: + self.widgetDict['contractM'].show() + except KeyError: + self.widgetDict['contractM'] = ContractManager(self.mainEngine) + self.widgetDict['contractM'].show() + + #---------------------------------------------------------------------- + def openSettingEditor(self): + """打开配置编辑""" + try: + self.widgetDict['settingEditor'].show() + except KeyError: + self.widgetDict['settingEditor'] = SettingEditor(self.mainEngine) + self.widgetDict['settingEditor'].show() + + #---------------------------------------------------------------------- + def closeEvent(self, event): + """关闭事件""" + reply = QtWidgets.QMessageBox.question(self, vtText.EXIT, + vtText.CONFIRM_EXIT, QtWidgets.QMessageBox.Yes | + QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No) + + if reply == QtWidgets.QMessageBox.Yes: + for widget in self.widgetDict.values(): + widget.close() + self.saveWindowSettings('custom') + + self.mainEngine.exit() + event.accept() + else: + event.ignore() + + #---------------------------------------------------------------------- + def createDock(self, widgetClass, widgetName, widgetArea): + """创建停靠组件""" + widget = widgetClass(self.mainEngine, self.eventEngine) + dock = QtWidgets.QDockWidget(widgetName) + dock.setWidget(widget) + dock.setObjectName(widgetName) + dock.setFeatures(dock.DockWidgetFloatable|dock.DockWidgetMovable) + self.addDockWidget(widgetArea, dock) + return widget, dock + + #---------------------------------------------------------------------- + def saveWindowSettings(self, settingName): + """保存窗口设置""" + settings = QtCore.QSettings('vn.trader', settingName) + settings.setValue('state', self.saveState()) + settings.setValue('geometry', self.saveGeometry()) + + #---------------------------------------------------------------------- + def loadWindowSettings(self, settingName): + """载入窗口设置""" + settings = QtCore.QSettings('vn.trader', settingName) + state = settings.value('state') + geometry = settings.value('geometry') + + # 尚未初始化 + if state is None: + return + # 老版PyQt + elif isinstance(state, QtCore.QVariant): + self.restoreState(state.toByteArray()) + self.restoreGeometry(geometry.toByteArray()) + # 新版PyQt + elif isinstance(state, QtCore.QByteArray): + self.restoreState(state) + self.restoreGeometry(geometry) + # 异常 + else: + content = u'载入窗口配置异常,请检查' + self.mainEngine.writeLog(content) + + #---------------------------------------------------------------------- + def restoreWindow(self): + """还原默认窗口设置(还原停靠组件位置)""" + self.loadWindowSettings('default') + self.showMaximized() + + +######################################################################## +class AboutWidget(QtWidgets.QDialog): + """显示关于信息""" + + #---------------------------------------------------------------------- + def __init__(self, parent=None): + """Constructor""" + super(AboutWidget, self).__init__(parent) + + self.initUi() + + #---------------------------------------------------------------------- + def initUi(self): + """""" + self.setWindowTitle(vtText.ABOUT + 'VnTrader') + + text = u""" + Developed by Traders, for Traders. + + License:MIT + + Website:www.vnpy.org + + Github:www.github.com/vnpy/vnpy + + """ + + label = QtWidgets.QLabel() + label.setText(text) + label.setMinimumWidth(500) + + vbox = QtWidgets.QVBoxLayout() + vbox.addWidget(label) + + self.setLayout(vbox) + \ No newline at end of file diff --git a/examples/CtaBacktesting/.ipynb_checkpoints/backtesting_IF-checkpoint.ipynb b/examples/CtaBacktesting/.ipynb_checkpoints/backtesting_IF-checkpoint.ipynb index 89681046..631c60be 100644 --- a/examples/CtaBacktesting/.ipynb_checkpoints/backtesting_IF-checkpoint.ipynb +++ b/examples/CtaBacktesting/.ipynb_checkpoints/backtesting_IF-checkpoint.ipynb @@ -3,9 +3,7 @@ { "cell_type": "code", "execution_count": 1, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "%matplotlib inline\n", @@ -19,9 +17,7 @@ { "cell_type": "code", "execution_count": 2, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# 创建回测引擎对象\n", @@ -30,24 +26,30 @@ }, { "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": false - }, + "execution_count": null, + "metadata": {}, "outputs": [], "source": [ - "# 设置回测使用的数据\n", + "# 使用历史数据缓存服务器,请先运行startHds.py\n", + "engine.initHdsClient() # 受限于机器内存,超出上限会报错" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# 设置回测使用的数据 \n", "engine.setBacktestingMode(engine.BAR_MODE) # 设置引擎的回测模式为K线\n", "engine.setDatabase(MINUTE_DB_NAME, 'IF0000') # 设置使用的历史数据库\n", - "engine.setStartDate('20130101') # 设置回测用的数据起始日期" + "engine.setStartDate('20150101') # 设置回测用的数据起始日期" ] }, { "cell_type": "code", "execution_count": 4, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# 配置回测引擎参数\n", @@ -61,9 +63,7 @@ { "cell_type": "code", "execution_count": 5, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# 在引擎中创建策略对象\n", @@ -76,21 +76,19 @@ { "cell_type": "code", "execution_count": 6, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "2018-02-16 22:10:48.332000\t开始载入数据\n", - "2018-02-16 22:10:48.417000\t载入完成,数据量:91381\n", - "2018-02-16 22:10:48.418000\t开始回测\n", - "2018-02-16 22:10:48.463000\t策略初始化完成\n", - "2018-02-16 22:10:48.463000\t策略启动完成\n", - "2018-02-16 22:10:48.463000\t开始回放数据\n", - "2018-02-16 22:10:56.057000\t数据回放结束\n" + "2018-08-06 15:01:09.708000\t开始载入数据\n", + "2018-08-06 15:01:28.454000\t载入完成,数据量:155070\n", + "2018-08-06 15:01:28.454000\t开始回测\n", + "2018-08-06 15:01:28.504000\t策略初始化完成\n", + "2018-08-06 15:01:28.504000\t策略启动完成\n", + "2018-08-06 15:01:28.504000\t开始回放数据\n", + "2018-08-06 15:01:39.480000\t数据回放结束\n" ] } ], @@ -103,7 +101,6 @@ "cell_type": "code", "execution_count": 7, "metadata": { - "collapsed": false, "scrolled": false }, "outputs": [ @@ -111,39 +108,39 @@ "name": "stdout", "output_type": "stream", "text": [ - "2018-02-16 22:10:56.062000\t计算按日统计结果\n", - "2018-02-16 22:10:56.304000\t------------------------------\n", - "2018-02-16 22:10:56.305000\t首个交易日:\t2013-01-11\n", - "2018-02-16 22:10:56.305000\t最后交易日:\t2014-06-04\n", - "2018-02-16 22:10:56.305000\t总交易日:\t334\n", - "2018-02-16 22:10:56.305000\t盈利交易日\t167\n", - "2018-02-16 22:10:56.305000\t亏损交易日:\t167\n", - "2018-02-16 22:10:56.305000\t起始资金:\t1000000\n", - "2018-02-16 22:10:56.305000\t结束资金:\t1,283,473.32\n", - "2018-02-16 22:10:56.305000\t总收益率:\t28.35%\n", - "2018-02-16 22:10:56.305000\t年化收益:\t20.37%\n", - "2018-02-16 22:10:56.305000\t总盈亏:\t283,473.32\n", - "2018-02-16 22:10:56.305000\t最大回撤: \t-101,191.41\n", - "2018-02-16 22:10:56.305000\t百分比最大回撤: -9.23%\n", - "2018-02-16 22:10:56.305000\t总手续费:\t33,266.68\n", - "2018-02-16 22:10:56.305000\t总滑点:\t94,260.0\n", - "2018-02-16 22:10:56.305000\t总成交金额:\t1,108,889,460.0\n", - "2018-02-16 22:10:56.305000\t总成交笔数:\t1,571.0\n", - "2018-02-16 22:10:56.305000\t日均盈亏:\t848.72\n", - "2018-02-16 22:10:56.305000\t日均手续费:\t99.6\n", - "2018-02-16 22:10:56.305000\t日均滑点:\t282.22\n", - "2018-02-16 22:10:56.305000\t日均成交金额:\t3,320,028.32\n", - "2018-02-16 22:10:56.305000\t日均成交笔数:\t4.7\n", - "2018-02-16 22:10:56.305000\t日均收益率:\t0.07%\n", - "2018-02-16 22:10:56.305000\t收益标准差:\t0.87%\n", - "2018-02-16 22:10:56.305000\tSharpe Ratio:\t1.3\n" + "2018-08-06 15:02:11.279000\t计算按日统计结果\n", + "2018-08-06 15:02:11.334000\t------------------------------\n", + "2018-08-06 15:02:11.334000\t首个交易日:\t2015-01-12\n", + "2018-08-06 15:02:11.334000\t最后交易日:\t2017-07-14\n", + "2018-08-06 15:02:11.334000\t总交易日:\t612\n", + "2018-08-06 15:02:11.334000\t盈利交易日\t297\n", + "2018-08-06 15:02:11.334000\t亏损交易日:\t315\n", + "2018-08-06 15:02:11.334000\t起始资金:\t1000000\n", + "2018-08-06 15:02:11.334000\t结束资金:\t1,133,650.64\n", + "2018-08-06 15:02:11.335000\t总收益率:\t13.37%\n", + "2018-08-06 15:02:11.335000\t年化收益:\t5.24%\n", + "2018-08-06 15:02:11.335000\t总盈亏:\t133,650.64\n", + "2018-08-06 15:02:11.335000\t最大回撤: \t-185,949.45\n", + "2018-08-06 15:02:11.335000\t百分比最大回撤: -16.31%\n", + "2018-08-06 15:02:11.335000\t总手续费:\t148,769.36\n", + "2018-08-06 15:02:11.335000\t总滑点:\t267,420.0\n", + "2018-08-06 15:02:11.335000\t总成交金额:\t4,958,978,520.0\n", + "2018-08-06 15:02:11.335000\t总成交笔数:\t4,457.0\n", + "2018-08-06 15:02:11.336000\t日均盈亏:\t218.38\n", + "2018-08-06 15:02:11.336000\t日均手续费:\t243.09\n", + "2018-08-06 15:02:11.336000\t日均滑点:\t436.96\n", + "2018-08-06 15:02:11.336000\t日均成交金额:\t8,102,906.08\n", + "2018-08-06 15:02:11.336000\t日均成交笔数:\t7.28\n", + "2018-08-06 15:02:11.336000\t日均收益率:\t0.02%\n", + "2018-08-06 15:02:11.336000\t收益标准差:\t1.72%\n", + "2018-08-06 15:02:11.336000\tSharpe Ratio:\t0.18\n" ] }, { "data": { - "image/png": "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\nNYuJEyee13tuDEmSGt1eXl7O2LFjWblyJTabjfXr1/uVa819wUuwPhGsXXbv3s2GDRt44403WL58\nOcePHw8oExMTA8DWrVt59dVXmTFjRtD8oN72GDVqVEAdKpWKHTt2MHbsWGJjY4mLiztXt9ooxcXF\nJCcnB2xPTEzk0KFDPPnkk9x2222sXr2a6dOns2fPHm699VbGjBnD0KFDfeUlSSIqKgqAlStXUlVV\nRb9+/QLaQa1W43a7SUpKCrr0pUqlYtWqVeTn5/t9By8G0dHRlJWVsX79eg4ePBjwzJw3b55v+nbw\n4MH8+OOPvmMvh/a41MaPBQsWMHPmTGw223m+c38uhfFj06ZN7N+/n+uvv/68329TXArjx7nuCy1K\nEdMayM7OZsWKFX7b/vWvf/HNN98wffp0ZFnG5XJx6NAhAHr27Akolor6/ziAPXv2cPfddzN37lx6\n9uxJeXl50ByIjaHRaHjnnXfYtGkT9957LytXrjzb22yU06dPs379ekpLS1m5ciXl5eW88sorSJLk\ne9Pq3r07H330EaCs/hIM71rRFouFefPmAQTN9dgUv/nNb7j11luZPHkyeXl5QQfg80FlZSV6vR61\nWo0sywFf5Ppv/bm5uYCyQk0wP5jW2Be8NNQn6uJtj7179/oGZ71eT6dOnYLW+e677/Lcc8/x/PPP\nExkZidlsbnZ7dOvWjY8//pg//OEPPP/88751w88n7dq18w1qddm/fz8JCQns27ePbt26AfhE35tv\nvhm0LlmWeeqpp9i/f7/Pilr3OwLgdrtRqRp/7546dSo33HADv/rVr9i8eTO9evVq0b2dLYcPHyYu\nLo7vv/+eb7/9NuCZefz4cd/zIthLXWtvj0tp/CgpKaGoqIgDBw4gyzInT57krrvuOu/+s5fK+PH3\nv/+dI0eO+GYrdu3aRUxMDDk5OefydhvkUho/zkdfuGwtgcHM+RkZGfTu3ZsVK1awYsUKRo4c6RMi\n3sFgy5YtZGdn+x33ww8/cOedd/L0008zYMAAAL+8hrIss2HDhkbzFD788MN8+eWXgGIZaerhdy54\n6623mDRpEi+++CIvvPACa9asYePGjZSVlfmmYoqKinz329A1zZo1i9zcXObPn+/7AvTo0YN169YB\n+HI9NsQXX3zBggULANBqtWi12gty/17uu+8+ioqKfEsORkVF+d7GAF9bhEJr7QteGuoTarWakpIS\nAHbt2gVAVlYWX3/9NaA8yL3b69e3atUqVq5cSWJiIgBdu3alqKgIu93OmTNnfPlBG2Lq1KmcPn0a\nUN6CG3rbPtdcddVVbNq0yXePoFg+oqKiSEpK8rv/t99+m1WrViFJEi6XK6CuBx98EIfDwfLly33T\noHW/I9u3b6d9+/YNXsu+ffv49a9/DSjWE51Od0H7Rd3nZXl5OWvXrmXkyJENPjNtNhsHDhwAlMCB\nDz/80K++y6k9vFys8cNms/Hee++xYsUKVq5cSURExAUJoLpUxo9Fixbx6quvsnLlSgYOHMg999xz\nwQQgXFrjx/noC5etJTDYQDJs2DA2b97M1KlTqaqq4uqrr/YNOh9++CEvvfQSFouFJ5980u+4xYsX\nY7fbefTRR5FlGavVyrJly5g/fz533323L69hsCkNL14/iuXLl6NSqXxvROeTf/zjHzz11FO+z2Fh\nYYwYMYK///3vvPnmm/ztb3/DaDTy1FNP+Xy26vPhhx+yZcsWHA4H69atQ5Ik7rrrLqZMmcLcuXMp\nKCjw5XpsiF69evH+++8zZcoUZFmmoKDAJxguBDNnzuSRRx5BkiRGjhyJ1Wpl9OjR3HnnnWzevNnP\nwlW33wTrQ621L3gJ1ieuueYa4uLiWLBgAfHx8bRr1w6A9u3bM2jQICZPnkxkZCRarRaNpvaR4Xa7\neeyxx0hISOD2229HkiR69erFHXfcQWFhIQUFBciyzJw5c3xCIBg33XQTN998MzqdDpvNxsKFC89f\nA9TBaDTyzDPP8Nhjj3Hq1ClcLhcdOnRg8eLFANxzzz089NBDPPPMMxgMBn7/+99z6NAhnnvuOTp1\n6sTo0aMBRTS/8cYb5OXlUVhYiCRJTJ8+neHDh7Nx40afD9Djjz/e4LWkp6eTk5PDDTfcgCRJDBo0\nqNGB8Vzz5ZdfMn36dFQqlS86My0tjbS0tKDPzIcffpj7778flUqFzWZjxowZvrouh/a41MaPi8Gl\nMn5cbC618eNcI/IEtkEKCwtZsGBBg+Z7gQCgtLSU999/n4KCAux2O2PHjuXll1++YD57AoHg0kOM\nH5cXl60lUNAwF2q6TdC6iYyM5Ouvv2bSpEmoVCquv/56IQAFgjaOGD8uL4QlUCAQCAQCgaANctkG\nhggEAoFAIBAIGkaIQIFAIBAIBII2iBCBAoFAIBAIBG0QIQIFAoFAIBAI2iBCBAoEAgFKQuxhw4Y1\nuH/NmjVBE0ULBAJBa0WIQIFAIICgS0LV5dlnnxUiUCAQXFaIPIECgaDNUllZyd13382ZM2d8kod2\nWAAAIABJREFUS4B99dVXLF26FFmWqaysZNGiRXz11VccP36cOXPmsHTpUhYvXkxRUREul4sZM2Yw\ncuTIi3wnAoFA0HyEJVAgELRZVq9eTfv27Vm5ciX5+fnIsswPP/zA008/zYoVKxg+fDjvv/8+kyZN\nIjY2liVLlrB+/XoOHTrEqlWrWLFiBc8++6zfAvACgUDQWhCWQIFA0Gb56aefGDJkCABdu3ZFq9Vi\ns9l45JFHMJlMHD16lB49egDKdLEsy3z//fd88803TJ8+HVmWcblcHDx48IIuai8QCATnAiECBQJB\nmyUzM5Nt27YxbNgwdu3ahcPh4KGHHuK///0vRqOR++67z1dWrVbjdrvJyMigd+/eLFiwAFmWWb58\nOSkpKRfxLgQCgaBliOlggUDQZpkyZQrFxcVMnTqV1157Db1ez7hx4ygoKKCgoIDKykpKSkoAyMvL\n45ZbbmHYsGEYjUamTp3KxIkTkSQJo9F4ke9EIBAImo9YO1ggEAgEAoGgDSKmgwUCQavk0KFDDB8+\nnA4dOgDgcrnQarUUFhYyfvz483ruv/71r/zvf//j8ccfP6/nEQgEgvOJEIECgaDVEhYWxptvvun7\nfPjwYWbMmIHJZGL48OEX8coEAoHg0keIQIFAcNmQkJDA7NmzeeGFF/j44485efIkBw8eZMiQIUyc\nOJEFCxb4/Pxyc3NZsmQJixYtwmAwcOedd3Ls2DEGDRrESy+9RO/evXn77bf5+OOP+f3vf88jjzzC\npk2biI6OJjo6GovFAsDRo0eZN28ehw4dAmDChAnMnDmTO+64gyFDhjBp0iS2b99Ofn4+H374IUlJ\nSTz77LOcOXMGg8HAoUOHKCkp4fDhw0RHR7NkyRJiY2MvZjMKBII2gggMEQgElxU5OTl8//33ANTU\n1PD2229z1113sWbNGiZMmMDq1av54IMPKC4uZt26dQwfPpzPPvsMgM8++4yYmBg+//xzAD766COu\nueYaXn31VQ4cOMB7773HX//6Vw4fPuw73913303fvn15++23ee2113jrrbd49913A+qNjY1l06ZN\nvnq9CaaLior485//zHvvvYfFYuH111+/YG0lEAjaNkIECgSCywpJkggLCwPw5fgDuOeee4iMjOSF\nF15g/vz5HDt2jIqKCvLy8jh69CilpaVs2LCBWbNm8fnnn+NwONiyZQuDBw/m888/Z8yYMajVagwG\nA+PGjQOgqqqKrVu3UlBQAIDZbGbChAl89tlnDB06lM2bN+Nyudi4cSOzZs1i48aNlJSUcOLECbp0\n6QJAr169fNHFHTt25OTJkxeyuQQCQRtGiECBQHBZ8fXXX/uCRUwmk2/7b37zG9asWUNiYiI33ngj\nHTt2BBTROGzYMD799FN27NjB5MmTKSkp4f333+eKK67AYDAgSRJ1Eymo1WoA3G53wPllWcbhcGC1\nWunYsSOffPIJ5eXljB8/nq+++ooPP/zQz1/RK1i91yIQCAQXCiECBQJBq6V+hqt9+/bxzDPPcOON\nNwaU/fzzz7n99tsZNWoUsiyzY8cOXC4XAFdddRUvvPAC7du3R6PR0KdPHxYvXsw111wDwMCBA3nr\nrbew2+3U1NTw7rvvAorI7NatG6tWrQLgzJkz/POf/2TAgAG+ehcvXkyfPn0wGo2kp6fzl7/8xVev\nQCAQXExEYIhAIGi12O12JkyYAChWNL1ez1133cXgwYN5//33/cr+5je/4fbbbyciIgKDwUCvXr04\ncOAAAH379qWkpISpU6cCMGDAAN577z2GDh0KQH5+PgcOHGDMmDFERkaSmprqq/f3v/89CxYs4B//\n+AdOp5Nx48b5UtRcffXVLFy4kHvvvddX76pVq/ymqQUCgeBiIZJFCwQCgUAgELRBWo0lUJZl5s+f\nz549e9DpdDz66KMkJydf7MsSCAQCgUAgaJW0Gp/ADz/8ELvdzurVq7nrrrtEpn6BQCAQCASCs6DV\niMCioiIGDhwIQLdu3fjmm28u8hUJBAKBQCAQtF5ajQgsLy/3ZegH0Gg0QdMzCAQCgUAgEAiaptX4\nBJrNZioqKnyf3W43KpW/hi0qKrrQlyUQCAQCgUDQYvLy8i7eyeVWwn/+8x/5vvvuk2VZlrdt2ybf\nfPPNAWW2bNni93nbtm2yxZIrg3wOfl6R9fpUz9/FMhhkcIVwnFtWqbTy+vXr5fDwnp5t62SQZKiR\nQZbV6t/LmZld5VOnTsmyLMtHjx6Vw8JiztF1i5+L+/NH2Wgc4fn7ffnKK4f7+qfV2k6GQ76ykvSi\nHBZ2s+fzYRmQ4Uydut6QTSarbDBkyyZTrKz0IWdI16HRhMnbtm2TzeYMz7YP5O7dh8rPPPOMbDB4\nz7lFlqQnzvqetdpsGd65BNpe/NT+VMmSpJXhrfN8Hpdct0+fix+zOV9+5ZVX5M6d+8vwySXQlqH9\naLVz5EceeVSWZVl2uVyyRqOXobwZdRyVjcZMeciQYbLJdEMLruER2WTKOsv7cMsajUGeOXOW3LFj\nb3nQoKtk+ONFb9vQfz6UtVq9bDINlJW+6Q7hmPflXr1GnC8p06RuudC0mung4cOHo9PpyM/P54kn\nnuD+++9v8phOnTpRXb0fKG/hWb8FvvD8fQy7/SDgBH4EqoCjIdRRjUqlIjU1FYfjiGfbEUAGDgAu\nJOkhPvnk31itVgAiIyOpqSkDxHR36+cManWJ5+8S4uNtvj2RkTZA2afTPY7FMhet9lPP3rJ6v5W/\nR4++jrlzb+SNN15BrzcBFTRNNbLspmPHjlRVHQTswA907JhFYmIiWu0hT7lNGAzvtugu6yJJp+td\nt+Disw1ZdgCnz/N5/oJePxDl+XZuUKsPkJKSQteuucB3Z1nbeiRp+bm4rCYxGjcycGB/AFQqFTEx\nKUBxyMer1UuZPPkq1q59HafzPUL7rtei052gouIH4FizjvOnAkmSePHF5Xz77ReMGDEUlepI04dd\nMhQxc+YtZGQ4MBp7oFabgT2efe8DP128S7tEaDUiUJIkHn74YVavXs3q1atJT09v8hitVktGRmdg\nW4vOqdG8jMHwVwBUqhJk2QUcAvZ6SvwUQi2nMRjCiYuLo7q6BEXYeb9EPwH7CQ+P9Ut3o9Vq0emM\nwJkWXbfg0kGtPo3LVSsCk5NrRWBMTF0R+CJvvvk6DkepZ69XRNVdR7YMmy2SefPuZ8SIEYSFWQit\nj5RhMkWi0+mIjk4CfkKr/YFu3bJJSEgADnuutRi1OpQXm8ZxuU4BpU2WE1xIvC+z51MEypjNS3G7\nDwM7z1mtTmcxycnJ9OiRg063+yxr24jZ/OY5ua7GqaSy8muuvPJK35akpBRgf4jHyxgMq/l//+9X\nxMTEkJfXD3i7WVeg053w/PVls47z5yjh4bXPrLi4OAyGn8+ivguL2VxE//5X8sEHb/KPfzzByJG/\nANYDYDQ+QHPb9HKk1YjAltKvXx7QMl9Bo3E3Wq0i2MLCvG9T+5GkfZ6/9wU9zp9TmEzh6HQ6DAYr\ncBy1uq4I/J6MjPYBR1ks0cCJgO2C1oVOd8Yj/mXU6hKSkmofqIpVUNlXXX2Y7t27U1NzEuVFwSv+\nai1qklRGu3aRvs9GY+gi0GxWjktPzwJ+wGD4gawsxRJotyuWQIOhGLv9bEWgHZerGiECLy3M5i+I\nj0/g/IrA9UREOLn55ttQq8+V0HJSVfUziYmJ5ObmEhZ2dpZAvf4wbvf/ztG1NcZXZGZ2xmg0+rZk\nZqaizP6EwlZMJjc9e/YE4JZb8jGbVzfrCtTq43Tv3gO1elO9PS7gjhBrKSE6uu4zKx6Npr4IPNis\n67qwbKVHjx7ExcUxcuRIfvGLoRiNXwCnqarajlb748W+wIvOZS8C+/fPw2RqmQiU5d1IktLhNZpj\nmExm4CdMpr3ExcURqgg0m5Vp3pgYxeoSFnaYzMxMVKp9wPd06RIoAsPDoxADaetHrT6N2+0ETqLX\nl2Cz1T5QFUF4DDiNWq0mMjKyjgU40BKo15cRGVkrAk2m0EVgeHgEAJ06KSJQlv9HVlYWsbGxOBwn\nATtq9UGPCK05iztWrkenE333UkKWv+Caa0agUp0/EWgyLWPu3DvIz78Oo/FcicDDhIfb0Gq15OTk\n4HKdnSVQrz9MZeUBmtfHjwCVzTqPJG3kqqv6+23LyUlBpQrNEqjVvsaMGVOQJAmAwYMHI8tbm3UN\ncIKxY8dgMtUXgWXAMhSXpqYooV27dr5PcXFxyLK/CJSkKwndwnkhOYXdfoScnBzflj59+qBWfwF8\ngUolYTDsbfjwNsJlLwLz8vJQqba04Mgaysv3+vz4JOkYeXl5SNJ+1Oq9DBs2DIPhpxDqOY3VGg5A\nXFw8cASN5gh9+/bFaPwJvf57unYNFIGRkUIEXg6o1V6RVoJGU18ExqJSlQBHiIyMB8Bs9v7fA0Wg\nRuMvAs3mUEXgSd9xXbpko9fvoapqH5mZmajVaqzWdsARXK5iJEmFd4q6ZZwCQKcTPoGXDkeAcnr0\n6IFWez4tgZsZPXoU/fr1A36m1m2mKZyen2AcID4+BYDU1FTs9uO03McbJOkQsiwT2gu8gl5/F/Bs\ns85jsWxk6FB/EZiWlorRGJolUKf7B9Om3eD7HBMTQ01N82aGXK4TjB49murqLSjWPy/eekIZX46S\nkOA/HVzr2w7gQJaPovy/LzW2kp3dDbVa7dvSpUsXamoOoFL9m2uuGYUsC0vgZS8CleCQAygOxaea\nceSPxMQk+/z4XK4SrryyJwbDT9jt+7jqqqvQ6UKzBIaHK5bA5GRFBMqyIgLV6p8IC/ue9u0DRWBM\njBCBlwenPW/zJUiSvwi02WyEhSkisF07RQRarZEoAjAwMESt9heBVmvolsDoaOW4rKwsJOlTLJZo\n31SVzZYAHKSq6jBJSTmEFvDUEKc91yr67qXDx/To0Yfw8PAWisDtwFtNlnI4yoiKikKtVjN06NV4\nfa+Csw44DoBavRCV6okGyh0gLS3FU05NYmI2imP/VqA65Dvw4nQeJj09Fwh9Sjgs7BgWy2fNOIuM\n3b6ZPn36+G1NSUlBrQ7FYuamsrLYz4JlMpk8gT2h37Pdfpzs7GzPDFRdH83Ser8bo4SUFP9nliJG\nvaLyGEoQ0Nm8OJ4vttK/v3/qFY1GQ8eOecBfmTFjOlVV+2jrAZiXvQjUarWMGnUtERFXYzQOb8aR\nu+nYsavHj+8EdvsxrrzyStTq73A4yujXrx8u108h1HOKqCjFEpierojAmpoj9OvXD4djH05ncBHY\nrp0QgZcHZ2jXLgkowek8FiACNRpFBCYmKiLQawHWasswmUz4B4aU+onA8PDQRaDNVisCq6t3kZKS\n5dublJQIbMNojPA4r5+NCDyFWq1B9N2Li0q1BpVqBVCD0TiP3/3u/7BarS2aDtZoXsVsfrCJUk6c\nzgpfhoPc3DQa9hUrQq0eAawAwGRaj1bbkCWpmA4dUnyfOnbMBd5GrR4AvBPyPSi4qar6mUGDBgI/\nhHyULJficGwg9IjnEtRqtyfoqpbU1FSczlAsgWfQ6YxoNLVpfCVJwmRqjp+4E6eznIiICIYPHwJ8\nUmffiXq/G0anKyEhoXY6WKPRYDJFURtx7H1WnE0E8vnBbN5K3749ArZfdVUf3O5Khg8fjtFopTZQ\ns21y2YtAgLfeepXvvitClpvjt7CHvLwcoqLigP04nRV069aN8vKvsNnSSEtLo6qqGH8zezBOExOj\niMCkpHj0+r04nRV07tyZmpoT1NT8TGpqasBR8fFCBF4OuN2nycjIBI5SU1NCbGysb5/NZkOSSoDD\npKUpItBrAdbpykhPT/ebVnW7/S2BkZGhi0BvQEl6ejqSpCI3t1YEpqUlAF/Qrl2y54F/dpbAuLgU\n3G7Rdy8eMkbjfMLC/g+Vajx9+3ZixIgRWK1WT/qe5mE0bqW8/Gsat56dJCws3JfAPyUlkbCwYCLw\nFEbj9Vx33bUe65qLqqqv0GpPBikLev0B0tNrMyf07JkLLCAmJoLmTOkqHMdoDKdHj07o9aGLQLe7\nFJerHMUC+T4q1ZogpVzA3zx/f0NWVmefP5+XpKQkqqsP0/SYUepxC/EnPLw5IrAUozESlUrFL35x\nFRbLR377/H83TH0/ZoDo6Dhqp3+9vy89S6Akbad79+4B2wcN6kfHjt2IiIggKSkTJeVb26VNiECA\n2NhYampKAUdI5U2m3XTq1MEzTfc1ZnMMaWlpyLKT1NR0wsLCsFhiUFLGNMYpYmKUt+P4+HgkaRsR\nEXFoNBqio5Ox2dL93vhqrze6Toi/oLXicp3xCK59nrd5k2+fzWbD5TqGVnvEI8TAZosCylCrT5KR\nkeE3ODocLROBOl3tdLBeryc2NoVu3bJ9+zMzE5GkL0lOTiI19exFYEpKOk6n8Am8eGzDaKzmyy8/\nIzPzIM8+uwjAY6VrrgiUqanZyujR45GkxoI9aiPQQRE8Ol0wEfgRXbtmsXjxYo917VscjnJUquCu\nOnq9kiPQS//+fRk69Bp++9v7CAtrrlP/IWJiEsjOziYsLHQRaLefYNCgq4FPMZnuwGz+Y5BSe4GZ\nKBaxb8jL6xzkXvSYzdE0bXkq87iF+BMZ2RwReByrNRqAoUOHUlOzgdqxL3SfQJXqaIAIVMZE7z0o\nIlCrvdREYBVVVXvJzc0N2DNmzBg+/vh9ADp0ECKwzYhAxQHeRqgOrBrNbnJyckhKigO+JjIylrCw\nMKzWduTmZgCQmJhGU7kCtdrTREQolsD4+Hhqar4hNlax+qSkpJOdHTgVDBAVFYVWK6wprR27/TRZ\nWZlotd/45dsC74tJCXr9EeLjlT4RFxcJlCJJiiVQrfaKKRm7/aSfCIyKsiBJTYtArbaMiIgI3+du\n3XI902oKCQkJyPIPZGUlk5jYDp3u7KaD09PTPC9c5y5hsCB0dLqV3HTTNDp37sz3339NVpZi9bVa\nrbjdzRWB+zEajfzf/83CYmlcBIaH+4vA4C/IB+nUqT1JSUmYzSZgBYmJKUhSQ/7a/iJwxIgRfPzx\ne2RlZaHXN3fwPkx8fAJZWVm4XKH6BDpwOiuZNOkXqFTzyMpqR3X1TgLFtFeQbsBg+IaePQNFIEBC\nQipNR9KWERERKAJjY6MJfXbohEc0KkElSUkZwFcASFKpr0xTuN3+0cGAZ0z0jqNHiY21odNdatPB\n35Kc3AGdThewR6VS+e6pc+cMVCohAtsMsbG1iXEbR6aqajcdOnTw+PHtJCZGmcZLTk6jY0dFBGZl\npdPUlIRWe8rnJ6MMtg4SEpQBv337ND+LTF0UB2shAls3Llyuao8F+RuiovxFoMViQZadSNKPPhEY\nGxuFRlOKLJd5pm69lsBy1Gqd30PNYrGg1TYtAusHlPzjH68zZswY3+fExEQAsrKSadfOXwRqtcuA\n5qT7OE18fAwajZ6zieIUtIR/A39CrX6NGTOmBey1Wq04nc0JjgPYSpcuPRgyZAhO5x68FiCV6kng\nP3XKlXr8WRWU/JOBlkCN5iBZWUkADBw4EEl6ntGjr0GWg08H19QU+4lALxkZGbhczbUEHiY1NZHU\n1FSqqg6hrJzTFGUYjZEMGjQIt7uEP/zhUbp27Q18Wq/cPtRqNTrdZ+h039C5c3ARmJqaQtO5Amst\n93Vp1645lsATxMTE+D6NHj0MlUqZEtbrS0lKSgppfHE4AqeD09NrRaBO9zO5uTke3+ZLie306NGt\nyVLZ2ZkYjUIEthnqro7QOHswm61ER0eTlBSHJO0kLk75IkyYcA29e/cGoFOndCSp8QeRWn2K8PBa\nSyBAaqoy9Xf33f+PW2+dGfS4qKioOm9sgtbJGfR6M+3atcPpPEi7dv4PU0mSsFptVFZ+7esbUVFR\n6HRlOJ2KCKwdHP2n20ARgRpN0yJQkvxFoMVi8fluAT4H9uRkRQTWrhryA3APFktDkZuBqFSniIqy\n1kl1I7hQhIcvZOzY9dx88y+DBptZrVYcjuZZAlWqrQwa1AOdTkdGRke8foEm00bU6rrBBmXExNT2\nMSX/5GnqR7OGhRV7rIQwcuRAZPkMI0aM8KwyU58zuN01REUF+scpPtnKspuhIkmHycxMqLNyzo4Q\njjqBxRJFbm4u7777LkOGDOG664aj033oV0qj2cvIkSMJC/uUqqpvGxSBubmhWAJLiY0NvOeEhOaJ\nQEU0KowadRVm86cAaLUnyM7ORq9vqi4ndvtJoqOj/bYmJcWj0ykvA3r9z+Tk5CDLwSyB76AszXbh\n0et30L//FU2WU/L1ChHYZlD8rpoWgZL0DmPH/gJQhJssHyMxUbEEPvLIwwwYMACAK67oisXS+INE\npTrtE4EGgwGDIdxjXYTu3bvTsWPHoMdFRUUJ5/pWzxkMBqvvTbpuvi0vUVE2XK4qnwiMjIxEoynF\n4SjzWDtqU8VYLIEisDYPYcPIsv80cn28lkCvCHS7jwIyJtNsHnroQSRpP7XrbTaOVnsaq9WK1ar4\nNgouHA7HQZYtW8If//hk0P0GgwG320FoFrBjKAEKW7nySiXC0maLxRsFqlIdx2TaVad8GbGxtX1M\npVIRGRn4vFWrD/qWyBw4cCBhYQYGDRqEwxFMBBYTE5MUEGABeFxzbISyWoVWuxRJ+jthYYdISlJe\neBYufACDYRxNL6lWSmRkNJIkMWrUKABGjLgavd5fBBoMe5k8eTKVld9hMlkb/L6lpycTFtbU+sH+\nKwN5sdmi0WhC9wlURKNCx44dcTq/B0ClKiU7OxuNpqnx5TgmU5Rfnj1QcgXq9T976jpKTk4OTmeg\nJVCn+wC9/t8hXu+5JSxsO1dc0bQIzMjIwG5v2wmj25QIzMhIQKVqWgRaLP9m4kRFBCorgyiJfeuT\nl5eH0+lNRP0pavXNQWqrnQ4GsNnifdPBjREVFYXTKURg6+Y0RqPFJwLrrhvsJTbWhkYT5vPZU6we\nJbhc1SQnJ3tW84D6Plfgteg1LQJdrrJGRaDVasVkMpGSkkK7du1wOI4CG7FYvufee++isLAAjeaV\nkO5Yq1VeehSfJtF/LxxOqqtLfM+rYEiSRFiYlVCCiQyGe9FqU6is/JQePRQRGB9fKwJdrmO43d/W\nOaI0QLi0a5dIfZHmch30WQJzc3PZtetboqOjcTjOEJiv7SCJick0RHJyBqEkpDYYVmI0PoBWe9Bn\n9b755pm8/PKfMJmmN3F0aYAl8oorrvC8KNWOJZK0j9zcXDp37k2HDsGtgKDkCtTpvNPBBwjmaqFW\n16Z0qkt0dOjBghrNCeLiaqeDExMTqa4+ipLc+YQnX2hTdZUQGRn4zIqLi0OlUkSg2/0zubm51NR4\n8wXWotWWoteHukzeucRNVdUOunVrejrYZrN5LNZns0pS66ZNicDExAQMhsDILJVqAvC959NJamqK\nuOqqqwDq+GoFisD09HSU5YR+Rqf7J0Zj/eV5QJZrLYEAOTkZZGRkNHmtUVFRwrm+1XMGi8XqmdpX\nBbUEJiTYiIyM91k7oqKisNv3YjBEYLFYcDorUVZTCPQTslhCiw622xsXgZIk8fnnn5OSkkJ0dDR2\n+ykMhuXMmTMLnU7HTTcVote/QihJVVUq5aVHJDu/0PyM1RqLVqtttJSS97TpKWGd7ieee+7PLF26\npI6lOBZvgmeH4ziVlfvxLqem1ZYFTGGmpibhLwLdVFUd9tUHeIKf1Oh0JgL7crFfepj6dOgQigis\noqrqG2w2HeXlH/mde+LEibhcR2k8vcmJgPtSq9V07tyTumvS19TsJT09nWuvvZoBAwJz03lRrKBe\nYfQeYWFLAsrodIHCExQRGKolUKc74TeNq9VqiYiIAw7idiuWQFlu6vtZQkxMcBHodisi0G7/maSk\nJI8PsH+/UoLaLsZycvswmyOCtmF9VCqVx6J8tmumt17alAhMSEhAowm0BOp024H/ej59QM+eA3yr\nKXjfrIOJQEmSfA8Dvf4jqqv3U1+0OZ3+lsB//etNhgwZ0uS16nQ6tNowQssDJ7g0OY3ZbEGtVhMe\nHhPgYA2Khdlmq7UMR0ZGUl19BLNZyfGlWG5OUd/nCryBJU31DwcuVzVms7nRUl27dgWUAc5sjsbh\n+Cc33vhLQLF8qNVVhOZKobz0KKluhAi8cBz0JCVvHGUd86ZFoMtVTP/+/bn11lt9Lyg2W4wnCtSB\nw1FOUlIuoKzlq9MFvmgoASB1I4RLMBjCCQsLCzif0RhO/RWdJOkg7ds3LAJDi+wsIi2tI/Pn34Pb\nbfdL4KxSqejevS8Q+PJeSynx8dEBW/v27YYkeVfhKANcREdH8+CD9/PEE480WFtKSgo1Ncp0sFq9\nF7U60FJWf3lIL8rLZKiWwONBfPnSgP3Y7YpPoNPZVF1H/RJFe0lOTqam5ghwHKezgoiICMLDY6kv\npiWplJqaC28J1GhWMHr0yJDLK3kP6xqHTnoS8bcN2pwIlOVgA1kFZrOyxJHR+C8KCmojJ8PDw9Hr\nw4IO4ACDB/dEpXoHp7MYlUrCf4UHcDhO+VkCdTpdUB+XYAjn+tbOaSIilBeAuDhb0D6UmGgjJaVW\nBHrfXr1Tv0owiLKMXP0pIovFgtvdlAgsw2CICLnPKdfQjpEjx/miCyVJIiEhjVDe6mVZeemJixM+\ngReWgyQnnysR6Kaqqnba1ktsbKxHBCq+Yl26dAaUKWG1ujRAuKSlJdbLFXgQmy34NZrN4dR/dhoM\nxaSkNHxPmZkZmExNWQI/Z9iwfuTn5/PLX94Y8B285pp+aDQbGzxakk54+rI/PXt2w2z2+oPvIz4+\nHUmSUKvVAT50dYmOjsbtrgbKMRp/9EQp+we3qFTBRaBybGjjgSSdCBCBGRmpwP9wOqtIT08PYabp\ncFAXFqPRSI8e/YCVWK02VCqVJ/OBf3CI211GTc1Jmp+b8mw4g0aznHnz7g35CGW2rzZ1nCR9R15e\nTsMHXGa0KREYHx+P3R4oAp3OClyu9UAFLte/mThxom+fJElkZGQEPBC99O6dhyz/jb5U6JPUAAAg\nAElEQVR9hxAXl4b/QOnC6axs0grTEIpzfagicDvwrxadR3C+OON7o1y4cD69evUKKDF8+HBuuaU2\nnYfJZEKt1hIervgIWiwRwEkkKdBZ3Gw2e1YyaIzAqOKmGDVqMPffP9tvW3p6Kk3lxARlhRSr1Ups\nbKRIcXRBOUhmZtMiUFnHvKlB+RgGg8U3G+IlNjYWtVoRgeHhMfTu3Qm1WhGBklQWMP2WlJSEXu8v\nAht6jlqtEdS3BGq1xb4gkmBkZmbichWhRKAGX1PXYtnEkCF90el0vPTSXwME2oAB/TAYGhaBen3w\nqVnF38wrAveG5OIDyngSE5MMFCNJP+J2OwnMXduwCHQ4QrMEut3+KWIAcnJSge0YjZEYjUZPhoDK\nBuuwWDYwaFCfoPumTh2LRvMCUVGKpVAJGvK3BDqdpZhMkTSdEufcoVI9wzXXDPflxgyF5OS6eQ/B\nZPqOzp0Dk0xfrrQpERgTExPECdSN01mFXq8CltC9e++A5Jjbtm31+P8F0rNnT2S5mvHjryI5uX4O\nKCVFSN10HM3Bu45saKxDo/lHi85z4ZCRpOtoO064p4mOViyBEydO9LMIe7niiisYP36877OyqkiU\nz/9PCbAoQ68PHBgsFovHob4xAgNKmmL58j/Rr18/v23KWrBNWwKdTmU6OCoqCr1eiMALhUZTHJII\nVCzTTYnAA9hsgeIrNjYWWT4OHCM6OpZOnTr6IoRlObB/JiUloVL5i8DMzOCiTkmo7y8CZblh0QhK\ndoWbbhpPZua9aLUP1dv7LlCEw/F5QF+uS69evaiu3kFDEdNabWmARQ2gQ4cOVFcXAxXAPl/u2FBQ\ngl0OUF39I4mJ2dQXSU5ncOGp+AuXEYqfuMMROB2ckZGKTrfVY1xoaqbJQU3Npz7f+PqMHTsWp3OX\nz11K8XeuawmUqakpo0OHblxIv0CT6SV++9s7m3WMkq2jdjpYpfou6EojlyttSgSqVCrCw+vP/1eh\n1YYxePAQJOkxbrutIOA4vV7fYJ0pKSkkJCQxYsQIsrPr54A67fF1aRnNc66vRK+vaPG5Lgw/Istv\n4nUuv/w5Q1RU831LzOZIX7qNqCjFEhjMT6g25YezkdoaDwoJlczMVAyGph7mMna7Mh0cFRUVQgoK\nwbmibv69xoiMDEUEFnuSGvsTExOD06lYAm22GDp27OiLEHY6A6eDk5KScDhqRaBaXUx2dvBrjIwM\nnA6urm7cEqjX6/nTn37Pm2+uQqdbQ604kjEYbiIiYixms67ROiwWC/HxGcC2oPtVqhNBBZlWqyUl\nJQf4hrCwH+nQIbiRIBgZGSnANjQaDZ06daO+CKy/PGTdc+p0RuqL5UBO4XSWB1x3amoqTucOIiIU\ncaiIwYYsi1+SkpIV1BfeW1daWhfP6iGQkmLD3xJYgVqtpWPH9gH3d/5wUFm5N6So4LokJMRhMHgt\ngS4qK/9Hhw4dzv3lXaK0KREIYLPVz11VgV5vYtSoQWg0MhMmTGhWfZIk8eOPSqfp0CEFjabuQHkK\nk8na4LFN0a5dFKGkQFCuoxKN5lIXgV95frcNcaBWn26Rg3FkZJRv6lcRgycDVv0Ape/p9WYaDx4K\nvvpAc0lNTUWna0oE1qBSqdDr9URGRopk5xcQlapxq5kXxTLdkAh8D6UvHSA7O1AEKsscHgOOkZAQ\nS1ZWFnb7UeAUdnvgdLDifnMCqALAYGj4GmNi6k8Hn0KS5KDW8/p07tyZiIgwap8v32MwaCkp2c+2\nbZua9Ift0aMLDYlAWQ5ulVOO6wa8iyS9wbBhw5q8Ti9KsMunJCRk0L59/dkjFw5HeYP3bbGEkjD6\nVYYPHxMQKZ6amorbXUV0tHI/yrJywb+javUHjB8/otGzTJ8+mS5dlBWv4uK8/qJeyjCZIunQISVo\n8Mv5YS/R0YmNGm2CER8fj1brNQz9RHi4zW+N98udNicCExMDRWBYmInrrruO559/1i+SN1S80W6p\nqakYDHU7/CkslpZbAmfPvhmr9Q+oVM83WVYRgJf2Ml063WbPX21DHOh0Z1rUn2Jjo3yWwNjYCLyB\nIcGsA2FhTaWJOemXxLelKAOIVwR+S/Dps9OeaGYl/5bbHZiOSXB+cDoPhSwCJckrAt2oVBko/lAV\nqNXXA2+g1RaTlRVoPTMYDKjVWmAvSUmxaDQaOnToDmxElh0BA6dGo8FmSweUCF61umERGBtbfzr4\nINHRwRNF10eSJAoLr0erXePZso7Bgwej1WpDapPExGgamp1wuYJPBwP07dsVeIS77/51g0n/g5GW\nloIkbSA7O5OsrJR6ufROYTBYG3QhUqx4jYlAGbP5OX7zm1sC9niX37PZvGsKNzzTZDJ9wKhRwxu9\nj/nzH+CBB+7z1GlDpdoKrEEZh8qwWqNISwtlBiF01Oqx1Ir9+uwhK6v5Fry4uDgkyWsJ/I7s7LYz\nFQxtUAQmJbXDPyeQIgJjY2OZMeOXZ1V3amoqKpX/dLDi69IyevbsSVHRBlSqO2lK4Gk0lcjypW0J\nNBi+8gQ6tA0RqFaf9uTyax5pae18UYw2WyRq9bPI8j4yMzMDyhqNTYnAMmy2iGZfQ32U9VaVFEha\n7Vjgn0FKncJoVESg4jN1AMVnSnB+cVNd/bNf+pOGCA8PR6v1isAS3O59qFQvA2+jUjkwGj8iLOxA\n0PV6AU8uwu+w2ZSgg6FDeyNJ7zcYgZ6d3R7vUnNO508N1hsVFY5aXXc6uLjRRNH1KSiYjE73d8CN\nyfQpv/jFkJCPjYmJQqcLLgLt9uDTwQBDhgxh2LBrePDB+0I+FygpVmS5gi5dMklJSUGvr7uCSGmj\ngVxRUU2JwK8wmc4E9eUzGAxYLDZftLOSxilYXaeprv6W/v37N3ofdf/fvXr1YvBgGzbbvcDbQCkR\nEZGkpJxbS6BOtxWd7tUG9u7hiiuaLwLj4+NxOr0vrN/Ro4cQgZc1cXHelBteKjAaz43pNyUlBbvd\nfzrYmyKkpWRlZREeHhh+Xx+1+lIXgU4qK7fTv/8w2ooIVKlaZgn84x8XU1Cg+KYOGDCA22+fyL59\n3wUELAGYzY2LQLX6NFFRLX8R8WKxWNDpDMCXOBz70OvXByl12mf51ul0pKV1QolaF5xflPx7oUyD\nWa3WOiKwGIPBjMHwAmbzq8ydey/wESpVwyIwKioW+M7nKzZgQG+02vd9wQb16dYtGyURfxlOZ2mD\nAXYRERHodKeAYmAFcJD09KateF46d+5MdnYyKtUyZFmxBIZKZGQkWm0wMWTH5apu8DvcvXt3Pvro\nvSYTdNfH27bt22d6/q4rksqwWhsWgYoVr2ERqNO9ym23zWjQkpiQkOrLe6gsKxfsWbyVrKwuzZpW\nzc7O5v33/87UqZNR/OLLiIqKJDU1FYfjXFkCXVRXH/UEQAYGxxiNe+jSpWWWQGU1FRmj8Tu6dRMi\n8LImJiYKrbZux684Z/P/ig9MGbXpCk6dkwFYefA2HkwhSRUhpAu5mHyLzZZMdnY6bUUESlLLLIFW\nqxWdTgfAoEGD+OMff9+gNaKpVUN0utMtEqLBiItLBV4kMzMHnS6YCDyFxVJ7rn798qi7qoLgfFFM\nTEzTS1GC0rfUaq8IPECfPlcRE6Onuvq/3HPPPRgMGs6c2dZgMIWSYeEnX/qR3r17Y7f/r8EI9E6d\n2mM0fg9so337KxoUJ+Hh4Wg0p4A3kKQbgX82mii6PpIk8frrL6LVPohOR1CreUMoQUzBnq9lGI2R\nzcqxGQrets3MzCQ5ORm73V8EKhkBgpOQEENjK5yEhX1PXl7Da+bm5KT5niWxsQ3ddxH9++c1dgsN\nUhtAVkpsbBQJCQm+5erOnmOYTJFERhoJNiWs1e4hJ6f5+f3CwsI8ATelaLVtKzIY2qAIVN76/C2B\n50oEKkkzE4HnsViGA2/4UoScDTExtWt2NnzuSpzOS9kS+BW9e19JXFwUKlXbEIHQMktgc7Bam7YE\nnqtrSEtLRZJWc999d2G3/4S/mF8KbPNzf+jfPw+TSYjA88/hRtcMrovVWtcnsJjs7BTuu2821157\nPVarlauvvgpw+5bLrE9cnGIB9FoCk5OTCQ+PbzACvX379mi1igjs27d7g9cVHh6OSnUSo7GIYcOG\nAu+Qmhq6CPSea8mSpygoyG+WcFOuPZh17USDFs6zwWQykZiYRPv27YmNjfU8t73P7lKPr15wunXL\nwWDY1eB+Wd5PWlpag/sXL36C/Px8ADp16oTRGGipN5uLPC9wzUcJIPsJUPKaarVa2rVLw7uyzNlx\nmJiYRAoKJqLVBqZDq6nZ3eKo3qioOGAX1dXf+VZPaiucMxFYXl7ObbfdRmFhIfn5+ezYoSTS3L59\nO5MnT6agoIClS5f6yi9dupTrr7+eKVOmsHOnsvxOWVkZN910E9OmTWPOnDnU1Cj55D7++GMmTZpE\nfn4+a9euPavrjIyMRKPxF4EWy7mLBEpJSSUy8kkWL76BESMs9OnTsi9TXZSFwBsXgVCJw1HOpbrW\nsFa7i969O7ep/HEuV8ssgc1BiT5uWASqVOdOBObkpCHL5YwaNYpu3foAGzx7KlCpfkNY2AK/VU3y\n8vJQqYQIPP+c8bwMNI3SFxQRqNUqUcC33XYLa9a8BMC4cVdjsyU2uOpFUpIi/uquJpOX17tB4dK+\nfXvs9u8xm7fSt2/Da+pGRCjRwWr1Vp566ikKC2dy5ZVXhnRPdZk16xaWLVvUrGOioqJwuYJZxL4O\nKbCkJfzvf9+TkJDglzxaoazRQK4rrrgCrbYhFwuZqqqfSE1NbfD4jIwMn2AfMGAA1dWbqZ+3VZKK\n+P/s3Xt8lPWd//3Xdc35fMppJudAAoFwClE5G8RjFaUaBJS1u7B2673YdtGtte2NaNfqdoG2v8Xu\n1nVrrZaicNt1996D1dJKsW7loKKleECB1ARBgiQzYZI5XL8/rswkQzLJTDI5zvf5ePRhc+WauS4m\nh3nn8z185s4d3PtWWVkZ0egJZLklPvdw8eKFdP+uGIomfD4fN9+8HJPpxYs+14KidKT8x9DF1Nad\n3+Pqq69LaUX6RKLN1BM9+eSTLFiwgDvuuIOPPvqIe+65h+eff57Nmzezfft2ioqK+OIXv8jRo0eJ\nRqMcOHCAXbt20dzczN13383u3bt57LHHWL58OStWrODxxx9n586d3H777Tz66KM8//zzGAwG1qxZ\nw7Jly1JqDt0Xtf9i4nBwJkPgT3/6GLm5ueTm5vKXf/mXGXnOwsKBK4GKEkBRIqirNtNbIj8StNpA\n1u0fF4kMfyXQ5eo/BEpS5kJgZWUppaXVFBYWcv31S3jjjb2EQjcCH5OTU8LRowcIhbqHfWpqaggG\nP0StcmTPlgsjrx2rtXc/3r64XK544DEaGykuvgwgPky7fPnyfqtoPl9iCAS4/volNDb2vRLc6/US\niQRQlN9QW3tf0ud1OByEQs2EQqepqanhpz/915T+PZngcrm6trLpKYLF8m02bfr7YbmmyWSK//+S\nkjJOnToGTAXOdc1b79uMGTNobz+COrx68VzET9HrDSn/vDscDsrKqnnvvd8DS7qOthIM/mnQQ6Kx\nBWR6fQsulxqgr7lmMf/5ny8TCNw1qOfs1kRJiY+qqiqCwY9QCx6x79V3KSmZMuih+8LCAt544+fc\nffcvh3iP40/GKoF/8Rd/ES8zh8NhDAYDfr+fUCgU/2tq0aJFvPrqqxw8eDC+8sjr9RKNRmlpaeHQ\noUMsXrwYUOdCvfbaaxw7dozS0lKsVis6nY65c+eyf3+yJeIDc7lcKEpiJdDpHFxbt75MmzYt6Qab\ng+Xz5aDR9D8nUFFi7X/SHRIOAw8P5rbSotG0YzabcbuzZzg4FBr+SqC6GXXyEKgomQuB11xzDY88\nonZmqK9fgsn0267PfExBQSEulyuhN6u6OGQa3e21hOHRjtVqGvg01EpQbG+/vhaA2Gw21qxZk/Tx\nOTk5GI3W+LZYAF/96lfYsuWRPs+XJImioirC4bP9BguHw0F7+wlKS6fF58OOFLvdTjjcTuK2Rzup\nqLDzuc99btivf9VV89Fq1UqZ0fghJSWFSc+1Wq3k5hYD7/bx2RPk5yevAvblc5+rR5Z/A7yI0Xg5\nsI/Jk2ei1Q6uPqQuIDMiy+/HCzWLFi1CUTJTCZw0yde1B2mUxM3FDzN9+uA3eC4tLcDjKUlrv8eJ\nYlAhcPfu3Sxfvjzhf8ePH0ev13PmzBm+9rWvcc899xAIBBL65losFtra2ggEAglvjhaLBb/fn3C8\nv3Pb2gZqlZWc2+0mHE4MgQ7H2K5S5ObmYjD0XwmMRmMhMN3FIR8D32K4u3jIcncIVJRsCIFRwuFg\nr/6rmeZ02pDl5D8PsV6+mVBdXc2aNd3ziYLB2BvRxxQX9/3GVVc3G3g7I9cXkglgt6cWArVaLZWV\ns4BDdHae7LebRl9yc3O75ih3k2U56fAxwNSpVZSXz+h3FW1sCG7evORDxsMl1qqx5xxXm20LW7c+\nmPFFIX256qorMJv3AAqy/D9cfXX/mzTPnj2bvv+wOt7vfMC+XH31UiyW/8Fs/mtqakLI8h2DXhQS\nU1BQSkfHG/Fh58rKSjSaDobaPs5kaqKoSB1CV+cZxp4vjMWyjS996c8G/dxLly7kgQe+3u/38UQ1\nqLjf0NBAQ0NDr+Pvvvsu9957L/fddx91dXX4/X78/u5QEggEuvap0hEIdFes/H4/drs9Hgbdbnc8\n/Fmt1l7P0d+bWlNTU9LPAQSDQTo7u3/YJckPRAZ83GjSaDRoNJ/0e04kEsBsdtLenm4lMLZJ5pvA\nlYO5vZQoip8LFy4QCoUIh1Nrgj6+taPTmTh16uLm8JmlKApabSudfbc+JRQ6T3t7e8a/vxVFAaKo\n2y19TEGBo89reDwWEvflFDJNkgLodJqUv8Zz507jD3/YR0fHWRRFSet7w2azUVlZntZjqqoKsVj0\n/T5GURQkSWb69IoR/13c1taGxeKkre1ToAA4TzD4PlVVVSNyLyUlJQSDR4C9WCwGrFZrv9edObOC\nl156g2j0Ajrdu3R0/EPXZ05QUpKb1j1XVFTg97/O4sVX8+ST27nxxlXU1c1I6zna2toSzvf5vHz4\n4RtEIt3vq3PmXMLevfuA9CqVPWm1f8JoXEBTUxMFBV5OnjwBzAaeZPLkHGpqagb99Zo/fz4wcH6Y\niDI2J/CDDz7gq1/9Kt///vfjK3SsVit6vZ7GRrWv5b59+9iwYQMajYYtW7awbt06mpubURQFp9NJ\nbW0te/fuZcWKFezdu5e6ujoqKio4ceIEra2tGI1G9u/fz/r165Pex0AbpiqK0tVvNQgY0ena8XpL\nUtpodbRUVVVdNIR6Akn6Oory8/iRcLid/Pxi2tvTrQTGQsobDGcIlOUgxcXFVFdXEwqdG/gB454f\ng8Ey7N9XRUVF6HQfJA2B4XAbVVVVCcN3meLzVXD8+Ifo9R8zffqUPv+tkyaVo9d/mPT+hKHT6dpx\nOJwpf69dddXlPPvs97FavWlXAn0+H0uXLk3rMQ8++ADhcHjAedx2u4Nly5aN+O/ipqYmcnPzOXUq\n9sfpfqqra/tdYJFpM2dexoEDX+eWW5YP+O+//PIl/OM/foNI5MdoNFbgu4CEXn+c2bOnpf36ffOb\n32L9+j+nrKyMt99WuzqlUwFtampKuObMmVXs2/dfVFVVxY9//vNX8vrrvyUYvD2te+tJkk5RU1OD\nz+ejpqaS118/AUQwm7/NP//zcxQWJh9GH8uam0e3s1LGQuC2bdvo7Ozk4YcfRlEU7HY7jz32GJs3\nb+bee+8lGo2ycOHC+PLruXPnsmrVKhRFYdMmdZ7RXXfdxX333cdzzz2Hy+Vi69ataLVa7r//ftat\nW4eiKKxcuTJh3lG6YqX/trZzgBetNnNbxAyX3NxcIpGew8EfIMsvE4nEPlYIhy/gdufy8cfpVwLN\nZguS9AaBYd1hRh0OttlsRCJB1Pk3Izv3Z2SpnWiGm81m67Hv28U6ACXtXpqpqqgo5/jxjzAYPqaw\ncFGf5+Tk5KDXvy5C4DBS59um/sZfV1dHR8dBKiv7/pplWqrTEX784yeYMyf5NjLDKSenu3WcJP0v\nV1wxb0Svf9NNV3DgwDf5/OcfHPDc2bNn097+Jn/zN/fx4x//FPgIqMBoPEFZWe9OIQP59rc3x/9/\nJoa/q6rKABJC//Tp0zEY/oNgMMmDUhAKdYfNKVNK0WpPEA6/i82mZ968kf16TSQZC4E//OEP+zw+\na9Ysnn322V7HN2zYwIYNGxKOeTwennjiiV7n1tfXU19fn5H7BLDZXPEQKMv+cRECOzt7hsAzXSv8\n2gEzEESrNXTNnUw/BF5xxRW88srwdnZQFDUESpKE2ezC7z8H9O6AMXEEMJlGJgRKUrI5gWov3+Ga\n11RdXc6ePR8hyx8n/Ss8Nzd3wEVNwtBoNO0Jq00HUlVVhcFgo6wsvSrgcLv55ptH7dpebw6xvQKt\n1v9l0aK/GNHrX3nlFXznO5aUOp0UFBTwwAMP8rd/ew8ffHCS//iP3wAVQP/bw4yU2D303GrF5/MR\njQ52qFUBInR0tMQLQKWlpZhMv6et7QB1dXVDvOPslnWbRQNdu9urQ5KyPPYrgQ6Ho2tD0dj2G7FA\n+Keu/wbQ681YrRbSXRhiNH7CFVcsJRg8jhoqh0c02h5/ndUNWCf6kHAAiyVzq86TUassyUNgrJfv\ncJgypRyj8SPC4eQhMCcnB0Xpa1FTO/CbYbu3bCLLgbQWIMmyzLRpc5kype/WcNlIbaH2KaAQCv3v\niFeWLrvsMg4dOpBS1V6SJDZv3oTFYuGGG+oxm38DKASD6S8MGQ5lZWXY7c6ERRZer5eOjsEMe/rR\naqcCv8Nmy4mvWi4rK0OSTmAw7Gfp0vT3kxS6ZWUIVMvU6hw7SRr7IVCWZazWnsMVscpKbIPRdoxG\nCw6HlXQrgXr9KUpKSigpmcpwruKMRNrjb1QOR+JKvIkpcz2p+2Oz2YhGkw0Ht2KxDF8IrKioQK//\ngAsXPkk6DyknJ4dwuK9K4KtotXcO271lE0lKrxII8MUv3sayZan3153o8vNzuvoHf4jJZBrx+WWS\nJA2q5Zk6QvYb4BySFNt0e3RNnTqVhx7anHDM5XIRjQZJv9DwP8jySXS6v0xojVhaWkpn5wmMxv2D\n2lRc6JaVIdDj6a4EZrJt3HByOrs3jO7eLibWc7Ido9GM3W4h3RAoSacoKCjgkkvmoC4OGR7hcHcI\n9HiyIwSqldnhpYbA5JXAnr18M628vJwLF17HYnEl3dtNncqgVlgSNRGJfEh3n21h8AJph8AvfelO\nrrvuumG6n/HH4/Gg138K/I5LLrlstG8nZZWVlRgMYSyWhcyYMXdEtrQZiNFo5Ctf+UrCMUmScLl8\nQHOPY08DF7d/S5w8bDb/gr//+0dwu8MJf2jm5eURDvsJBA5TWzvy2wpNJBmbEzieqO1s1BCoKOMj\nBLrdOZw8qYY/vf5TiooqOXasEUUBdRGCuWu/w/SGgyMRNQTOnz+LX/ziTTo6Bn5M+hTC4e5qhdpi\nauKHwEx2oknGbrcTCiUPgQ7H8IXAsrIyQqHzFBcnn8wfmweq/nHSc3i8GUWJAu8B2dWrU/UZ6sKd\noc+Ljc23FQYvJycHrfYsVuv/x+rVN4327aRMkiT+7d92otPpxnxFLDfXy5kzTcAkAMzmF5Hl47S1\n3RI/R6utJxz+NrAM6CQS+S9WrdrCjBkzOH68MX6eLMvk5BSj0+kT9iIW0peVITA/30UshESj4yME\n5ufn0j0cfIa5c2tpajpJezuoK28tOJ1WJCnQFQxToRAMniI/P5+amhr0+ueGKQR2IMva+HwONYSL\nEJgJVqu1KwT2bKEU04rTOXwh0Gw2Y7fnDzh0Zrfncvbsp/QMgXp9U9eK4T+SjSFQkv4PBsMJgsGL\n26MFgS8Dj6f8XIqS/nCwkMjj8RAKvU80+gm33PLT0b6dtCxZsmTgk8aAoiIfR450Lw7Rahs5f/5V\n1BGtEqCNSOT3aLUvEQ4vA/YweXI1Xq8Xr9fb6/mKi0spKxue3s7ZJCuHg91uFwaDWgmMRMZHCFR7\ndqqVQEU5Q21tLTpd95xAs9mMxWJBp0unEtjWNd/QSk1NDR0dR+g9bJcJ7ej13ZWKgoKL+zdPRP4R\n6USj1WrRag30PdemFZdreHsXFxVVUF7efwh0OnO4uCONwdDMpEmTkaQ/DuPdjV1m8/vo9Yf6+Myf\ngH8hnYVT0Wj6w8FCIo/HQ3v7e1xxxdXD3u87W5WVJQ4HRyKNzJ9/OZL0XNeR32M0mrsWuoDRuJs7\n7ki+Ynz+/Flcf339sN1vtsjSEOhGp1N/yYbD4yMEFhXlEAuBodCnzJkzh2g0FgIDWCxmrFYrWm06\ncwJP4XQWAOocC51OQ88f0sxpx2DoDoEejxu9fqKHwABO58h8X5lMyVYIt+LxDO8bWlVVOZMm9R8C\nc3K6v3djZLmZZcuuwGLJzhCo1X6A3/8H1CHhnmKv05GUn6vnVAthcNTvUfirv1o7yncycZWXe9Fo\nYpXAKBcufMw3v/m3WK07AZDlfaxf/5cEg+8Ap1GU51m79rakz/eDH2zhC1/4wvDf+ASXlSHQ5XJ1\ndeBQ56qNhxCYl5eL0XgGiNLRcZY5c+YQDJ5Erdy1Y7NZsFgsyHJ6ITA3tyD+UXn5VOCdzN44cHEI\ndLlc6HQTOwTKcgC7fWTmqphMNqD3CmFJGv4QeP/9X4n3E06m51SGmEikiWXLliHL2RkCOzqOYbO5\ngD9c9JnTXf+9+HhykYgIgUPlcrlYtWo111577WjfyoTl8/kwGmNFhk8wmRxce+21XQsdf4fVuo9r\nr72SadPqgHupq7tsTHfymiiyMgS63W4k6RwQRKPRjYum0SUlJej1x4Hz6PUWcnJyuu77M9QQaO4K\ngekMB3+Cz9cdAmfNqmK4QqDJ1B0C7XY7spxsMcPEoNONXIXZYrHRVyVQr28d9jtoVmUAACAASURB\nVKGtSy+9lMrKyn7P8fkuHg5WCAabqa+vp739fSCS5JET1Xmi0Xbq66+i94r82OKvVENghEikc1ja\nAmYTWZbZufPnSVe5C0Pn9XrRamOVwEby84u7Wsh+G4tlI8Hg6yxYsIAbbqgHnmbDBlHlGwlZGQLV\nPYtaUDdZHvtVQICamhoikXeAMzgcuQDk5haj7hUYwGZTh4MlKb1KYHFxdwicMaMKk2lkQqAkJdvb\nbmLQaEYuBKrbwPQOgVrt8IfAVBQW5iDLPYeDP0Or1ZOXl4fDkYfa9iqbHMPnm8SSJbUYDL1D4MyZ\nszCZUg2BF9DpTGNiaxBB6E9i15BGSkrUjjV/9mdr8Xo78HrLcLlcXHnlUqxWBzfdNH5WaY9nWRsC\nw+FzjFR/10xQt+NoAY7hdqvzV4qKilFXVrXjcFi6QkfqIVCWT1FW1h0Cp06dik6X+jBU6nqHQEUR\nITBT7Pa+h4M1mrERAnNzczEYelYCm3G71dV+lZXVqCuEs8kxJk+ezJw5c3qFQJ3uDPX1l9PZmerP\nYSBh0ZUgjFU+n69H15BGJk9WQ6Asy/z85//CQw99DVBXO7/77hExxWGEZGUIdLvddHZ+Bnw2bkKg\nLMuUllYDr5Cbq1YCJ08uAU4iSe3xSqCipD4cbDSeoqCge5+yqqoqLlz4AxDN7M3TnrBxst1u76fL\nxcQwkp1oHI6+h4MlaWyEwJycHHS6xBCYn6+GQLXZfGNfD5vAPqCmZhKzZ8/mwoXD9BwO1+vPdC36\nCpDaNkrt4+Z3mJDdnE4n0WgHEECn6w6BAHV1ddxxh7ooR5IkMRdwBGVlCDQYDJSX1wC/GpHWXpky\na9Z04DcUFKiVwOnTK9BqP0KnC8S3iFHfPFKj032c8MPmcDiwWBxk/k25HYslsRIYDk/0EOgfsRDo\ndidfHTxWQqAk9RwObqKoSP2+U3u2nh2V+xotZvMHVFdPxuVy4XB4gA/jn9Nqz5CXl0d5+TRSWyGs\ndgsShLFO7RriBZoxGk9SUiJ6V48FWRkCAW688Sok6YVxFQIvu6wGOEBRkVoJrKiowGw+hkajrnC2\nWCyEw6lXAqPR3j+IXm8JwxECrdbuNyqbzdZPl4uJYuQqgS5X38PB0ejYCIG5ublEo4mVwPJytRKY\nl5fT1a4re+h06nAwQFFRGd3tHwHOkJuby+zZ00lthXAgYaqFIIxl+fk+4Diy3EhxcfGA5wvDL2tD\n4HXXXYmi7BuR/q6ZMn36dCASrwRWVFQAH6LRqJtFW61WwuFUK4EKwWDvH8SiokKgqe+HDJo6XB1j\nMBhQh5x77pG2H/ggw9cdPYoSGLF2Rm63DUnqHarD4bERAnNycujs/AR4CTiKXt9ESYkaAtWerdlV\nCQyFPmDSJLV1VmlpEeoG0apwWA2BV1wxD4vlf1J4tvZx9YeskN3WrbsVi2ULoZAIgWNF1obARYsW\nodXqx1UIrKmpAYjPCZw0aRLB4IfIsjocbDKZCIeDpLblxnlkWcLhcCQcragoBD7O7I3Tjt3eHQIl\nScJoTBzC1OsfB57P8HVHz0h2orHb7eh0vUNgKDQ2QqDb7SYnx8XMmQ9hMtWj0RyIT0PweDxoNNkU\nAjvo6DhDUZHa7qqqKrbCH0Cho0MNgWvWrAF+S8+h4r4FEqZaCMJYtmHDXTidH3Hhwikx72+MyNoQ\naDQaqa1dNCL9XTOlqKgIi8UeD4FOpxOtVks4fLJrj0AZnc5E3y3ELnaS3NziXltLlJf7euzllCmB\nhBAIYDbb6TmEaTC0odVOnGHBkQyBNpsNne7i4eAwkUhwTGyErtVq+dOfjvHWW7/lgQf+lgsXXo33\nAlU7NUycr/vAPsNkcsT3Ji0rK8ZkioVAP5IkY7FYsFqt3HnnevT67QM8X/u4+kNWyG46nY4f/vAf\nKC4uj/eSF0ZX1oZAgNWrr4/PrxsPJElixYqbqKqqih/z+SoIBo9gNqshy2i0kto2MY1dW8wkKiws\nxGjMbCVQq23vVa2wWBJDoCy3TahhwZFsR2iz2frYfPszjEbbmNs/7mtf28jWrduora0F1EpgJDJx\nvu4D82M22+IfFRcXo9PFhoPPYLd3/z7auHEDsvwU0N8838T5toIw1t14440cOfLWaN+G0CWro/hX\nvvLl0b6FtD3zzE8TPp48uYIPPjjYIwRaaGtLLQROmtR7dZbP5+vR3zEztNr2+P3FWK0XL2Zom0B/\nGUYJh4Mjts9V3x1Y9jJjxiUjcv10SJLExo1/E/9YnS+YTSGwDbO5e66oOiwcqwSewe3uDoHFxcWU\nl1fxxz8eBC5P8nyBhPm2gjAejIURCkGV1ZVAWZaR5fH9EtTUqBPMYz9U6hvMwCtvZbmxaz5SosLC\nQiKRzFYCYwtXenI4EucEKkorkjRRwkA7Op1pxL63bLbeq4PN5n9n7dqxv+O+w+EgHG4HOkf7VkaI\nv6vNn6q4uJiOju4QGJvqETNv3hx6t5brqR27XbyhCoIwOOM7AQlUVVUAxEOW1Zo4zJqMyXQy3ran\nJ5/PRzDYBCgZu0dZThYCu+8zGm0jGp0oIdCPwTByb8w2mw1FaQN+h15/FdBBNPqf3HTTjSN2D4Ml\nSRJms4vUNkaeCNq6QrvK4/F0baDrB85QUJAYAufPn4PZ3DMEtiDL9/T4uB2HQ1QCBUEYHBECxzl1\nm5juEOhyOYHzAz5Oq23sc7NOm83WNWl94OeQ5W8Chwc8T5Lae5X/1Q2Ou0NgONxGKDRRFggEMJlG\nZnsYUIeDI5E2dLpfoCh7kaTVFBYWj5vNWB2OHLJnw+g27Pbu7w1JkvB4YtvEnOk1R3nOnDlotW/2\nOLKTaHQbcByItScUIVAQhMERIXCci4XAWMhyuRzAZwM+LhJJvk+Tx5PKXoFBJOkHpNLVQJJ6VwI9\nnt4hsKOjhcy3rBsNAUymka0EhsOtGI0v8+STP0an+yVr1oz9KmCM2+0he1YI+7va/HXz+dR5gVrt\nGXy+xBBYU1NDe/t7xPbUtNmewestAl4GYouuxHCwIAiDI0LgOFdcXMzChYswGo0A5OSkUgmMcuHC\nn+J7lV2soMDHwHsF/ppIJEAqgVPd0DYxBLrdPeexhYlGQxgMthSfb6wLjOgGvjabjY6OzwiFPuTW\nW2/lxRf/k7vv/n9G7PpDlZOTTa3j2nC5EqvEZWXFwJ8wGE73mhNoNBrx+SYD76Bupn6MBx/chMXy\nEgBabaDXz5YgCEKqMh4Cjx07Rl1dHZ2d6kTvN998k1tvvZXbbruN7du797zavn07K1euZM2aNRw+\nrA4pnjt3jvXr17N27Vo2btxIR4f61++ePXtoaGhg9erV7Nq1K9O3PK5ptVr27fttfCuQvDwnAwep\n05hMjqSrV0tKBq4EGo3/3tURY+DQpii9Q6Ddbkevjy0MaUOvt2K3T5RhwZENgXq9Hq1Wz2WXXY5O\np6O+vp68vLwRu/5Q5edn016B/q42f93UBVoHiUT+iwULFvR6RF2dujhEo3mS225bzXXXXUck8isg\niiyLSqAgCIOX0RDo9/v57ne/29UWTLV582a2bdvGjh07OHz4MEePHuXIkSMcOHCAXbt2sW3bNh56\n6CEAHnvsMZYvX84zzzzD1KlT2blzJ+FwmEcffZSf/OQnPP300zz77LO0tGTLJPL0ud0OtNqBKoGN\n5Ocnb9kzadJAlUAFWf4PbrnlFjSa5CFQlr8CHCIa7TsEdm9w3IrZbMflmigVocCIb+BrNtv4/Oev\nHNFrZkph4UT5ug9Mo2nD4eirEvhPrF9/B1OnTu31mEWL5qDVPojT+TQbN/41RUVFeDy5wJt9LroS\nBEFIVUZD4KZNm9i4cWN8aNLv9xMKheLDjosWLeLVV1/l4MGDLFy4EACv10s0GqWlpYVDhw6xePFi\nAJYsWcJrr73GsWPHKC0txWq1otPpmDt3Lvv378/kbU8oTqcTvX6g6tyZfitFpaWFGAz9hcA3cTot\nXHbZZeh0ya9lNr+OJP1P0hCo0cRCYBtmsw2PZ6LMDQuMeCeayZPLuPbaa0b0mpmSn58zobrF9Eer\nTVwdDOq83tzcfL7znQf6fMznPvc57rzzZj744HB8o/gbbrgaWf5v1KqzCIGCIAzOoHbn3b17N089\n9VTCMZ/Px/XXX8+UKVNQFHV7kUAg0DVkqLJYLDQ2NmI0GnE6nQnH/X4/gUAg/gvSYrHQ1taWcKzn\ncaFvDocjXp3T6bYTDrtRlNsuOqsFj8eV9Dl8Ph8Gw6/oGo3vwzvU1dXhdDrR6T4jGOz7LFn2Y7O9\nRkdH3yFQkrpDoMVi6xoWnAgVIf+I79124MDrY647SKo8Hg96/duEw6N9J8NPq/X3CoFXXnkl7777\nh6R9nqdMmcIPf/iDhGN33LGKn/1sPeAQw8GCIAzaoEJgQ0MDDQ0NCceuueYadu/eza5du/j0009Z\nv349//RP/4Tf393yKBAI4HA40Ol0BALdXS38fj92uz0eBt1udzz8Wa3WXs+R7JeloFYCZVkdDjaZ\nfkl7+y8JhyuBnt0jzpGX5076HLm5uUhSf0PuzZSX+xKu1ZdoNEA4/BqhUO9qReIGx2p1xOudOJXA\ni4f8htt4DYCghkCNZiKE/4FpNG0JfxiD+rVzuZL/UdaX+fPnY7NF+OSTt0QlUBCEQctYn64XX3wx\n/v+vuOIKfvzjH6PT6dDr9TQ2NlJUVMS+ffvYsGEDGo2GLVu2sG7dOpqbm1EUBafTSW1tLXv37mXF\nihXs3buXuro6KioqOHHiBK2trRiNRvbv38/69euT3kdTU2Zbno03oVCIaFQNcIrSzJ13ruPJJ28l\nGPyox1ktWK3aXq9VW1sbTU1NBINBotHkw7w63cc4HE46Ozvj1+pLONxGNBpEUSJ8+umnCUElGAwS\niXSHQKNRj8WiQ5Y/JTrud4kJoNFEs/57MT2jFQJfBC4F0gthg6UorXR2dmbke2Pt2pv5h3/4e/x+\nP0ajUXy/DVLs954wOOL1G9+GpVmrJEnxIeEHH3yQe++9l2g0ysKFC5k5cyYAc+fOZdWqVSiKwqZN\nmwC46667uO+++3juuedwuVxs3boVrVbL/fffz7p161AUhZUrV/Y7n83n8w3HP2ncaG1tJdaOTVE+\n5Z577uHxx58AgoA6V1Ov/4zS0pJer1VTUxM+n48LFy6gKMn7DxuNp6iunk9lZSX9dScJhwMsWnQF\nr732CoWFhQmfi0QiXV0uANrIz8+loqICg+ENLlxI8x89xshyAJ+vIOu/F1M1ZcqUIXaLUYCfY7N9\nn0BgDdHo3wz4iNjjDIb1dHR8C/jSEK6fiiggI8sBSktLM/K9cffdf82WLd+lvLwcEL/7Biv2e08Y\nHPH6DU1zc/OoXn9YQuCvfvWr+P+fOXMmzz77bK9zNmzYwIYNGxKOeTwennjiiV7n1tfXU19fn/H7\nnIicTifh8GeAQjD4Cfn5+VgsblpbzwFeAHS6FlyumUmfw263Ew4nD3ey3ITX6+1xrb5EiEQ6WL78\nCt54o/dCHrvdTijUXQl0udSFIeN/gcAFTKb/prLym6N9I+OGx+MhFBpKCPwleXnfZO3am3niiV/R\n2tpfCFSQ5alEoy8DbXR0fIzV+kv8/lgIPI9Gs45I5GfE/mgauoNI0tdQlF+hKL0XhgxWcXExv/rV\nr/B6vaP+RiIIwvgkNoueYBwOB52d54EAkiRhtVqxWhN7s2o05/qdg5QY0HqLRJrx+dQ5gaFQshDY\njk5nYtGiRV0bQyeyWq2EQn7UCkkrbrcaAmV5fM8NMxi+wtKlU3rNmRWSU7/uEeDMoB5vtT7Mtm1/\nx5e//GVCoUMDnP0x0eh7yPLPgJe46qobCIV+DairUgyG/5dI5Hngj4O6l76dRKv9EIBIxN9rTuBQ\nLF26dFzPBxUEYXSJEDjBqNvzKMBJHA512NzpdAPn4udI0jnc7uQLQ7r3eexrebBCMKhWAk0mE4oS\nQR1qvpgfo9HKJZdcwssv/7LXZzUaDTqdCQggy204nTZycnJQlPFcCXwLi+W/2bHjCfHGnAZZlqmu\nrgVeG8Sjf4vF8jGrVq2ipKQESeoA+quKHcHh8GA2P43d/kvuvPMOvN5i4ABwAIPhORYuvAr4wyDu\nJZmzRCLNgEI4nLlKoCAIwlCJEDjBSJKEyeQA3iMnJx8Aj8dNz0qgovRfCQQwGm3E5hYmakWj0WK1\nWruulaxNXQCjUT0n1t/4YiaTHbVbiPrGOPRhwdH2G2644XrxJj8IS5bUotWmHwItln/iW9/aiFar\nRZIkpk+vBd7o5xF/YOXK1ZhM7QQCL3PFFVdwww1XIUn/isl0Cz/60Q+4+urFaDSZDIEtRKMdwGeE\nw723iBEEQRgtIgROQFarE3gvvoAmJydxODgcbhkwBJrNdvpe9NGE2+296Fp9DQn7MZn637/MYlGv\nEdtA1+Px0NFxFrWSOf5YrXu5+urFo30b49Jll9VhNv8u7cdptceZPXtW/OOFC+cgSX0NCZ8CwGT6\nA3Pn1rBu3VqmTJmFx+PhhhuuRlGe4Nvf3sjq1auoqZmOxZK5ENi9/c1JFCWKXq/P2HMLgiAMhQiB\nE5DdroZAn08Ngfn5icPBnZ0DVwJjAa23ZvLzu1eCqdfqKwQGsFj6n/tktdqB88hyG3a7HaPRiE5n\noO/K4linEA7/liVLloz2jYxLc+bM4cKFg0AorcdFIqcTdgu47LJarNaLK4H/iyxPBtrR648wbdo0\nvvGNv2X3bnXD+2XLlrFnzx7uuecrAEyfPp1o9J0h/GsSGQyxEPg+RqNNTBUQBGHMECFwArLb1eHg\n0lJ1ODg/39Vj8+cgihIdcINZm+3iEHgUdbJ8E0VF3ZVAhyPZcLB/wE4GxcVFwHEkqXuelDqP8XS/\njxub3sVqNVNcnLwns5Cc3W7H56sA3kzrcZ2diSGwtraWaDSxEqjTPU80GgD+iwsXjjB9+nTsdjvV\n1dUAaLVali5dGj9/0qRJBIPNQPJtktKh1Z7FYDCihsCR3URcEAShPyIETkButxNJeg+vV31z9Hjc\n6PWxSuA5LBbXgNUIu71nR48OLJYVWK1fJ9YtpOe1kg0H22z9v+HNnz8DWX4baI2HQHUe43gMgXtF\nFXCIliyZT3qLQy4QiXTgcDjiRyZPnkw4/CndlW8Fvf4XfOlLX8Jg+D5Go7GrR3VyWq2W4uIpZGqF\nsCSdZcqUqcD7mM1iPqAgCGOHCIETkNvtQFHUPQLVj93odLFK4LmuLWP653CoizYAtNotzJxZSGfn\nr9Hp3qO0tLsS6HY7SDYcPFD/3FmzZmC1vk002l0JzM/PAz4Z8P5Gk0bzL8BzCcdMple49loxH3Ao\n5s2bhcmUzly809jteQl/0MiyjNvtIzYHEI5gNHby4IMP0tn5GpWV01N65hkzppOpFcKK0sKMGdOR\n5fewWEQIFARh7BAhcALKy3N2/VetBLpcLjSaWGWkBadz4BDodseGg1vRaLawY8e/Ule3gEjkWbze\n7hCoXqvvSqDd3n8lcMaMGUQih4lEukNgYeHYrwRaLE9gtf5bwjGt9igzZswYpTuaGAoKCtDp0vkD\n4DRud+/uQS6Xh1gbOln+NxoaVpCXl8fcufXU1k5L6ZkvvXQ6Wm1mQmAodJaamhok6f2M7hEoCIIw\nVMPSMUQYXbm5agjsWQnsXh088KIQAI8nFgJPkpvro6ysjD//81v43e9eTGgRlJvrRJY/66Pfrx+X\nq/83vMmTJ9PZeZpoNBIPgeo8xrFcCTyL338Is/lcwtFI5Ez89RYGJz8/H0lKLwT21UJSHe5VQ6DV\n+grLl6sLPv7xHx/u2kdzYDNnzsBsfozWhLVRh4ACIJ0WWQodHWeZPn06kcjprmkWgiAIY4OoBE5A\nsTlSPSuBkUh3CMzJSb5RdEx3CDxFXl4BADfddBOyLCdUAp1OJ3p938PBDkf/w8EajYbS0moikY54\nCCwoyMNoHMuVwJdYsOAqOjub6K6AKnR0nOm3p7UwsPz8fCKR9EJgbN5rT3l53SEQPon3rZ43bx6z\nZ89O6ZlnzZpFKPRW/GNJehKNZh6y/M9p3B+AH41GR1lZGQAOh6gECoIwdogQOAE5nU4kSY5PgHe7\n3YTD3QtD8vIGrgTabDb0+jbgFIWFagjMy8vjhRdeYPLkyQnX0mo/Q6v9FyRpZ/y4VjvwwhCAOXNm\noNOZ0Wg0gBoE0hsSHFkm04usWvU5qqpmo1aGANqQZc2Aq6GF/hUUFBAMpvO1/yS+Ar4nr7c7BIZC\np8nNzU37XoqLi7u6j3wCvIHN9g0eemhzH9vPDOQsNpsn/oeT0ykqgYIgjB0iBE5ADocDlysXWVa/\nvC6Xi46Oz1D79LZQUDBwCLTb7eh0aiWwtLQgfvyGG26IBzaIBc7T6HSbsNmejB/X6QIphaJ582Yk\nrJjMy8tDlsdqJVBBkn7JNddcw+LFdaitxkBdoJAzmjc2IahdaAD8KZ2v052O74XZk9cb60EdpaPj\n00GFQEmSmDJlFvAWkvRLVq9eyerVqwmH09vCBlpwODy43W40Gt2AUyQEQRBGkgiBE5DT6cTn666Q\naLVa9Hozaou2c7jdqYVAjaYVrfYUJSUFSc9zOp20tf2aiopigsHXiPUb1mj8KU2CnzlzJi5XdwjM\nz88nGh2rlcCT6HRRJk+ezIIFdVitB7uOn8bpFCEwE5zOfLpX9vbPYEg+J1DdoPkz9HrLoDt0zJ8/\nG3gTm+3XXHvtUsrLy4lG24B0+lufxe12I8syDkd+wve6IAjCaBMhcAKaN28eTz75rwnHrFZ1cYhO\nd65roUj/7HY7ktSK0dhMQUH/IRCiPPzwNykvnwr8LwCynFoIXLRoET/4wdb4x3l5eXR0jNVKYBs2\nmxtJkpg7dy49K4G5uf3vPSekRt0nMrU/AjSa5CFQpzsLnMHpHPw8zUsumYXFcoBg8HdcfvnlSJLE\n1Kmz6b838cXOxr838vO9om+wIAhjigiBE5Ber6euri7hmN3uAs6h0aS2OthuVxeGaDSn+g2BhYWF\nXHnl1dxwww0sX34lGs3LAEhSasPBRqORG2+8Mf6xuoilHQgiyxuA4wM+R7dOJOnPGL7ewwFMJrXT\nSlVVFaHQadTFIacpKBCVwExQv9dSC4E998LsyePxIElngdN4PIMPgbNnz+bChRcoLp4c/8MpVh1M\n3Vny89UQWFJSILaIEQRhTBEhMEu4XGolUJJaUgqBNpsNRWkjGu0/BDqdTl566UU0Gg3XXrsMi+VX\nXZ9JrRJ4MUmSsNlygZPA48DeNB79EYryDPB+2tdNTTtmsxpsNRoNRUWVwHvAaQoLRSUwE4qLU68E\nhkLJK4GKolYC8/LSnw8Yo7aVi3Lddd0t5S67bA4WSyqVwBOoFcOz+Hzq98bXv76Rq666atD3IwiC\nkGkiBGYJdVuYM4RCJ1PaysRutxOJtNLZ2X8I7GnhwoUEg4cBP4qSWiWwL253PvDvRKMhdLp0qi4f\ndP03ndZj6WjHYunuuTxlShXwPgbDJ+TliUpgJpSUpBoCky/68Hg8hEJqJbCwcPCVQIPBwLRpM7ju\numXxY3PmzEGSDgF/Ai4kfawsP4HV+kX0+pb4cHB9fT2lpaWDvh9BEIRMEyEwS+TmuoAnmDSphMrK\nygHPt9vtdHScJRxuG7DXaozRaMTnmwS8RzQ6uEogqPMCJWkXM2bMwmx+a+AHxB3DZDJhNA5XCAwk\nhMDZsyuRpPfQ60+TkyNCYCZ4vfmYTKmEwHMYDNY+F314PB46OtQQWFw8+EogwJ49L3LdddfFP66u\nrsZkCmCzzcVsXgh09vk4q/Vt/P4DwG9SmoMrCIIwGkQIzBIFBW5gD9/5zjcSeq0mY7VaCYcvYLfn\nxbeaSYW6h+D7hMODD4E+Xz6K8jpf/eqXCQbfJNU5fnr9MW666Sb0+uGrBNps3dXNqVOrsFjeR5ZF\nCMwUdZ/IVFYHn8bl6rtDi9FoRKPRodd/REHB0Dbwzs3NTfh50el0nD7dyPnzp5g3rxCd7oE+HxeN\nvs3NN6+ks/OtlP+IEgRBGGkiBGaJvDw3kyfP5Prrr0/pfI1Gg8FgxeNJbSg4Rq2OvU84PPjh4OLi\nPCRJ5pZbbkGv16IOvQ3MaPyAW265hWDwGNA2qGv3rx2brbsSWFlZiSy/RzQqQmCmFBQUpNg67hM8\nnuRVPpvNgywfHdQegamQJImf//xfMRp/jDovtKcAHR3NPProwwAiBAqCMGaJEJglbr/9dl544ecp\nVQFjjEZbyvMBY6ZOrcRsfpdw+AJms3ngB/ShqCifysrZOBwOqqvVDXtToSjHqK6u7urm8fqgrt2/\nxFZ4VVVVBIPvEwp9IkJghqTeOu581/ZEfXM4PIRCfxzWVn55eXnU1c0H3r7oM3+gpGQqlZWVfOc7\nj3QtMBEEQRh7RAjMEoWFhUybNi2tx1gsdoqK0guBlZWVSNJb6HSmtIaRe1qyZAlf//qXAViwYDaS\nlMrikAjt7cepqKhg2bL5SNJwDAm3Y7d3B1u3241Op6WjI7Vtd4SB5efn09GRSghMHJq/mNvtIRI5\nP+z9nKdPnwQcu+jo28yaVQPA/fd/vd+wKgiCMJpECBSSslrtlJenHwLb2/+I0Tj4/dAuvfRS/uIv\nvgBAXd0szObfoe7H15+Psdk8mEwmpk6dhNHYOOjrJ6PRJK4OBigpqcJqzUlopScMnjqPNAoEBjgz\ngNWavNKcm+vu+u/wDAfHTJ1agdGYGAL1+reZN2/GsF5XEAQhE0QIFJJyu+34fOmFwIKCAnQ6IyZT\nZjbFXbp0KaWlZ9HpfMDOfs78gJKSSYBaodNqWzJy/Z602t7zHKurK3G5hrfalE0kScLpLAA+HuDM\nduz25JXA2AbNwz1MP2nSJAyGD7s+Og+0YzK9w4wZIgQKgjD2ZSwERqNRHn74YW677TYaGhp45ZVX\nAHjzzTe59dZbue2229i+fXv8/O3bt7Ny5UrWrFnD4cOHATh37hzr169np4uF6wAAIABJREFU7dq1\nbNy4kY4OtQ/tnj17aGhoYPXq1ezatStTtywMYMqU1LaT6UmSJHy+yZhMg1sUcjGfz8cf/vB7vva1\ne4Gj/Zx5jKlTJwN09WodjhDY3mue4+zZVcM+5JhtFiyYD/x6gLMS52dezOfzYDa70Wq1Gb23i02a\nNIlIRK0EWixfxGisoL399yIECoIwLmTsN+QLL7xAJBJhx44dfPLJJ7z44osAbN68me3bt1NUVMQX\nv/hFjh49SjQa5cCBA+zatYvm5mbuvvtudu/ezWOPPcby5ctZsWIFjz/+ODt37uT222/n0Ucf5fnn\nn8dgMLBmzRqWLVsm9t4aAT/5yY8H9bgpUyo5cSK1Fb2pKi4uxGQ6wIUk+/NqNMeYMUOtBLpcLhTl\nXEavDyDLvUPgggXzaWsbaOhSSMfq1cvZs+dntLX9VT9nJc7PvFhurmdILeNSVVpayoULHwMhFGUf\nzzzzGPv3H8Ln8w37tQVBEIYqY5XAffv2kZeXx1/91V+xadMmli5dit/vJxQKUVRUBMCiRYt49dVX\nOXjwIAsXLgTA6/USjUZpaWnh0KFDLF68GFAXB7z22mscO3aM0tJSrFYrOp2OuXPnsn///kzdtjAM\nZs2q7HfS/mD4fD50uqaknzeZTlBWpnZjcLvdRCKZrwT21Q952bJlfPe7j2T8WtnsmmuuoaPjFaA9\n6Tk6Xf9bEHk8Hrze4Z0PCGqfbpfLC+xDowlz88038+ijD6e1Cl8QBGG0DKoSuHv3bp566qmEY263\nG4PBwI9+9CP279/P/fffz9atWxM2DLZYLDQ2NmI0GhNWzFksFvx+P4FAAJvNFj/W1taWcKzncWHs\nmjZtCkeO9Dd0m77CwkL6myem0bTE53+53W5CoeEIgb0rgULmuVwupk+v5Y039gA39HlOX0PzPV1y\nySWsXds6THeYqKSkgk8/fYZLLlkgwp8gCOPKoEJgQ0MDDQ0NCcc2btzI0qVqo/VLLrmE48ePY7Va\n8fv98XMCgQAOhwOdTkcg0D2E5vf7sdvt8TDodrvj4a+v57Db7UnvrakpebVI6F9bW1tGXr/FixdT\nU1OT0a+FLMt0dCQfYo5GPyUSidDU1EQ0GiUUagdCgC5j9xCNqn+U9PXvytRrl60ufv2uv34JR478\nOx0dfYdAWW6js7Mz6WvucDi45ZabR+RrUlrq5dCh3cybd/eofQ+I77/BE6/d0IjXb3zL2JzAuXPn\n8sorr3DVVVdx9OhRfD4fFosFvV5PY2MjRUVF7Nu3jw0bNqDRaNiyZQvr1q2jubkZRVFwOp3U1tay\nd+9eVqxYwd69e6mrq6OiooITJ07Q2tqK0Whk//79rF+/Pul9iLk4g9fU1JSx16+8vDwjzxNTUFBA\nONwKdACGXp9XlM+YMmVK/P7NZieBwGdA5oYEJSlIWVlZn69RJl+7bHTx6/eFL9zB1q0LgR/S168p\njeYChYWFY+I1v/TSGfziFzu49tprRu1+xPff4InXbmjE6zc0zc3No3r9jIXAlStXsnnzZlatWgXA\ngw8+CKgLQ+69916i0SgLFy5k5syZgBoaV61ahaIobNq0CYC77rqL++67j+eeew6Xy8XWrVvRarXc\nf//9rFu3DkVRWLlypViNmYVkWcZuz+fcuWagrNfnQ6GWhMVCNpubQKCFTIbAaFQMB4+UyZMnU1pa\nxtGjLwPX9vq8JLUPui1hplVUVKDRaKmrqxvtWxEEQUhLxkKgXq/nO9/5Tq/js2bN4tlnn+11fMOG\nDWzYsCHhmMfj4Yknnuh1bn19PfX19Zm6VWGcys8v5Ny5JnqHwDChUFvCPFOHw8WpU5ldIRyJBEQI\nHEFf+tLtfOMbO2hv7x0CYfC9qTOttraWm2++BZPJNNq3IgiCkBaxWbQwbhQVJVsc8hlGoyOhTZ1a\nFczs4pBIZOxUn7LB6tWriET+g75XCY+dquzkyZN57rn+NjIXBEEYm0QIFMaNsjIf0NcE5BZstsR9\nI3NyMh8Cw+GxEzyyQX5+PnV184Dnen0uGh07lUBBEITxSoRAYdyYNKkQjaavSmALTmdiCMzLcwGZ\nHA7uBBR0usytNhYG9u1vfw2L5WHUld7dxPxMQRCEoRMhUBg3fD4fJlPflUCPx5NwxOvNdCWwHb3e\nIvaBG2FLly5l2rRi4KcJx8NhUQkUBEEYKhEChXGjsDBZJfBs1/BvN4/HjV6f6RAoKk+j4Xvf+zZm\n88OAEj8WiYhKoCAIwlCJECiMGz6fj2i070pgQUFiCHS5XOj1mRwODmA0isrTaFiwYAEGQydwrOtI\nhEikQ6zGFQRBGCIRAoVxo7CwkGDwY3pWhFS9Q6Db7UajyWwl0GQSlafRIEkSCxcuBvZ1HWlHpzOL\noXlBEIQhEiFQGDfsdjs6nR44k3DcYGghJydxTmD3FjEh4P0MXF2EwNF07bWLMJl+2/WRGJoXBEHI\nBBEChXGluLiSi0OdXp/YLQTU4eBotAX4KUbjn2XgygHMZjEcPFoWLVqERhOrBIqheUEQhEwQIVAY\nV6qrq7g4BMry2V4h0O12Ew6fw2bbhSyfzMCV27FYRPVptNTU1BCJfAKcBtoxGsXXQhAEYahECBTG\nlVmzKpHli4d3+64EBoNn6ex8jY6Os0DHEK8sQuBo0mg01NYuQJ0XKKqygiAImSBCoDCuTJlSicWS\nGAIjkd77BBoMBrRaA5dffiUul4++282lI4DNJoLHaLr66vlotb9HzM8UBEHIDBEChXGlsrISSXov\n4Vgo1LsSCOqQ8Lp1t+LzlQBDHRJux24XwWM0lZeXYTQ2AgGsVhHIBUEQhkqEQGFcqays5MKFD+je\nJiZCZ2crDoej17n//M//yE033URFhQiBE0H3ZuHtIgQKgiBkgAiBwrjicDi6FgU0dx35DJPJjkaj\n6XXuihUrMBqNTJlSgiQNNQQGcDhE8BhNhYWFRCIfo1YCRSAXBEEYKhEChXGntLTnCuEWbDZPf6dT\nXl6C0Ti0EKjVijZlo617s/AAdrsI5IIgCEMlQqAw7lRX99wr8FOczt7zAXsqKSlBrxchcLyzWq1o\ntXrgT2JoXhAEIQNECBTGnVmzKtFqY4tDPqKioqzf80tKSohGhxYCNZoAFouoPo22nJxC4H0xNC8I\ngpABIgQK40519VTM5qMASNL7zJpV2e/5xcXFBIMn6d1zOHWyLCqBY4HPV4QkvSf2bBQEQcgAEQKF\ncWfatGlEo38EwGJ5r6uLSHJ2ux2tVgecG/Q1JSkgQuAYUFpaiKJ8IKqygiAIGSBCoDDuTJo0iWDw\nT8AFNJr3qazsvxIIkJ8/tG1iJMmPzWYb9OOFzJg8uRAIikAuCIKQASIECuOOTqfD650EvEswmFoI\nLC4uAU4M+pqKcr7PvQiFkVVcXAggKoGCIAgZIEKgMC5VV1cDe5FlerWM60tZmQ9oGvT1otFW7Hb7\noB8vZEZhoQiBgiAImSJCoDAuXXrpNCTp3ykpqUKSpAHPLy/3IknNA56XTCgkQuBYEAuBYjhYEARh\n6DIWAv1+P3feeSe3334769at4+zZswC8+eab3Hrrrdx2221s3749fv727dtZuXIla9as4fDhwwCc\nO3eO9evXs3btWjZu3EhHRwcAe/bsoaGhgdWrV7Nr165M3bIwjtXUTENRXmHq1IGHggF8Pi8m02BD\noEJn53kRAscAUQkUBEHInIyFwOeff54pU6bws5/9jOuuu44nnngCgM2bN7Nt2zZ27NjB4cOHOXr0\nKEeOHOHAgQPs2rWLbdu28dBDDwHw2GOPsXz5cp555hmmTp3Kzp07CYfDPProo/zkJz/h6aef5tln\nn6WlpSVTty2MU9OmTQPCA24PE+P1etHpBhsCL6DR6NDr9YN8vJApeXl5aLVaUQkUBEHIgIyFwKqq\nKvx+P6BWBXU6HX6/n1AoRFFREQCLFi3i1Vdf5eDBgyxcuBBQ35yj0SgtLS0cOnSIxYsXA7BkyRJe\ne+01jh07RmlpKVarFZ1Ox9y5c9m/f3+mblsYp6qqqpAkmSlTUg+B3f2G09WKySSqgGOBLMvMmVNL\nbm7uaN+KIAjCuKcdzIN2797NU089lXBs06ZNvPrqq1x//fWcP3+eHTt2EAgEsFqt8XMsFguNjY0Y\njUacTmfCcb/fTyAQiG/DYbFYaGtrSzjW87iQ3QwGA9XV05g+fXpK53u9XkKhwYbA81gsYmXwWPH6\n678f7VsQBEGYEAYVAhsaGmhoaEg4dvfdd3PnnXdy66238u6777JhwwZ27NgRrw4CBAIBHA4HOp2O\nQCAQP+73+7Hb7fEw6Ha74+HParX2eo7+5mY1NQ1+BWi2a2trG1ev3wsv/AKz2ZzSPUciEYLBM0AE\n0KR5pVaMxv6vM95eu7FGvH5DI16/wROv3dCI1298G1QI7IvD4YhX/WIhzmq1otfraWxspKioiH37\n9rFhwwY0Gg1btmxh3bp1NDc3oygKTqeT2tpa9u7dy4oVK9i7dy91dXVUVFRw4sQJWltbMRqN7N+/\nn/Xr1ye9D5/Pl6l/UtZpamqa0K+f2ezC7z8DFKT5yFbcbk+/r81Ef+2Gm3j9hka8foMnXruhEa/f\n0DQ3D37XikzIWAj88pe/zLe+9S127NhBOBzm7/7u7wB1Yci9995LNBpl4cKFzJw5E4C5c+eyatUq\nFEVh06ZNANx1113cd999PPfcc7hcLrZu3YpWq+X+++9n3bp1KIrCypUrycvLy9RtC1kkJ8eL399M\n+iHwPA6HmBMoCIIgTCwZC4F5eXk8/vjjvY7PmjWLZ599ttfxDRs2sGHDhoRjHo8nvqq4p/r6eurr\n6zN1q0KWKijwcvx4MzAnzUe24nKJECgIgiBMLGKzaCFrFBcPdoVwKx6PWBgiCIIgTCwiBApZo7x8\nsCHwPDk5ohIoCIIgTCwiBApZo6jIi8GQfgjUaltxOkUIFARBECYWEQKFrOH1Di4E6nStOBxiOFgQ\nBEGYWEQIFLKG1+tFkgZTCRR9gwVBEISJR4RAIWt4vV7C4fRDoCy3ihAoCIIgTDgiBApZw+v1Egye\nApQ0HymGgwVBEISJR4RAIWuYTCb0ehNwLq3HKYoYDhYEQRAmHhEChazidqe/TUwkIoaDBUEQhIlH\nhEAhq+Tne4H0mp2Hw2I4WBAEQZh4RAgUskpxsY/0KoEKnZ2t2Gy24bolQRAEQRgVIgQKWaWiIt3h\n4ABarRGtNmNttgVBEARhTBAhUMgqxcVe9Pp0hoNbMZvFULAgCIIw8YgQKGQVn8+XZteQ81gsYlGI\nIAiCMPGIEChkFa/XiyynVwm0WkUIFARBECYeEQKFrOL1eolE0qkEtmK3i+FgQRAEYeIRIVDIKmrX\nkGZS7xpyivz8nOG8JUEQBEEYFSIEClnFarWi0WiB8ymdr9W+w7x5NcN7U4IgCIIwCkQIFLJOOl1D\nzOa3mTlzxvDekCAIgiCMAhEChayTl+cF3kOrrQVO9ntuOPw2NTWiEigIgiBMPCIEClmnuNiHJN1L\nNPoW8FLX0d8Apy868zMikc8oKysbydsTBEEQhBEhQqCQdcrKvOh0p9i8eTMWy68BBYPhC8ALF535\nDuXl05Fl8WMiCIIgTDzi3U3IOjfddB3/8i8/Ys2aNSjKr4HX6eg4iU539KIz36a2VswHFARBECYm\n0RBVyDpXXnklAIqiYDTKdHQ8zPTpszh+/I+EQt3nGQxvc+mlYj6gIAiCMDENqRL40ksvcc8998Q/\nfuutt7j11lu57bbb2L59e/z49u3bWblyJWvWrOHw4cMAnDt3jvXr17N27Vo2btxIR0cHAHv27KGh\noYHVq1eza9cuQH2zfuCBB1i9ejV33HEHjY2NQ7ltQQBAkiTq65cSifwHDzywiWg0sRJoNL7DjBmi\nEigIgiBMTIMOgQ8//DDf+973Eo498MADbNu2jR07dnD48GGOHj3KkSNHOHDgALt27WLbtm089NBD\nADz22GMsX76cZ555hqlTp7Jz507C4TCPPvooP/nJT3j66ad59tlnaWlp4eWXX6azs5OdO3dyzz33\n8MgjjwztXy0IXZYvv4KysinceOONXZtIX+j6zAWCwcPMmjVrNG9PEARBEIbNoENgbW0tmzdvjn/s\n9/sJhUIUFRUBsGjRIl599VUOHjzIwoULAbVbQzQapaWlhUOHDrF48WIAlixZwmuvvcaxY8coLS3F\narWi0+moq6vj9ddf5+DBg/FzZ82axTvvvDPY2xaEBGvXruW3v30ZrVaL1zsJeLfrM/8/s2dfisfj\nGc3bEwRBEIRhM+CcwN27d/PUU08lHHvkkUe47rrreP311+PHAoEAVqs1/rHFYqGxsRGj0YjT6Uw4\n7vf7CQQC2Gy2+LG2traEYwBms7nP41qtlmg0KlZtCkOm1Wrjf7hUV0+lsfEoMBur9Rnuuuv20b05\nQRAEQRhGA4bAhoYGGhoaBnyiWLiLCQQCOBwOdDodgUAgftzv92O32+Pnu93ueMizWq19PofVak14\nDhEAheEwd241L730RxTlLOHwK3z+80+P9i0JgiAIwrDJ2Opgq9WKXq+nsbGRoqIi9u3bx4YNG9Bo\nNGzZsoV169bR3NyMoig4nU5qa2vZu3cvK1asYO/evdTV1VFRUcGJEydobW3FaDRy4MAB1q9fD8Cv\nf/1rrr32Wt58802qqqqS3kdTU1Om/klZp62tLatfP58vH7P5N7S3/x8WLarH7/cn/FHSn2x/7YZK\nvH5DI16/wROv3dCI1298y+gWMQ8++CD33nsv0WiUhQsXMnPmTADmzp3LqlWrUBSFTZs2AXDXXXdx\n33338dxzz+Fyudi6dStarZb777+fdevWoSgKDQ0N5OXlcdVVV/Hqq6+yevX/Ze/Oo6Ou7/2PvyZM\nFpIhEFvIgCHkgiCioJJcfz2AHKTSkisIKbGSKGDJrcaCohgKESQBZalH6CIgeqkbKJtbbW9t1YOS\ny+IF4gWqNLRVlghJZFMyA5KQ+f7+iDMmYUgmySSzfJ+PcziHfOe7fL6f+cx33vNZJ0pSowNDevTo\n4c9bMpXjx4+bOv+GDBmi8+cfVq9eJXrmmbealRdmz7vWIv9ah/xrOfKudci/1ikr820d+7ZiMQzD\nCGgK/Ki4uFipqamBTkbIMvuHuaqqSqtWrdLPf/5zxcXFNetYs+dda5F/rUP+tRx51zrkX+sEOm5h\nsmjgW1FRUXrooYcCnQwAANoFoysAAABMiCAQAADAhAgCAQAATIggEAAAwIQIAgEAAEyIIBAAAMCE\nCAIBAABMiCAQAADAhAgCAQAATIggEAAAwIQIAgEAAEyIIBAAAMCECAIBAABMiCAQAADAhAgCAQAA\nTIggEAAAwIQIAgEAAEyIIBAAAMCECAIBAABMiCAQAADAhAgCAQAATIggEAAAwIQIAgEAAEyIIBAA\nAMCEWhUEvvfee3rkkUc8f+/cuVMTJ07UpEmTNGPGDF24cEGStGLFCt1xxx3KysrS/v37JUlnzpxR\nTk6O7r77bs2cOdOz75YtW5SZmamJEydq8+bNkiTDMFRQUKCJEydq8uTJKi0tbU2yAQAATK/FQeCi\nRYv061//ut62hQsXatWqVVq7dq169eqlzZs368CBA9qzZ482b96s5cuXa+HChZKklStXauzYsVq3\nbp369++vDRs26OLFi1q6dKlefPFFrV27Vhs3btTp06f1/vvvq6qqShs2bNAjjzyiJUuWtO6uAQAA\nTK7FQeDgwYNVWFhYb9vatWt1xRVXSJIuXryo6OhoFRcXa+jQoZKk7t27y+Vy6fTp0/r444918803\nS5KGDx+unTt36rPPPlOvXr1ks9kUGRmptLQ07dq1S8XFxZ59r7/+en3yySctTTYAAADkQxD42muv\naezYsfX+ffLJJ0pPT79k3+9///uSpHfffVe7du3SuHHj5HA41KlTJ88+cXFxcjgccjqdnu1xcXGq\nrKyst02SYmNjvW63Wq1yuVwtv2sAAACTsza1Q2ZmpjIzM30+4Ysvvqh3331Xv//97xUVFSWbzSan\n0+l53eFwKD4+3hMMXnHFFZ4gz2azyeFwePZ1Op3q3LnzJedwuVyKiPAevxYXF/ucVlyqrKws0EkI\nWeRd65B/rUP+tRx51zrkX+hqMghsjmeeeUZ///vf9eKLLyoqKkpSbbPxU089palTp6qsrEyGYahL\nly4aPHiwioqKNH78eBUVFSktLU29e/fWkSNHdPbsWcXExGjPnj3KycmRJH3wwQcaPXq09u7dq379\n+nm9fmpqqj9vBwAAIGz5LQg8deqUVq5cqeuuu045OTmyWCz6j//4D02cOFGpqam68847ZRiG5s+f\nL0m6//77NXv2bG3atEkJCQlatmyZrFar8vPzNXXqVBmGoczMTHXr1k2jRo3S9u3bNXHiREliYAgA\nAEArWQzDMAKdCAAAALQvvzYHB4JhGCosLNTBgwcVFRWlRYsWqWfPnoFOVtD7yU9+IpvNJklKSkpS\n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\nWy0H8x+GZz8uX/ukNmwC9VVLjwtXwbjOc6j9UG9L7rwIxiC5vVgMwzACnQh/KS4uVlpamtfXmrpN\ni8VSbz/33w3P4W2/huf2ts3bOS93vsu53D00PEdztl0ubS29Xt1j3Q+/o+VHL3ts3eMaS5ev9+Ht\n2IbpariPLx+By73PjWkqf3zZ5u215rzPvhzTVJlrqtw2NvKxJem7HF/fq8aOr6up8l13P18+95c7\nV3P3ayx/WvJ5aep1X+63qWObSntzz5eYmCjpu5G+3u6hJWnxdlxTGnvvfc3vutdt7vOrqc+pL8+0\nlnyveDs+sWdPSd/9cLzcdRo7p6/frU2drzVlvbHPU3OfWS35TLq/G0srSrVnzx6lpqZ6vYf2ENY1\ngWhaW9b40JxuDqFaYxYuNbnhKFwnYQ/1FoVw6B/pL62p9Q6m78aQCQINw1BBQYEmTpyoyZMnq5SC\n6BetediG8/JW4XxvbSXUmpD93QQUyC/zti6vTGvkH97KXLA1Rfq7LIVTF4G6n/FwCYhDJgh8//33\nVVVVpQ0bNuiRRx7RkiVLAp0k0wvn5a3a+t5C6de/r8K19sZXgfwyb+vyGqw1vf7ocxeMP14C+Xzw\nd1kKl2BJCr6A3R9CZsWQ4uJi3XzzzZKk66+/Xp988kmAUwS0XGseJuG4HFYoa6qWg/er7fijSS0Y\nf7iEY7CB4BQyQaDD4VCnTp08f1utVrlcLkVEBHdlZsOljBD6Aj3a73K/0sOlySXUNFXLESy15YEu\ntwCCT3BHUHXYbDY5nU7P36EQAEo0kYWjYOrUW1c4NbtI7TOVhZlWNAnGMovgZ5bPhz+FUnefkKkJ\nHDx4sD744AONHj1ae/fuVb9+/bzul5jYS7J+I0nK/c9crX7+eUmqN13Jd/0/Ymr3y71HiVfWTkng\n/tIpKCjQ86trj1VUlaTaNzYxsZek75p4CgoKPMd8ExXl2ebuSO2e6sBuT1FERKy6du3qSV+K3a6I\nmBh17drVk76ePRN17NiJ2v2+TZ8knThTKnuSXTEX5bnW19/u17On+xp2nThRqa5du36XziS7Kstq\n93MP70+2J+vE18e+3S/RkxcnTpy4JH2lX3+trl27urNAiqpSREREvfTZ7SmKiKrd5k6fPTlZMRHf\n3tu3eVFVJU8euPPPne+5/5nrqclyX2tq7tR6+Xe59+pwefl3tRzfHjx1aq4nD9xyc+9p9L2qqnLv\nl1sv/9z7udPXM7GnJ32rV6/25J/7GqvXrK6Xvntycz3H1E3f6tUv1ktfYWGhen17v+70RVVJJ74+\npuRk+yXvld2eUi8v3GXJnX/JyXbPe1W3o7f7vXKnL7FnT09Zch9cVSWdOFFZew13Plq/UWXZCaXY\n7fXKUkxM7fnc+We3p3jKUt30ucuSOy8ul74TZ0pry9K3f7vz/XLvlScfvXzuvZWl5r5XTX3u3XmR\nO3XqJekrKCjwpM9dlsq/KL+krBcUFHieS+5rFRYWNvpc8va5cqv7uXeXpZ6JPT2f+7rPwIbPpcLC\nQsUlJMhms3l9LvlSlpLtyfWeSw0/97J+o1j3e18nLxorS+709UpMvOS55K0s1c3Hhp+ruvnnLhd1\nv7jd5SL3P3PrlfXG3iv3Me5nYFOf+6a+Q55/frUn/9zbvJX1xsqSu6wXFBRckj73d0iyPbne5/7S\n90qe98rbc8n9XtUtS+6yHnNRPn3H1f0OqZsXDb9Dpk7N1VNPrZDNZvP6HVc3fe6y5K2su9PnLhf3\n5ObWS5/nPf02v93Xys29Ry9++8x3Xyt36tRL3qvc3NxLvuMKCws9z6W63yGBFjLzBBqGocLCQh08\neFCStGTJEv3bv/1bvX2Ki4svO99OY3NGubc33M/bOXydg6rhebz1C2rOfGm+zPPU2JxHTW1r6TXr\n7tfUvEp1j/VlvjJf76Op97Sxe2pOeWjOnHaXS3NT+7d0rkR/vQf+vK63/epuqzu/YHPfg5bwtaw3\nNu+ar3PfeTtXU+9Hw64jTc1H58u1fH3dn/MKXvrs++6+fJ0v09f78JZOX+d09GX+v7qv161IaOkc\nfk2l2dtxrZmLsKl0NJbuln4nteR5U/f7sbnlq6nvqZbOg9nc53fDc/h6vsbilvYQMjWBFoslpDvL\n+qtTuPuXSyitygF4421yaTML137D4XJfdSe998Zd4xNKS0YGCwZNBU7IBIGodbhOLUFL9UzsSf8g\nE3A3a4Q6d3Mc2p+nORNNKv+i9c/mcEZZCk4EgSZUtzkD4aupmrZeiaERJJYfbbwGJhQ1XH4rWDVV\n+xUKCgoKAnIs6gv2suSPIDUxRJ6pdREEAiZ1OEya6bwJ9iDLHdjyY6zttWakZiiN8sR3vAV0DQcK\nNuSPIDUUuz4QBAIIOwRZUrQke2Ii/YfbQCjW+JiJt4COfofeEQQC8KvExF4BnSC5LZvwmqpN8KfW\n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\nO4qLi83UqVPNkiVLQvfRv8jZu3evWbZsmTHGmJqaGpOXlxfliqLvo48+MtnZ2ebFF180xhizYMEC\nU1VVZYwx5u233zb79u0zjY2NJjs72wSDQePz+Ux2drZpaWkxW7ZsMR988IExxpivv/7aFBcXG2M6\nXrOxaNeuXaa0tNQYY8ylS5fMpEmT6F+Y9u3bZ5YvX26MMeann34yeXl59K6bgsGgef31183TTz9t\n6uvrbdE/S38cPHv2bOXl5UmSvF6vUlJS5Pf7FQwGNWzYMEnS+PHjdfjwYVVXV2vcuHGSpPvuu0+t\nra36+++/9fPPP4eGTE+cOFFHjx7VH3/8obS0NHk8HiUkJCgzM1NVVVXR2ckeNGfOHL300kuS2v6F\nnJSURP+6KSMjQ6tXrw7dpn+RxRD4W6Wlpam8vDx0++TJk8rKypLUtoaOHDmi48ePKzMzU/Hx8fJ4\nPEpPT9epU6dUXV2tiRMnhrb98ccfO1yzR44c6f0d6wVTp07VokWLJEn//POP4uLi9Msvv9C/MEye\nPFlr166V1PZ+27dvX3rXTe+8845mzJih1NRUGWNs0T/LhMAvvvhCzz33XLufEydOyOVyafbs2fr0\n0081efJkBQIBeTye0N9LTk6Wz+dTIBBoN0w6OTlZfr+/3f2dbevz+XpvZ3tAR/07ffq0EhMT1djY\nqDfffFP5+fn07zZut/6mTp3abjv6F1kMgb/VlClT2s30NDddu9fRupKku+++O3T/jfV583rraM3G\norvuuivUi0WLFumNN96gf93gdru1bNkyFRcXKzs7m951w+7duzVgwACNGzcu1LebX8us2j/LfCcw\nNzdXubm5Hf5u27Ztqq+v1/z58/XVV1/J7/eHfhcIBNS3b18lJCS0Gybt9/uVkpISavy9994bar7H\n47nlMVJSUnpu53rB7fpXV1engoICFRYWKisrS36/n/51oLP1d7Mb/biB/t0ZhsB37eZ+3Fgrt1tD\nN/fzxnrraM3G8no7f/68Fi5cqFmzZunZZ5/Ve++9F/od/evaunXr1NTUpNzcXDU3N4fup3ed2717\nt1wulw4fPqy6ujoVFhbq4sWLod9btX+WfrXdvHmz9uzZI6ktLcfFxSk5OVmJiYk6e/asjDE6dOiQ\nMjMzNXbsWB06dEjGGHm9Xhlj1K9fP2VkZKiyslKSVFlZqaysLA0fPlx//fWXLl++rJaWFlVVVWnM\nmDHR3NUe8fvvv2vx4sVav369xo8fL6ntTZf+/ffoX2RlZGTo4MGDkqSamho99NBDUa7IekaNGhX6\nukBlZaUyMzP12GOPqbq6Wi0tLfL5fKqvr9fIkSM1duzYUD8PHjyorKys267ZWHThwgXNnTtXS5cu\n1bRp0yRJjzzyCP0Lw549e0L/AUNSUpLcbrceffRRHTt2TBK968r27dtVUVGhiooKPfzww3r33Xc1\nYcIEy689S88JbGpqUmFhoZqbm2WMUUFBgcaMGaPa2lqVlpaqtbVV48aN0+LFiyW1XZ1ZWVkpY4yK\nioqUkZEReowrV66of//+2rBhg/r06aMffvhBGzdulDFGubm5mjFjRpT3NvJee+011dXVaejQoTLG\nKCUlReXl5fSvm44dO6adO3eGrg4+fvy4SkpK6F8EGIbAd+jcuXPKz8/Xjh07dPr0aa1cuVLBYFAj\nRoxQcXGxXC6XPv/8c+3cuVPGGOXl5Wny5Mm6du2aCgsL1djYqMTERG3YsEEDBgy47ZqNNSUlJfrm\nm280fPhwGWPkcrn01ltvqbi4mP514erVqyoqKtKFCxd0/fp1zZ8/X8OHD9eKFSvoXTe98sorWrNm\njVwul+WPXUuHQAAAAPQMS38cDAAAgJ5BCAQAAHAgQiAAAIADEQIBAAAciBAIAADgQIRAAAAAByIE\nAgAAOBAhEAAAwIH+D021Ozi9hJCHAAAAAElFTkSuQmCC\n", + "image/png": "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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -158,9 +155,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# 显示逐笔回测结果\n", @@ -170,9 +165,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# 显示前10条成交记录\n", @@ -184,9 +177,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# 优化配置\n", @@ -207,9 +198,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# 显示优化的所有统计数据\n", @@ -224,9 +213,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [] } @@ -247,9 +234,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", - "version": "2.7.13" + "version": "2.7.14" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/CtaBacktesting/backtesting_IF.ipynb b/examples/CtaBacktesting/backtesting_IF.ipynb index ff358df6..631c60be 100644 --- a/examples/CtaBacktesting/backtesting_IF.ipynb +++ b/examples/CtaBacktesting/backtesting_IF.ipynb @@ -3,9 +3,7 @@ { "cell_type": "code", "execution_count": 1, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "%matplotlib inline\n", @@ -19,9 +17,7 @@ { "cell_type": "code", "execution_count": 2, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# 创建回测引擎对象\n", @@ -30,24 +26,30 @@ }, { "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": false - }, + "execution_count": null, + "metadata": {}, "outputs": [], "source": [ - "# 设置回测使用的数据\n", + "# 使用历史数据缓存服务器,请先运行startHds.py\n", + "engine.initHdsClient() # 受限于机器内存,超出上限会报错" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# 设置回测使用的数据 \n", "engine.setBacktestingMode(engine.BAR_MODE) # 设置引擎的回测模式为K线\n", "engine.setDatabase(MINUTE_DB_NAME, 'IF0000') # 设置使用的历史数据库\n", - "engine.setStartDate('20130101') # 设置回测用的数据起始日期" + "engine.setStartDate('20150101') # 设置回测用的数据起始日期" ] }, { "cell_type": "code", "execution_count": 4, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# 配置回测引擎参数\n", @@ -60,10 +62,8 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, + "execution_count": 5, + "metadata": {}, "outputs": [], "source": [ "# 在引擎中创建策略对象\n", @@ -75,11 +75,23 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2018-08-06 15:01:09.708000\t开始载入数据\n", + "2018-08-06 15:01:28.454000\t载入完成,数据量:155070\n", + "2018-08-06 15:01:28.454000\t开始回测\n", + "2018-08-06 15:01:28.504000\t策略初始化完成\n", + "2018-08-06 15:01:28.504000\t策略启动完成\n", + "2018-08-06 15:01:28.504000\t开始回放数据\n", + "2018-08-06 15:01:39.480000\t数据回放结束\n" + ] + } + ], "source": [ "# 运行回测\n", "engine.runBacktesting() # 运行回测" @@ -87,12 +99,54 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": { - "collapsed": false, "scrolled": false }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2018-08-06 15:02:11.279000\t计算按日统计结果\n", + "2018-08-06 15:02:11.334000\t------------------------------\n", + "2018-08-06 15:02:11.334000\t首个交易日:\t2015-01-12\n", + "2018-08-06 15:02:11.334000\t最后交易日:\t2017-07-14\n", + "2018-08-06 15:02:11.334000\t总交易日:\t612\n", + "2018-08-06 15:02:11.334000\t盈利交易日\t297\n", + "2018-08-06 15:02:11.334000\t亏损交易日:\t315\n", + "2018-08-06 15:02:11.334000\t起始资金:\t1000000\n", + "2018-08-06 15:02:11.334000\t结束资金:\t1,133,650.64\n", + "2018-08-06 15:02:11.335000\t总收益率:\t13.37%\n", + "2018-08-06 15:02:11.335000\t年化收益:\t5.24%\n", + "2018-08-06 15:02:11.335000\t总盈亏:\t133,650.64\n", + "2018-08-06 15:02:11.335000\t最大回撤: \t-185,949.45\n", + "2018-08-06 15:02:11.335000\t百分比最大回撤: -16.31%\n", + "2018-08-06 15:02:11.335000\t总手续费:\t148,769.36\n", + "2018-08-06 15:02:11.335000\t总滑点:\t267,420.0\n", + "2018-08-06 15:02:11.335000\t总成交金额:\t4,958,978,520.0\n", + "2018-08-06 15:02:11.335000\t总成交笔数:\t4,457.0\n", + "2018-08-06 15:02:11.336000\t日均盈亏:\t218.38\n", + "2018-08-06 15:02:11.336000\t日均手续费:\t243.09\n", + "2018-08-06 15:02:11.336000\t日均滑点:\t436.96\n", + "2018-08-06 15:02:11.336000\t日均成交金额:\t8,102,906.08\n", + "2018-08-06 15:02:11.336000\t日均成交笔数:\t7.28\n", + "2018-08-06 15:02:11.336000\t日均收益率:\t0.02%\n", + "2018-08-06 15:02:11.336000\t收益标准差:\t1.72%\n", + "2018-08-06 15:02:11.336000\tSharpe Ratio:\t0.18\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "# 显示逐日回测结果\n", "engine.showDailyResult()" @@ -101,9 +155,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# 显示逐笔回测结果\n", @@ -113,9 +165,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# 显示前10条成交记录\n", @@ -126,54 +176,9 @@ }, { "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2018-02-16 22:15:18.251000\t------------------------------\n", - "2018-02-16 22:15:18.251000\tsetting: {'atrLength': 12}\n", - "2018-02-16 22:15:18.256000\t开始载入数据\n", - "2018-02-16 22:15:18.319000\t载入完成,数据量:91381\n", - "2018-02-16 22:15:18.319000\t开始回测\n", - "2018-02-16 22:15:18.361000\t策略初始化完成\n", - "2018-02-16 22:15:18.361000\t策略启动完成\n", - "2018-02-16 22:15:18.361000\t开始回放数据\n", - "2018-02-16 22:15:25.992000\t数据回放结束\n", - "2018-02-16 22:15:25.992000\t计算按日统计结果\n", - "2018-02-16 22:15:26.085000\t------------------------------\n", - "2018-02-16 22:15:26.085000\tsetting: {'atrLength': 14}\n", - "2018-02-16 22:15:26.087000\t开始载入数据\n", - "2018-02-16 22:15:26.142000\t载入完成,数据量:91381\n", - "2018-02-16 22:15:26.142000\t开始回测\n", - "2018-02-16 22:15:26.183000\t策略初始化完成\n", - "2018-02-16 22:15:26.183000\t策略启动完成\n", - "2018-02-16 22:15:26.183000\t开始回放数据\n", - "2018-02-16 22:15:34.799000\t数据回放结束\n", - "2018-02-16 22:15:34.799000\t计算按日统计结果\n", - "2018-02-16 22:15:34.881000\t------------------------------\n", - "2018-02-16 22:15:34.881000\tsetting: {'atrLength': 16}\n", - "2018-02-16 22:15:34.883000\t开始载入数据\n", - "2018-02-16 22:15:34.935000\t载入完成,数据量:91381\n", - "2018-02-16 22:15:34.935000\t开始回测\n", - "2018-02-16 22:15:34.981000\t策略初始化完成\n", - "2018-02-16 22:15:34.981000\t策略启动完成\n", - "2018-02-16 22:15:34.981000\t开始回放数据\n", - "2018-02-16 22:15:43.120000\t数据回放结束\n", - "2018-02-16 22:15:43.120000\t计算按日统计结果\n", - "2018-02-16 22:15:43.151000\t------------------------------\n", - "2018-02-16 22:15:43.151000\t优化结果:\n", - "2018-02-16 22:15:43.151000\t参数:[\"{'atrLength': 16}\"],目标:819765.66\n", - "2018-02-16 22:15:43.151000\t参数:[\"{'atrLength': 14}\"],目标:534310.005\n", - "2018-02-16 22:15:43.151000\t参数:[\"{'atrLength': 12}\"],目标:278612.3988\n", - "耗时:24.9000000954\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "# 优化配置\n", "setting = OptimizationSetting() # 新建一个优化任务设置对象\n", @@ -193,9 +198,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# 显示优化的所有统计数据\n", @@ -210,9 +213,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [] } @@ -233,9 +234,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", - "version": "2.7.13" + "version": "2.7.14" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/CtaBacktesting/startHds.py b/examples/CtaBacktesting/startHds.py new file mode 100644 index 00000000..ea6bc44f --- /dev/null +++ b/examples/CtaBacktesting/startHds.py @@ -0,0 +1,3 @@ +from vnpy.trader.app.ctaStrategy.ctaBacktesting import runHistoryDataServer + +runHistoryDataServer() \ No newline at end of file diff --git a/examples/DataRecording/DR_setting.json b/examples/DataRecording/DR_setting.json index 5ec30169..05ae8279 100644 --- a/examples/DataRecording/DR_setting.json +++ b/examples/DataRecording/DR_setting.json @@ -1,5 +1,6 @@ { "working": true, + "marketCloseTime": "15:05:00", "tick": [ diff --git a/examples/FutuTrader/CTA_setting.json b/examples/FutuTrader/CTA_setting.json new file mode 100644 index 00000000..c1d08f4a --- /dev/null +++ b/examples/FutuTrader/CTA_setting.json @@ -0,0 +1,19 @@ +[ + { + "name": "double ema", + "className": "DoubleMaStrategy", + "vtSymbol": "rb1805" + }, + + { + "name": "atr rsi", + "className": "AtrRsiStrategy", + "vtSymbol": "IC1802" + }, + + { + "name": "king keltner", + "className": "KkStrategy", + "vtSymbol": "IH1802" + } +] \ No newline at end of file diff --git a/examples/FutuTrader/Futu_connect.json b/examples/FutuTrader/Futu_connect.json new file mode 100644 index 00000000..6af766f1 --- /dev/null +++ b/examples/FutuTrader/Futu_connect.json @@ -0,0 +1,7 @@ +{ + "host": "127.0.0.1", + "port": 11111, + "market": "HK", + "password": "321321", + "env": "REAL" +} \ No newline at end of file diff --git a/examples/FutuTrader/RM_setting.json b/examples/FutuTrader/RM_setting.json new file mode 100644 index 00000000..c22ac5fe --- /dev/null +++ b/examples/FutuTrader/RM_setting.json @@ -0,0 +1,10 @@ +{ + "active": false, + "orderFlowLimit": 50, + "orderFlowClear": 1, + "orderSizeLimit": 100, + "tradeLimit": 1000, + "workingOrderLimit": 20, + "orderCancelLimit": 10, + "marginRatioLimit": 0.85 +} \ No newline at end of file diff --git a/examples/FutuTrader/VT_setting.json b/examples/FutuTrader/VT_setting.json new file mode 100644 index 00000000..473017fb --- /dev/null +++ b/examples/FutuTrader/VT_setting.json @@ -0,0 +1,20 @@ +{ + "fontFamily": "微软雅黑", + "fontSize": 12, + + "mongoHost": "localhost", + "mongoPort": 27017, + "mongoLogging": true, + + "darkStyle": true, + "language": "chinese", + + "logActive": true, + "logLevel": "debug", + "logConsole": true, + "logFile": true, + + "tdPenalty": ["IF", "IH", "IC"], + + "maxDecimal": 4 +} \ No newline at end of file diff --git a/examples/FutuTrader/run.py b/examples/FutuTrader/run.py new file mode 100644 index 00000000..05bb3146 --- /dev/null +++ b/examples/FutuTrader/run.py @@ -0,0 +1,46 @@ +# encoding: UTF-8 + +import sys + +# vn.trader模块 +from vnpy.event import EventEngine +from vnpy.trader.vtEngine import MainEngine +from vnpy.trader.uiQt import createQApp +from vnpy.trader.uiMainWindow import MainWindow + +# 加载底层接口 +from vnpy.trader.gateway import futuGateway + +# 加载上层应用 +from vnpy.trader.app import riskManager, ctaStrategy + + +#---------------------------------------------------------------------- +def main(): + """主程序入口""" + # 创建Qt应用对象 + qApp = createQApp() + + # 创建事件引擎 + ee = EventEngine() + + # 创建主引擎 + me = MainEngine(ee) + + # 添加交易接口 + me.addGateway(futuGateway) + + # 添加上层应用 + me.addApp(riskManager) + me.addApp(ctaStrategy) + + # 创建主窗口 + mw = MainWindow(me, ee) + mw.showMaximized() + + # 在主线程中启动Qt事件循环 + sys.exit(qApp.exec_()) + + +if __name__ == '__main__': + main() diff --git a/examples/FutuTrader/temp/ContractData.vt.dat b/examples/FutuTrader/temp/ContractData.vt.dat new file mode 100644 index 00000000..10634b68 Binary files /dev/null and b/examples/FutuTrader/temp/ContractData.vt.dat differ diff --git a/examples/FutuTrader/temp/ContractData.vt.dir b/examples/FutuTrader/temp/ContractData.vt.dir new file mode 100644 index 00000000..60f03216 --- /dev/null +++ b/examples/FutuTrader/temp/ContractData.vt.dir @@ -0,0 +1 @@ +'data', (0, 1462865) diff --git a/examples/README.md b/examples/README.md index 0739e3f2..f42ddc5a 100644 --- a/examples/README.md +++ b/examples/README.md @@ -2,12 +2,16 @@ 本文件夹中的内容主要是关于如何在交易业务中使用vn.py的示例 -* VnTrader:最常用的vn.py图形交易界面 +* CryptoTrader:vn.crypto数字货币交易平台 + +* VnTrader:最常用的vn.py图形交易系统 * OptionMaster: 期权量化交易系统 * WebTrader:使用Web前端作为监控的交易系统 +* FutuTrader:针对富途证券futuquant的交易系统(只支持Python 3) + * DataRecording:全自动行情记录工具(无需用户每日定时重启) * CtaTrading:无图形界面模式的CTA策略交易 @@ -24,4 +28,4 @@ * FutuDataService:富途证券历史行情服务(美股、港股) -* QuantosDataService: quantOS历史行情服务(A股、期货) \ No newline at end of file +* CoinapiDataService:CoinAPI.io历史行情服务(数字货币) \ No newline at end of file diff --git a/examples/VnTrader/CTP_connect.json b/examples/VnTrader/CTP_connect.json index 11beaff8..4ab4bb76 100644 --- a/examples/VnTrader/CTP_connect.json +++ b/examples/VnTrader/CTP_connect.json @@ -3,5 +3,5 @@ "mdAddress": "tcp://180.168.146.187:10031", "tdAddress": "tcp://180.168.146.187:10030", "userID": "000300", - "password": "19890624" + "password": "123456789" } \ No newline at end of file diff --git a/examples/VnTrader/OANDA_connect.json b/examples/VnTrader/OANDA_connect.json deleted file mode 100644 index 21dd2cd9..00000000 --- a/examples/VnTrader/OANDA_connect.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "token": "请在OANDA网站申请", - "accountId": "请在OANDA网站申请", - "settingName": "practice" -} \ No newline at end of file diff --git a/examples/VnTrader/run.py b/examples/VnTrader/run.py index f09170eb..b2975c27 100644 --- a/examples/VnTrader/run.py +++ b/examples/VnTrader/run.py @@ -5,9 +5,14 @@ try: reload # Python 2 except NameError: # Python 3 from importlib import reload + import sys reload(sys) -sys.setdefaultencoding('utf8') + +try: + sys.setdefaultencoding('utf8') +except AttributeError: + pass # 判断操作系统 import platform @@ -20,17 +25,17 @@ from vnpy.trader.uiQt import createQApp from vnpy.trader.uiMainWindow import MainWindow # 加载底层接口 -from vnpy.trader.gateway import (ctpGateway, oandaGateway, - ibGateway) +from vnpy.trader.gateway import (ctpGateway, ibGateway) if system == 'Linux': from vnpy.trader.gateway import xtpGateway elif system == 'Windows': from vnpy.trader.gateway import (femasGateway, xspeedGateway, - futuGateway, secGateway) + secGateway) # 加载上层应用 -from vnpy.trader.app import (riskManager, ctaStrategy, spreadTrading) +from vnpy.trader.app import (riskManager, ctaStrategy, + spreadTrading, algoTrading) #---------------------------------------------------------------------- @@ -47,14 +52,12 @@ def main(): # 添加交易接口 me.addGateway(ctpGateway) - me.addGateway(oandaGateway) me.addGateway(ibGateway) if system == 'Windows': me.addGateway(femasGateway) me.addGateway(xspeedGateway) me.addGateway(secGateway) - me.addGateway(futuGateway) if system == 'Linux': me.addGateway(xtpGateway) @@ -63,6 +66,7 @@ def main(): me.addApp(riskManager) me.addApp(ctaStrategy) me.addApp(spreadTrading) + me.addApp(algoTrading) # 创建主窗口 mw = MainWindow(me, ee) diff --git a/examples/WebTrader/README.md b/examples/WebTrader/README.md index 0563187c..2f5b55e7 100644 --- a/examples/WebTrader/README.md +++ b/examples/WebTrader/README.md @@ -6,17 +6,17 @@ 1. 修改CTP_connect.json中的账号和服务器地址 2. 修改WEB_setting.json中的网页登录用户名和密码 -3. 打开cmd,运行python server.py -4. 打开另一个cmd,运行python run.py -5. 用浏览器(推荐Chrome)访问http://localhost:5000/ -6. 输入2中的用户名和密码登录后,点击左下角的“连接CTP” -7. 网页前端的使用方法和常规版本的VnTrader基本一致 -8. 如需运行CTA策略,请修改CTA_setting.json中的配置 +3. 打开cmd,运行python run.py +4. 浏览器将会自动打开,并访问http://127.0.0.1:5000/ +5. 输入2中的用户名和密码登录后,点击左下角的“连接CTP” +6. 网页前端的使用方法和常规版本的VnTrader基本一致 +7. 如需运行CTA策略,请修改CTA_setting.json中的配置 ## 文件功能 -* server.py:基于vnpy.rpc模块实现的交易服务器,包含CTP接口和CTA策略模块 -* run.py:基于Flask实现的Web服务器,内部通过vnpy.rpc客户端来访问交易服务器 +* tradingServer.py:基于vnpy.rpc模块实现的交易服务器,包含CTP接口和CTA策略模块 +* webServer.py:基于Flask实现的Web服务器,内部通过vnpy.rpc客户端来访问交易服务器 +* run.py: 无人值守服务 ## 架构设计 diff --git a/examples/WebTrader/run.py b/examples/WebTrader/run.py index 742ca5c3..1bcaa0b9 100644 --- a/examples/WebTrader/run.py +++ b/examples/WebTrader/run.py @@ -9,6 +9,8 @@ from time import sleep from datetime import datetime, time from multiprocessing import Process +import webbrowser + from webServer import run as runWebServer from tradingServer import main as runTradingServer from vnpy.trader.vtEngine import LogEngine @@ -28,6 +30,9 @@ if __name__ == '__main__': pWeb = None pTrading = None + import os + print(os.getpid()) + while True: le.info('-'*30) @@ -48,6 +53,8 @@ if __name__ == '__main__': pWeb = Process(target=runWebServer) pWeb.start() le.info(u'启动WEB服务器进程') + + webbrowser.open('http://127.0.0.1:5000') else: le.info(u'当前处于非交易时间段') diff --git a/examples/WebTrader/webServer.py b/examples/WebTrader/webServer.py index 18efd35b..411c6cfd 100644 --- a/examples/WebTrader/webServer.py +++ b/examples/WebTrader/webServer.py @@ -50,8 +50,8 @@ with open("WEB_setting.json") as f: # 创建Flask对象 from flask import Flask, send_file -from flask.ext.restful import Api, Resource, reqparse -from flask.ext.socketio import SocketIO +from flask_restful import Api, Resource, reqparse +from flask_socketio import SocketIO from flask_cors import * app = Flask(__name__) @@ -510,6 +510,7 @@ class CtaStrategyStop(Resource): engine.stopStrategy(name) return {'result_code':'success','data':''} + ######################################################################## class CtaStrategyName(Resource): """»ñÈ¡²ßÂÔÃû""" @@ -533,6 +534,7 @@ class CtaStrategyName(Resource): l = engine.getStrategyNames() return {'result_code':'success','data':l} + ######################################################################## class CtaStrategyLoad(Resource): """加载策略""" @@ -672,7 +674,11 @@ ee.register(EVENT_CTA_STRATEGY, handleEvent) #---------------------------------------------------------------------- def run(): """启动Web服务""" - socketio.run(app,debug=True,host='0.0.0.0',port=5000) + socketio.run(app, + debug=True, + host='0.0.0.0', + port=5000, + use_reloader=False) if __name__ == '__main__': diff --git a/init.bat b/init.bat index f244dd63..162a0bc3 100644 --- a/init.bat +++ b/init.bat @@ -2,7 +2,7 @@ @"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin" ::װAnaconda -choco install anaconda2 --version 4.0.0 --x86 -y --ignorechecksum --params="'/AddToPath=1'" +choco install anaconda2 --version 5.2.0 --x86 -y --ignorechecksum --params="'/AddToPath=1'" setx PATH "%PATH%;C:\Program Files\Anaconda2\;C:\Program Files\Anaconda2\Scripts\" ::װVC Redist diff --git a/install.bat b/install.bat index f8af121f..564150f0 100644 --- a/install.bat +++ b/install.bat @@ -6,7 +6,5 @@ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/f conda config --set show_channel_urls yes conda install -c quantopian ta-lib=0.4.9 -y -::conda install -c conda-forge python-snappy -y - :: Install vn.py python setup.py install \ No newline at end of file diff --git a/install.sh b/install.sh index e4337eab..27fd9235 100755 --- a/install.sh +++ b/install.sh @@ -24,7 +24,6 @@ pip install -r requirements.txt conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ conda config --set show_channel_urls yes conda install -c quantopian ta-lib=0.4.9 -#conda install -c conda-forge python-snappy #Install vn.py python setup.py install diff --git a/requirements.txt b/requirements.txt index 97bdf730..2155d7da 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,4 +9,6 @@ future flask-socketio flask-restful flask-cors -gevent-websocket \ No newline at end of file +gevent-websocket +pyjwt +ccxt diff --git a/vnpy/__init__.py b/vnpy/__init__.py index 5477e7f3..708620bf 100644 --- a/vnpy/__init__.py +++ b/vnpy/__init__.py @@ -1,4 +1,4 @@ # encoding: UTF-8 -__version__ = '1.8.1' +__version__ = '1.9.0' __author__ = 'Xiaoyou Chen' \ No newline at end of file diff --git a/vnpy/api/README.md b/vnpy/api/README.md deleted file mode 100644 index 539dceee..00000000 --- a/vnpy/api/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# vn.api - API接口的Python封装 - -### 内盘 -* vn.ctp:CTP接口 -* vn.lts:华宝LTS接口 -* vn.femas:飞马接口 -* vn.xspeed:飞创接口 -* vn.qdp:量投QDP接口 -* vn.sgit:飞鼠接口 -* vn.ksotp:金仕达期权接口 -* vn.ksgold:金仕达黄金接口 - -### 外盘 -* vn.ib:Interactive Brokers接口 -* vn.oanda:OANDA接口 -* vn.shzd:直达期货接口 - -### 比特币 -* vn.okcoin:币行接口 -* vn.huobi:火币接口 -* vn.lhang:链行接口 - -### 数据下载 -* vn.datayes:通联数据接口 \ No newline at end of file diff --git a/vnpy/api/bigone/__init__.py b/vnpy/api/bigone/__init__.py new file mode 100644 index 00000000..7c63904a --- /dev/null +++ b/vnpy/api/bigone/__init__.py @@ -0,0 +1 @@ +from .vnbigone import BigoneRestApi \ No newline at end of file diff --git a/vnpy/api/bigone/vnbigone.py b/vnpy/api/bigone/vnbigone.py new file mode 100644 index 00000000..0e4bfd29 --- /dev/null +++ b/vnpy/api/bigone/vnbigone.py @@ -0,0 +1,154 @@ +# encoding: UTF-8 + +from __future__ import print_function +import hashlib +import hmac +import json +import ssl +import traceback +import base64 + +from queue import Queue, Empty +from multiprocessing.dummy import Pool +from time import time +from urlparse import urlparse +from copy import copy +from urllib import urlencode +from threading import Thread + +import requests +from jwt import PyJWS +from six.moves import input + +REST_HOST = 'https://big.one/api/v2/' + + + + +######################################################################## +class BigoneRestApi(object): + """REST API""" + jws = PyJWS() + + #---------------------------------------------------------------------- + def __init__(self): + """Constructor""" + self.apiKey = '' + self.apiSecret = '' + + self.active = False + self.reqid = 0 + self.queue = Queue() + self.pool = None + self.sessionDict = {} # 会话对象字典 + + #---------------------------------------------------------------------- + def init(self, apiKey, apiSecret): + """初始化""" + self.apiKey = str(apiKey) + self.apiSecret = str(apiSecret) + + #---------------------------------------------------------------------- + def start(self, n=10): + """启动""" + if self.active: + return + + self.active = True + self.pool = Pool(n) + self.pool.map_async(self.run, range(n)) + + #---------------------------------------------------------------------- + def close(self): + """关闭""" + self.active = False + + if self.pool: + self.pool.close() + self.pool.join() + + #---------------------------------------------------------------------- + def addReq(self, method, path, callback, params=None, postdict=None): + """添加请求""" + self.reqid += 1 + req = (method, path, callback, params, postdict, self.reqid) + self.queue.put(req) + return self.reqid + + #---------------------------------------------------------------------- + def processReq(self, req, i): + """处理请求""" + method, path, callback, params, postdict, reqid = req + url = REST_HOST + path + + header = {} + header['Authorization'] = 'Bearer ' + self.generateSignature() + + try: + # 使用长连接的session,比短连接的耗时缩短20% + session = self.sessionDict[i] + resp = session.request(method, url, headers=header, params=params, json=postdict) + #resp = requests.request(method, url, headers=header, params=params, data=postdict) + + code = resp.status_code + d = resp.json() + + if code == 200: + callback(d, reqid) + else: + self.onError(code, str(d)) + except Exception as e: + self.onError(type(e), e.message) + + #---------------------------------------------------------------------- + def run(self, i): + """连续运行""" + self.sessionDict[i] = requests.Session() + + while self.active: + try: + req = self.queue.get(timeout=1) + self.processReq(req, i) + except Empty: + pass + + #---------------------------------------------------------------------- + def generateSignature(self): + """生成签名""" + payload = '{"type":"OpenAPI","sub":"%s","nonce":%s}' %(self.apiKey, time()*1000000000) + signature = self.jws.encode(payload, self.apiSecret) + return signature + + #---------------------------------------------------------------------- + def onError(self, code, error): + """错误回调""" + print('on error') + print(code, error) + + #---------------------------------------------------------------------- + def onData(self, data, reqid): + """通用回调""" + print('on data') + print(data, reqid) + + + + +if __name__ == '__main__': + from datetime import datetime + from time import sleep + + API_KEY = '' + API_SECRET = '' + + # REST测试 + rest = BigoneRestApi() + rest.init(API_KEY, API_SECRET) + rest.start(1) + + #rest.addReq('GET', '/markets/EOS-BTC/depth', rest.onData) + + rest.addReq('GET', '/viewer/orders', rest.onData) + + + input() diff --git a/vnpy/api/binance/__init__.py b/vnpy/api/binance/__init__.py new file mode 100644 index 00000000..57a962fc --- /dev/null +++ b/vnpy/api/binance/__init__.py @@ -0,0 +1 @@ +from .vnbinance import BinanceApi \ No newline at end of file diff --git a/vnpy/api/binance/test.py b/vnpy/api/binance/test.py new file mode 100644 index 00000000..d755fd21 --- /dev/null +++ b/vnpy/api/binance/test.py @@ -0,0 +1,46 @@ +from __future__ import absolute_import +from time import sleep + +from six.moves import input + +from .vnbinance import BinanceApi + + +if __name__ == '__main__': + apiKey = '' + secretKey = '' + + api = BinanceApi() + api.init(apiKey, secretKey) + api.start() + + #api.queryPing() + #api.queryTime() + #api.queryExchangeInfo() + + api.queryDepth('BTCUSDT') + #api.queryDepth('BTCUSDT') + #api.queryTrades('BTCUSDT') + #api.queryAggTrades('BTCUSDT') + #api.queryKlines('BTCUSDT', '1m') + #api.queryTicker24HR() + #api.queryTickerPrice() + #api.queryBookTicker() + + api.queryAccount() + #api.queryOrder('BTCUSDT', '1231231') + #api.queryOpenOrders('BTCUSDT') + #api.queryAllOrders('BTCUSDT') + #api.queryMyTrades('BTCUSDT') + + #api.startStream() + #api.keepaliveStream('12312312') + #api.closeStream('123213') + + #api.newOrder('BTCUSDT', 'BUY', 'LIMIT', 3000, 1, 'GTC') + #api.cancelOrder('BTCUSDT', '132213123') + + #api.initDataStream(['btcusdt@ticker', 'btcusdt@depth5']) + #api.initUserStream('NXvaiFwZz2LuKqINVerKOnWaQQG1JhdQNejiZKY9Kmgk4lZgTvm3nRAnXJK7') + + input() diff --git a/vnpy/api/binance/vnbinance.py b/vnpy/api/binance/vnbinance.py new file mode 100644 index 00000000..618e428a --- /dev/null +++ b/vnpy/api/binance/vnbinance.py @@ -0,0 +1,630 @@ +# encoding: UTF-8 + +from __future__ import print_function +import json +import requests +import hmac +import hashlib +import traceback +import ssl + +from queue import Queue, Empty +from threading import Thread +from multiprocessing.dummy import Pool +from time import time, sleep +from urllib import urlencode + +from websocket import create_connection + + + +REST_ENDPOINT = 'https://www.binance.com' +DATASTREAM_ENDPOINT = 'wss://stream.binance.com:9443/stream?streams=' +USERSTREAM_ENDPOINT = 'wss://stream.binance.com:9443/ws/' + + + +######################################################################## +class BinanceApi(object): + """""" + + ################################################### + ## Basic Function + ################################################### + + #---------------------------------------------------------------------- + def __init__(self): + """Constructor""" + self.apiKey = '' + self.secretKey = '' + + self.active = False + self.reqid = 0 + self.queue = Queue() + self.pool = None + + self.headers = {} + self.secret = '' + self.recvWindow = 5000 + + self.dataStreamNameList = [] + self.dataStreamUrl = '' + self.dataStreamActive = False + self.dataStreamWs = None + self.dataStreamThread = None + + self.userStreamKey = '' + self.userStreamUrl = '' + self.userStreamActive = False + self.userStreamWs = None + self.userStreamThread = None + + self.keepaliveCount = 0 + self.keepaliveThread = None + + #---------------------------------------------------------------------- + def init(self, apiKey, secretKey, recvWindow=5000): + """""" + self.apiKey = apiKey + self.secretKey = secretKey + + self.headers['X-MBX-APIKEY'] = apiKey + self.secret = bytes(secretKey.encode('utf-8')) + self.recvWindow = recvWindow + + #---------------------------------------------------------------------- + def start(self, n=10): + """""" + if self.active: + return + + self.active = True + + self.pool = Pool(n) + self.pool.map_async(self.run, range(n)) + + #---------------------------------------------------------------------- + def close(self): + """""" + self.active = False + + if self.pool: + self.pool.close() + self.pool.join() + + #---------------------------------------------------------------------- + def request(self, method, path, params=None, signed=False, stream=False): + """""" + if not signed: + url = REST_ENDPOINT + path + headers = {} + else: + if not stream: + params['recvWindow'] = self.recvWindow + params['timestamp'] = int(time()*1000) + query = urlencode(sorted(params.items())) + + signature = hmac.new(self.secret, query.encode('utf-8'), + hashlib.sha256).hexdigest() + query += "&signature={}".format(signature) + + url = REST_ENDPOINT + path + '?' + query + params = None # 参数添加到query中后,清空参数字典 + else: + if params: + query = urlencode(sorted(params.items())) + url = REST_ENDPOINT + path + '?' + query + params = None + else: + url = REST_ENDPOINT + path + + headers = self.headers + + try: + resp = requests.request(method, url, params=params, headers=headers) + + if resp.status_code == 200: + return True, resp.json() + else: + error = { + 'method': method, + 'params': params, + 'code': resp.status_code, + 'msg': resp.json()['msg'] + } + return False, error + except Exception as e: + error = { + 'method': method, + 'params': params, + 'code': e, + 'msg': traceback.format_exc() + } + return False, error + + #---------------------------------------------------------------------- + def addReq(self, method, path, params, callback, signed=False, stream=False): + """添加请求""" + self.reqid += 1 + req = (method, path, params, callback, signed, stream, self.reqid) + self.queue.put(req) + return self.reqid + + #---------------------------------------------------------------------- + def processReq(self, req): + """""" + method, path, params, callback, signed, stream, reqid = req + result, data = self.request(method, path, params, signed, stream) + + if result: + callback(data, reqid) + else: + self.onError(data, reqid) + + #---------------------------------------------------------------------- + def run(self, n): + """""" + while self.active: + try: + req = self.queue.get(timeout=1) + self.processReq(req) + except Empty: + pass + + ################################################### + ## REST Function + ################################################### + + #---------------------------------------------------------------------- + def queryPing(self): + """""" + path = '/api/v1/ping' + return self.addReq('GET', path, {}, self.onQueryPing) + + #---------------------------------------------------------------------- + def queryTime(self): + """""" + path = '/api/v1/time' + return self.addReq('GET', path, {}, self.onQueryTime) + + #---------------------------------------------------------------------- + def queryExchangeInfo(self): + """""" + path = '/api/v1/exchangeInfo' + return self.addReq('GET', path, {}, self.onQueryExchangeInfo) + + #---------------------------------------------------------------------- + def queryDepth(self, symbol, limit=0): + """""" + path = '/api/v1/depth' + params = {'symbol': symbol} + if limit: + params['limit'] = limit + return self.addReq('GET', path, params, self.onQueryDepth) + + #---------------------------------------------------------------------- + def queryTrades(self, symbol, limit=0): + """""" + path = '/api/v1/trades' + params = {'symbol': symbol} + if limit: + params['limit'] = limit + return self.addReq('GET', path, params, self.onQueryTrades) + + #---------------------------------------------------------------------- + def queryAggTrades(self, symbol, fromId=0, startTime=0, endTime=0, limit=0): + """""" + path = '/api/v1/aggTrades' + + params = {'symbol': symbol} + if fromId: + params['fromId'] = fromId + if startTime: + params['startTime'] = startTime + if endTime: + params['endTime'] = endTime + if limit: + params['limit'] = limit + + return self.addReq('GET', path, params, self.onQueryAggTrades) + + #---------------------------------------------------------------------- + def queryKlines(self, symbol, interval, limit=0, startTime=0, endTime=0): + """""" + path = '/api/v1/klines' + + params = { + 'symbol': symbol, + 'interval': interval + } + if limit: + params['limit'] = limit + if startTime: + params['startTime'] = startTime + if endTime: + params['endTime'] = endTime + + return self.addReq('GET', path, params, self.onQueryKlines) + + #---------------------------------------------------------------------- + def queryTicker24HR(self, symbol=''): + """""" + path = '/api/v1/ticker/24hr' + params = {} + if symbol: + params['symbol'] = symbol + return self.addReq('GET', path, params, self.onQueryTicker24HR) + + #---------------------------------------------------------------------- + def queryTickerPrice(self, symbol=''): + """""" + path = '/api/v3/ticker/price' + params = {} + if symbol: + params['symbol'] = symbol + return self.addReq('GET', path, params, self.onQueryTickerPrice) + + #---------------------------------------------------------------------- + def queryBookTicker(self, symbol=''): + """""" + path = '/api/v3/ticker/bookTicker' + params = {} + if symbol: + params['symbol'] = symbol + return self.addReq('GET', path, params, self.onQueryBookTicker) + + #---------------------------------------------------------------------- + def newOrder(self, symbol, side, type_, price, quantity, timeInForce, + newClientOrderId='', stopPrice=0, icebergQty=0, newOrderRespType=''): + """""" + path = '/api/v3/order' + + params = { + 'symbol': symbol, + 'side': side, + 'type': type_, + 'price': price, + 'quantity': quantity, + 'timeInForce': timeInForce + } + if newClientOrderId: + params['newClientOrderId'] = newClientOrderId + if timeInForce: + params['timeInForce'] = timeInForce + if stopPrice: + params['stopPrice'] = stopPrice + if icebergQty: + params['icebergQty'] = icebergQty + if newOrderRespType: + params['newOrderRespType'] = newOrderRespType + + return self.addReq('POST', path, params, self.onNewOrder, signed=True) + + #---------------------------------------------------------------------- + def queryOrder(self, symbol, orderId=0, origClientOrderId=0): + """""" + path = '/api/v3/order' + params = {'symbol': symbol} + if orderId: + params['orderId'] = orderId + if origClientOrderId: + params['origClientOrderId'] = origClientOrderId + return self.addReq('GET', path, params, self.onQueryOrder, signed=True) + + #---------------------------------------------------------------------- + def cancelOrder(self, symbol, orderId=0, origClientOrderId='', + newClientOrderId=''): + """""" + path = '/api/v3/order' + params = {'symbol': symbol} + if orderId: + params['orderId'] = orderId + if origClientOrderId: + params['origClientOrderId'] = origClientOrderId + if newClientOrderId: + params['newClientOrderId'] = newClientOrderId + return self.addReq('DELETE', path, params, self.onCancelOrder, signed=True) + + #---------------------------------------------------------------------- + def queryOpenOrders(self, symbol=''): + """""" + path = '/api/v3/openOrders' + params = {} + if symbol: + params['symbol'] = symbol + return self.addReq('GET', path, params, self.onQueryOpenOrders, signed=True) + + #---------------------------------------------------------------------- + def queryAllOrders(self, symbol, orderId=0, limit=0): + """""" + path = '/api/v3/allOrders' + params = {'symbol': symbol} + if orderId: + params['orderId'] = orderId + if limit: + params['limit'] = limit + return self.addReq('GET', path, params, self.onQueryAllOrders, signed=True) + + #---------------------------------------------------------------------- + def queryAccount(self): + """""" + path = '/api/v3/account' + params = {} + return self.addReq('GET', path, params, self.onQueryAccount, signed=True) + + #---------------------------------------------------------------------- + def queryMyTrades(self, symbol, limit=0, fromId=0): + """""" + path = '/api/v3/myTrades' + params = {'symbol': symbol} + if limit: + params['limit'] = limit + if fromId: + params['fromId'] = fromId + return self.addReq('GET', path, params, self.onQueryMyTrades, signed=True) + + #---------------------------------------------------------------------- + def startStream(self): + """""" + path = '/api/v1/userDataStream' + return self.addReq('POST', path, {}, self.onStartStream, signed=True, stream=True) + + #---------------------------------------------------------------------- + def keepaliveStream(self, listenKey): + """""" + path = '/api/v1/userDataStream' + params = {'listenKey': listenKey} + return self.addReq('PUT', path, params, self.onKeepaliveStream, signed=True, stream=True) + + #---------------------------------------------------------------------- + def closeStream(self, listenKey): + """""" + path = '/api/v1/userDataStream' + params = {'listenKey': listenKey} + return self.addReq('DELETE', path, params, self.onCloseStream, signed=True, stream=True) + + ################################################### + ## REST Callback + ################################################### + + #---------------------------------------------------------------------- + def onError(self, data, reqid): + """""" + print((data, reqid)) + + #---------------------------------------------------------------------- + def onQueryPing(self, data, reqid): + """""" + print((data, reqid)) + + #---------------------------------------------------------------------- + def onQueryTime(self, data, reqid): + """""" + print((data, reqid)) + + #---------------------------------------------------------------------- + def onQueryExchangeInfo(self, data, reqid): + """""" + print((data, reqid)) + + #---------------------------------------------------------------------- + def onQueryDepth(self, data, reqid): + """""" + print((data, reqid)) + + #---------------------------------------------------------------------- + def onQueryTrades(self, data, reqid): + """""" + print((data, reqid)) + + #---------------------------------------------------------------------- + def onQueryAggTrades(self, data, reqid): + """""" + print((data, reqid)) + + #---------------------------------------------------------------------- + def onQueryKlines(self, data, reqid): + """""" + print((data, reqid)) + + #---------------------------------------------------------------------- + def onQueryTicker24HR(self, data, reqid): + """""" + print((data, reqid)) + + #---------------------------------------------------------------------- + def onQueryTickerPrice(self, data, reqid): + """""" + print((data, reqid)) + + #---------------------------------------------------------------------- + def onQueryBookTicker(self, data, reqid): + """""" + print((data, reqid)) + + #---------------------------------------------------------------------- + def onNewOrder(self, data, reqid): + """""" + print((data, reqid)) + + #---------------------------------------------------------------------- + def onQueryOrder(self, data, reqid): + """""" + print((data, reqid)) + + #---------------------------------------------------------------------- + def onCancelOrder(self, data, reqid): + """""" + print((data, reqid)) + + #---------------------------------------------------------------------- + def onQueryOpenOrders(self, data, reqid): + """""" + print((data, reqid)) + + #---------------------------------------------------------------------- + def onQueryAllOrders(self, data, reqid): + """""" + print((data, reqid)) + + #---------------------------------------------------------------------- + def onQueryAccount(self, data, reqid): + """""" + print((data, reqid)) + + #---------------------------------------------------------------------- + def onQueryMyTrades(self, data, reqid): + """""" + print((data, reqid)) + + #---------------------------------------------------------------------- + def onStartStream(self, data, reqid): + """""" + print((data, reqid)) + + #---------------------------------------------------------------------- + def onKeepaliveStream(self, data, reqid): + """""" + print((data, reqid)) + + #---------------------------------------------------------------------- + def onCloseStream(self, data, reqid): + """""" + print((data, reqid)) + + ################################################### + ## Websocket Function + ################################################### + + #---------------------------------------------------------------------- + def initDataStream(self, nameList=None): + """""" + if nameList: + self.dataStreamNameList = nameList + s = '/'.join(self.dataStreamNameList) + self.dataStreamUrl = DATASTREAM_ENDPOINT + s + + result = self.connectDataStream() + if result: + self.dataStreamActive = True + self.dataStreamThread = Thread(target=self.runDataStream) + self.dataStreamThread.start() + + #---------------------------------------------------------------------- + def runDataStream(self): + """""" + while self.dataStreamActive: + try: + stream = self.dataStreamWs.recv() + data = json.loads(stream) + self.onMarketData(data) + except: + self.onDataStreamError('Data stream connection lost') + result = self.connectDataStream() + if not result: + self.onDataStreamError(u'Waiting 3 seconds to reconnect') + sleep(3) + else: + self.onDataStreamError(u'Data stream reconnected') + + #---------------------------------------------------------------------- + def closeDataStream(self): + """""" + self.dataStreamActive = False + self.dataStreamThread.join() + + #---------------------------------------------------------------------- + def connectDataStream(self): + """""" + try: + self.dataStreamWs = create_connection(self.dataStreamUrl, + sslopt={'cert_reqs': ssl.CERT_NONE}) + return True + except: + msg = traceback.format_exc() + self.onDataStreamError('Connecting data stream falied: %s' %msg) + return False + + #---------------------------------------------------------------------- + def onDataStreamError(self, msg): + """""" + print(msg) + + #---------------------------------------------------------------------- + def onMarketData(self, data): + """""" + print(data) + + #---------------------------------------------------------------------- + def initUserStream(self, key): + """""" + self.userStreamKey = key + self.userStreamUrl = USERSTREAM_ENDPOINT + key + + result = self.connectUserStream() + if result: + self.userStreamActive = True + self.userStreamThread = Thread(target=self.runUserStream) + self.userStreamThread.start() + + self.keepaliveThread = Thread(target=self.runKeepalive) + self.keepaliveThread.start() + + #---------------------------------------------------------------------- + def runUserStream(self): + """""" + while self.userStreamActive: + try: + stream = self.userStreamWs.recv() + data = json.loads(stream) + self.onUserData(data) + except: + self.onUserStreamError('User stream connection lost') + result = self.connectUserStream() + if not result: + self.onUserStreamError(u'Waiting 3 seconds to reconnect') + sleep(3) + else: + self.onUserStreamError(u'User stream reconnected') + + #---------------------------------------------------------------------- + def closeUserStream(self): + """""" + self.userStreamActive = False + self.userStreamThread.join() + self.keepaliveThread.join() + + #---------------------------------------------------------------------- + def connectUserStream(self): + """""" + try: + self.userStreamWs = create_connection(self.userStreamUrl, + sslopt={'cert_reqs': ssl.CERT_NONE}) + return True + except: + msg = traceback.format_exc() + self.onUserStreamError('Connecting user stream falied: %s' %msg) + return False + + #---------------------------------------------------------------------- + def onUserStreamError(self, msg): + """""" + print(msg) + + #---------------------------------------------------------------------- + def onUserData(self, data): + """""" + print(data) + + #---------------------------------------------------------------------- + def runKeepalive(self): + """""" + while self.userStreamActive: + self.keepaliveCount += 1 + + if self.keepaliveCount >= 1800: + self.keepaliveCount = 0 + self.keepaliveStream(self.userStreamKey) + + sleep(1) + \ No newline at end of file diff --git a/vnpy/api/bitfinex/__init__.py b/vnpy/api/bitfinex/__init__.py new file mode 100644 index 00000000..15180a7a --- /dev/null +++ b/vnpy/api/bitfinex/__init__.py @@ -0,0 +1 @@ +from .vnbitfinex import BitfinexApi \ No newline at end of file diff --git a/vnpy/api/bitfinex/vnbitfinex.py b/vnpy/api/bitfinex/vnbitfinex.py new file mode 100644 index 00000000..0132c000 --- /dev/null +++ b/vnpy/api/bitfinex/vnbitfinex.py @@ -0,0 +1,136 @@ +# encoding: UTF-8 + +from __future__ import print_function +import json +import requests +import traceback +import ssl +from threading import Thread +from queue import Queue, Empty + +import websocket + +from six.moves import input + + +WEBSOCKET_V2_URL = 'wss://api.bitfinex.com/ws/2' +RESTFUL_V1_URL = 'https://api.bitfinex.com/v1' + + +######################################################################## +class BitfinexApi(object): + """""" + + #---------------------------------------------------------------------- + def __init__(self): + """Constructor""" + self.ws = None + self.thread = None + self.active = False + + self.restQueue = Queue() + self.restThread = None + + #---------------------------------------------------------------------- + def start(self): + """""" + self.ws = websocket.create_connection(WEBSOCKET_V2_URL, + sslopt={'cert_reqs': ssl.CERT_NONE}) + + self.active = True + self.thread = Thread(target=self.run) + self.thread.start() + + self.restThread = Thread(target=self.runRest) + self.restThread.start() + + self.onConnect() + + #---------------------------------------------------------------------- + def reconnect(self): + """""" + self.ws = websocket.create_connection(WEBSOCKET_V2_URL, + sslopt={'cert_reqs': ssl.CERT_NONE}) + + self.onConnect() + + #---------------------------------------------------------------------- + def run(self): + """""" + while self.active: + try: + stream = self.ws.recv() + data = json.loads(stream) + self.onData(data) + except: + msg = traceback.format_exc() + self.onError(msg) + self.reconnect() + + #---------------------------------------------------------------------- + def close(self): + """""" + self.active = False + + if self.thread: + self.thread.join() + + if self.restThread: + self.thread.join() + + #---------------------------------------------------------------------- + def onConnect(self): + """""" + print('connected') + + #---------------------------------------------------------------------- + def onData(self, data): + """""" + print(data) + + #---------------------------------------------------------------------- + def onError(self, msg): + """""" + print(msg) + + #---------------------------------------------------------------------- + def sendReq(self, req): + """""" + self.ws.send(json.dumps(req)) + + #---------------------------------------------------------------------- + def sendRestReq(self, path, callback): + """""" + self.restQueue.put((path, callback)) + + #---------------------------------------------------------------------- + def runRest(self): + """""" + while self.active: + try: + path, callback = self.restQueue.get(timeout=1) + self.httpGet(path, callback) + except Empty: + pass + + #---------------------------------------------------------------------- + def httpGet(self, path, callback): + """""" + url = RESTFUL_V1_URL + path + resp = requests.get(url) + callback(resp.json()) + + + +if __name__ == '__main__': + api = BitfinexApi() + api.start() + + d = { + 'event': 'subscribe', + 'channel': 'book', + 'symbol': 'BTCUSD' + } + api.sendReq(d) + + input() diff --git a/vnpy/api/bithumb/__init__.py b/vnpy/api/bithumb/__init__.py new file mode 100644 index 00000000..c45d2fc5 --- /dev/null +++ b/vnpy/api/bithumb/__init__.py @@ -0,0 +1 @@ +from .vnbithumb import BithumbRestApi \ No newline at end of file diff --git a/vnpy/api/bithumb/vnbithumb.py b/vnpy/api/bithumb/vnbithumb.py new file mode 100644 index 00000000..981a2f51 --- /dev/null +++ b/vnpy/api/bithumb/vnbithumb.py @@ -0,0 +1,166 @@ +# encoding: UTF-8 +import base64 +import hashlib +import hmac +import urllib +from multiprocessing.dummy import Pool +from time import time + +import requests +from six.moves import input +from queue import Queue, Empty + +REST_HOST = 'https://api.bithumb.com' + + +######################################################################## +class BithumbRestApi(object): + + #---------------------------------------------------------------------- + def __init__(self): + """Constructor""" + self.apiKey = '' + self.apiSecret = '' + + self.active = False + self.reqid = 0 + self.queue = Queue() + self.pool = None # type: Pool + self.sessionDict = {} + + #---------------------------------------------------------------------- + def init(self, apiKey, apiSecret): + """初始化""" + self.apiKey = str(apiKey) + self.apiSecret = str(apiSecret) + + #---------------------------------------------------------------------- + def start(self, n=10): + """启动""" + if self.active: + return + + self.active = True + self.pool = Pool(n) + self.pool.map_async(self.run, range(n)) + + #---------------------------------------------------------------------- + def close(self): + """关闭""" + self.active = False + + if self.pool: + self.pool.close() + self.pool.join() + + #---------------------------------------------------------------------- + def addReq(self, method, path, callback, params=None, postdict=None): + """添加请求""" + self.reqid += 1 + req = (method, path, callback, params, postdict, self.reqid) + self.queue.put(req) + return self.reqid + + def processReq(self, req, i): + """处理请求""" + method, path, callback, params, postdict, reqid = req + url = REST_HOST + path + + body = '' + header = {} + + # 如果调用的是需要签名的API,则加上签名 + # 不是以/public/开头的API都需要签名 + if path[:8] != '/public/': + nonce = BithumbRestApi.generateNonce() + + # 如果有参数,使用urlencode编码参数 + body = urllib.urlencode(postdict) if postdict else '' + + # 加上签名 + header = { + 'Api-Key': self.apiKey, + 'Api-Sign': self.generateSignature(path, body, nonce), + 'Api-Nonce': nonce, + 'Content-Type': 'application/x-www-form-urlencoded' + } + + try: + # 使用长连接的session,比短连接的耗时缩短20% + session = self.sessionDict[i] + resp = session.request(method, url, headers=header, params=params, data=body) + + code = resp.status_code + d = resp.json() + + if code == 200: + callback(d, reqid) + else: + self.onError(code, str(d)) + except Exception as e: + self.onError(type(e), e.message) + + #---------------------------------------------------------------------- + def run(self, i): + """连续运行""" + self.sessionDict[i] = requests.Session() + + while self.active: + try: + req = self.queue.get(timeout=1) + self.processReq(req, i) + except Empty: + pass + + #---------------------------------------------------------------------- + def generateSignature(self, path, body, nonce): + """生成签名""" + # 要签名的数据包括path,body和nonce,用\x00隔开 + data = path + chr(0) + body + chr(0) + nonce + + # 签名的核心方法:hmac-sha512 + # 签名流程:base64(hex(hmac-sha512(要签名的数据))) + return base64.b64encode(hmac.new(bytes(self.apiSecret), data, hashlib.sha512).hexdigest()) + + #---------------------------------------------------------------------- + def onError(self, code, error): + """错误回调""" + print('on error') + print(code, error) + + #---------------------------------------------------------------------- + def onData(self, data, reqid): + """通用回调""" + print('on data') + print(data, reqid) + + #---------------------------------------------------------------------- + @staticmethod # 静态函数:不依赖于self的函数 + def generateNonce(): + """生成时间戳""" + return str(int(time() * 1000)) + + +if __name__ == '__main__': + API_KEY = '0c2f5621ac18d26d51ce640b25eb44f9' + API_SECRET = '62bb8b4e263476f443f8d3dbf0aad6bc' + + rest = BithumbRestApi() + rest.init(apiKey=API_KEY, apiSecret=API_SECRET) + rest.start(1) + + + def onBtcTick(jsonObj, reqid): + print('on_btc_tick : \n{}'.format(jsonObj)) + pass + + + def onAccountInfo(jsonObj, reqid): + print('on_account_info : \n{}'.format(jsonObj)) + pass + + + rest.addReq('POST', '/public/ticker/BTC', onBtcTick) + rest.addReq('POST', '/info/account', onAccountInfo, postdict={'currency': 'BTC'}) + + input() diff --git a/vnpy/api/bitmex/__init__.py b/vnpy/api/bitmex/__init__.py new file mode 100644 index 00000000..857efbac --- /dev/null +++ b/vnpy/api/bitmex/__init__.py @@ -0,0 +1 @@ +from .vnbitmex import BitmexRestApi, BitmexWebsocketApi \ No newline at end of file diff --git a/vnpy/api/bitmex/vnbitmex.py b/vnpy/api/bitmex/vnbitmex.py new file mode 100644 index 00000000..962b0c33 --- /dev/null +++ b/vnpy/api/bitmex/vnbitmex.py @@ -0,0 +1,273 @@ +# encoding: UTF-8 + +from __future__ import print_function +import hashlib +import hmac +import json +import ssl +import traceback + +from queue import Queue, Empty +from multiprocessing.dummy import Pool +from time import time +from urlparse import urlparse +from copy import copy +from urllib import urlencode +from threading import Thread + +from six.moves import input + +import requests +import websocket + + +REST_HOST = 'https://www.bitmex.com/api/v1' +WEBSOCKET_HOST = 'wss://www.bitmex.com/realtime' + + + + +######################################################################## +class BitmexRestApi(object): + """REST API""" + + #---------------------------------------------------------------------- + def __init__(self): + """Constructor""" + self.apiKey = '' + self.apiSecret = '' + + self.active = False + self.reqid = 0 + self.queue = Queue() + self.pool = None + self.sessionDict = {} # 会话对象字典 + + self.header = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Accept': 'application/json' + } + + #---------------------------------------------------------------------- + def init(self, apiKey, apiSecret): + """初始化""" + self.apiKey = apiKey + self.apiSecret = apiSecret + + #---------------------------------------------------------------------- + def start(self, n=3): + """启动""" + if self.active: + return + + self.active = True + self.pool = Pool(n) + self.pool.map_async(self.run, range(n)) + + #---------------------------------------------------------------------- + def close(self): + """关闭""" + self.active = False + + if self.pool: + self.pool.close() + self.pool.join() + + #---------------------------------------------------------------------- + def addReq(self, method, path, callback, params=None, postdict=None): + """添加请求""" + self.reqid += 1 + req = (method, path, callback, params, postdict, self.reqid) + self.queue.put(req) + return self.reqid + + #---------------------------------------------------------------------- + def processReq(self, req, i): + """处理请求""" + method, path, callback, params, postdict, reqid = req + url = REST_HOST + path + expires = int(time() + 5) + + rq = requests.Request(url=url, data=postdict) + p = rq.prepare() + + header = copy(self.header) + header['api-expires'] = str(expires) + header['api-key'] = self.apiKey + header['api-signature'] = self.generateSignature(method, path, expires, params, body=p.body) + + # 使用长连接的session,比短连接的耗时缩短80% + session = self.sessionDict[i] + resp = session.request(method, url, headers=header, params=params, data=postdict) + + #resp = requests.request(method, url, headers=header, params=params, data=postdict) + + code = resp.status_code + d = resp.json() + + if code == 200: + callback(d, reqid) + else: + self.onError(code, d) + + #---------------------------------------------------------------------- + def run(self, i): + """连续运行""" + self.sessionDict[i] = requests.Session() + + while self.active: + try: + req = self.queue.get(timeout=1) + self.processReq(req, i) + except Empty: + pass + + #---------------------------------------------------------------------- + def generateSignature(self, method, path, expires, params=None, body=None): + """生成签名""" + # 对params在HTTP报文路径中,以请求字段方式序列化 + if params: + query = urlencode(sorted(params.items())) + path = path + '?' + query + + if body is None: + body = '' + + msg = method + '/api/v1' + path + str(expires) + body + signature = hmac.new(self.apiSecret, msg, + digestmod=hashlib.sha256).hexdigest() + return signature + + #---------------------------------------------------------------------- + def onError(self, code, error): + """错误回调""" + print('on error') + print(code, error) + + #---------------------------------------------------------------------- + def onData(self, data, reqid): + """通用回调""" + print('on data') + print(data, reqid) + + +######################################################################## +class BitmexWebsocketApi(object): + """Websocket API""" + + #---------------------------------------------------------------------- + def __init__(self): + """Constructor""" + self.ws = None + self.thread = None + self.active = False + + #---------------------------------------------------------------------- + def start(self): + """启动""" + self.ws = websocket.create_connection(WEBSOCKET_HOST, + sslopt={'cert_reqs': ssl.CERT_NONE}) + + self.active = True + self.thread = Thread(target=self.run) + self.thread.start() + + self.onConnect() + + #---------------------------------------------------------------------- + def reconnect(self): + """重连""" + self.ws = websocket.create_connection(WEBSOCKET_HOST, + sslopt={'cert_reqs': ssl.CERT_NONE}) + + self.onConnect() + + #---------------------------------------------------------------------- + def run(self): + """运行""" + while self.active: + try: + stream = self.ws.recv() + data = json.loads(stream) + self.onData(data) + except: + msg = traceback.format_exc() + self.onError(msg) + self.reconnect() + + #---------------------------------------------------------------------- + def close(self): + """关闭""" + self.active = False + + if self.thread: + self.thread.join() + + #---------------------------------------------------------------------- + def onConnect(self): + """连接回调""" + print('connected') + + #---------------------------------------------------------------------- + def onData(self, data): + """数据回调""" + print('-' * 30) + l = data.keys() + l.sort() + for k in l: + print(k, data[k]) + + #---------------------------------------------------------------------- + def onError(self, msg): + """错误回调""" + print(msg) + + #---------------------------------------------------------------------- + def sendReq(self, req): + """发出请求""" + self.ws.send(json.dumps(req)) + + + + + +if __name__ == '__main__': + API_KEY = '' + API_SECRET = '' + + ## REST测试 + rest = BitmexRestApi() + rest.init(API_KEY, API_SECRET) + rest.start(3) + + data = { + 'symbol': 'XBTUSD' + } + rest.addReq('POST', '/position/isolate', rest.onData, postdict=data) + #rest.addReq('GET', '/instrument', rest.onData) + + # WEBSOCKET测试 + #ws = BitmexWebsocketApi() + #ws.start() + + #req = {"op": "subscribe", "args": ['order', 'trade', 'position', 'margin']} + #ws.sendReq(req) + + #expires = int(time()) + #method = 'GET' + #path = '/realtime' + #msg = method + path + str(expires) + #signature = hmac.new(API_SECRET, msg, digestmod=hashlib.sha256).hexdigest() + + #req = { + #'op': 'authKey', + #'args': [API_KEY, expires, signature] + #} + + #ws.sendReq(req) + + #req = {"op": "subscribe", "args": ['order', 'execution', 'position', 'margin']} + #req = {"op": "subscribe", "args": ['instrument']} + #ws.sendReq(req) + + input() diff --git a/vnpy/api/coinbase/__init__.py b/vnpy/api/coinbase/__init__.py new file mode 100644 index 00000000..282a1dc0 --- /dev/null +++ b/vnpy/api/coinbase/__init__.py @@ -0,0 +1 @@ +from .vncoinbase import CoinbaseRestApi, CoinbaseWebsocketApi \ No newline at end of file diff --git a/vnpy/api/coinbase/vncoinbase.py b/vnpy/api/coinbase/vncoinbase.py new file mode 100644 index 00000000..04b3b4d2 --- /dev/null +++ b/vnpy/api/coinbase/vncoinbase.py @@ -0,0 +1,294 @@ +# encoding: UTF-8 + +from __future__ import print_function + +import hashlib +import hmac +import base64 +import json +import ssl +import traceback + +from queue import Queue, Empty +from multiprocessing.dummy import Pool +from time import time +from urlparse import urlparse +from copy import copy +from urllib import urlencode +from threading import Thread + +from six.moves import input + +import requests +import websocket + + +REST_HOST = 'https://api-public.sandbox.pro.coinbase.com' +WEBSOCKET_HOST = 'wss://ws-feed-public.sandbox.pro.coinbase.com' + + + +######################################################################## +class CoinbaseRestApi(object): + """REST API""" + + #---------------------------------------------------------------------- + def __init__(self): + """Constructor""" + self.apiKey = '' + self.secretKey = '' + self.passphrase = '' + self.hmacKey = '' + + self.active = False + self.reqid = 0 + self.queue = Queue() + self.pool = None + self.sessionDict = {} # 会话对象字典 + + self.header = { + 'Content-Type': 'Application/JSON' + } + + #---------------------------------------------------------------------- + def init(self, apiKey, secretKey, passphrase): + """初始化""" + self.apiKey = apiKey + self.secretKey = secretKey + self.passphrase = passphrase + + self.hmacKey = base64.b64decode(self.secretKey) + + #---------------------------------------------------------------------- + def start(self, n=10): + """启动""" + if self.active: + return + + self.active = True + self.pool = Pool(n) + self.pool.map_async(self.run, range(n)) + + #---------------------------------------------------------------------- + def close(self): + """关闭""" + self.active = False + + if self.pool: + self.pool.close() + self.pool.join() + + #---------------------------------------------------------------------- + def addReq(self, method, path, callback, params=None, postdict=None): + """添加请求""" + self.reqid += 1 + req = (method, path, callback, params, postdict, self.reqid) + self.queue.put(req) + return self.reqid + + #---------------------------------------------------------------------- + def processReq(self, req, i): + """处理请求""" + method, path, callback, params, postdict, reqid = req + url = REST_HOST + path + timestamp = str(time()) + + if postdict: + rq = requests.Request(url=url, data=json.dumps(postdict)) + else: + rq = requests.Request(url=url) + p = rq.prepare() + + header = copy(self.header) + header['CB-ACCESS-KEY'] = self.apiKey + header['CB-ACCESS-PASSPHRASE'] = self.passphrase + header['CB-ACCESS-TIMESTAMP'] = timestamp + header['CB-ACCESS-SIGN'] = self.generateSignature(method, path, timestamp, params, body=p.body) + + # 使用长连接的session,比短连接的耗时缩短80% + session = self.sessionDict[i] + if postdict: + resp = session.request(method, url, headers=header, params=params, data=json.dumps(postdict)) + else: + resp = session.request(method, url, headers=header, params=params) + + #resp = requests.request(method, url, headers=header, params=params, data=postdict) + + code = resp.status_code + d = resp.json() + + if code == 200: + callback(d, reqid) + else: + self.onError(code, d) + + #---------------------------------------------------------------------- + def run(self, i): + """连续运行""" + self.sessionDict[i] = requests.Session() + + while self.active: + try: + req = self.queue.get(timeout=1) + self.processReq(req, i) + except Empty: + pass + + #---------------------------------------------------------------------- + def generateSignature(self, method, path, timestamp, params=None, body=None): + """生成签名""" + # 对params在HTTP报文路径中,以请求字段方式序列化 + if params: + query = urlencode(sorted(params.items())) + path = path + '?' + query + + if body is None: + body = '' + + msg = timestamp + method + path + body + msg = msg.encode('ascii') + signature = hmac.new(self.hmacKey, msg, hashlib.sha256) + signature64 = base64.b64encode(signature.digest()).decode('utf-8') + return signature64 + + #---------------------------------------------------------------------- + def onError(self, code, error): + """错误回调""" + print('on error') + print(code, error) + + #---------------------------------------------------------------------- + def onData(self, data, reqid): + """通用回调""" + print('on data') + print(data, reqid) + + +######################################################################## +class CoinbaseWebsocketApi(object): + """Websocket API""" + + #---------------------------------------------------------------------- + def __init__(self): + """Constructor""" + self.ws = None + self.thread = None + self.active = False + + #---------------------------------------------------------------------- + def start(self): + """启动""" + self.ws = websocket.create_connection(WEBSOCKET_HOST, + sslopt={'cert_reqs': ssl.CERT_NONE}) + + self.active = True + self.thread = Thread(target=self.run) + self.thread.start() + + self.onConnect() + + #---------------------------------------------------------------------- + def reconnect(self): + """重连""" + self.ws = websocket.create_connection(WEBSOCKET_HOST, + sslopt={'cert_reqs': ssl.CERT_NONE}) + + self.onConnect() + + #---------------------------------------------------------------------- + def run(self): + """运行""" + while self.active: + try: + stream = self.ws.recv() + data = json.loads(stream) + self.onData(data) + except: + msg = traceback.format_exc() + self.onError(msg) + self.reconnect() + + #---------------------------------------------------------------------- + def close(self): + """关闭""" + self.active = False + + if self.thread: + self.thread.join() + + #---------------------------------------------------------------------- + def onConnect(self): + """连接回调""" + print('connected') + + #---------------------------------------------------------------------- + def onData(self, data): + """数据回调""" + print('-' * 30) + l = data.keys() + l.sort() + for k in l: + print(k, data[k]) + + #---------------------------------------------------------------------- + def onError(self, msg): + """错误回调""" + print(msg) + + #---------------------------------------------------------------------- + def sendReq(self, req): + """发出请求""" + self.ws.send(json.dumps(req)) + + + + + +if __name__ == '__main__': + API_KEY = '2982e190ce2785b862c36f7748ec6864' + API_SECRET = 'sUXjm5HZKA+Dru9+dtekGF6DlfQnHvbQCs+DaTuOTSBFR+vvMIiWkpPTwHcfZwNapSRpFhjNerrb111hojazIA==' + PASSPHRASE = 'vnpytesting' + + # REST测试 + rest = CoinbaseRestApi() + rest.init(API_KEY, API_SECRET, PASSPHRASE) + rest.start(1) + + #data = { + #'symbol': 'XBTUSD' + #} + #rest.addReq('POST', '/position/isolate', rest.onData, postdict=data) + + rest.addReq('GET', '/orders', rest.onData, {'status': 'all'}) + + ## WEBSOCKET测试 + #ws = CoinbaseWebsocketApi() + #ws.start() + + #req = { + #'type': 'subscribe', + #"product_ids": [ + #"ETH-USD" + #], + #"channels": ['level2'] + #} + #ws.sendReq(req) + + #expires = int(time()) + #method = 'GET' + #path = '/realtime' + #msg = method + path + str(expires) + #signature = hmac.new(API_SECRET, msg, digestmod=hashlib.sha256).hexdigest() + + #req = { + #'op': 'authKey', + #'args': [API_KEY, expires, signature] + #} + + #ws.sendReq(req) + + #req = {"op": "subscribe", "args": ['order', 'execution', 'position', 'margin']} + #req = {"op": "subscribe", "args": ['instrument']} + #ws.sendReq(req) + + input() diff --git a/vnpy/api/fcoin/__init__.py b/vnpy/api/fcoin/__init__.py new file mode 100644 index 00000000..ac8a89bc --- /dev/null +++ b/vnpy/api/fcoin/__init__.py @@ -0,0 +1 @@ +from .vnfcoin import FcoinRestApi, FcoinWebsocketApi \ No newline at end of file diff --git a/vnpy/api/fcoin/vnfcoin.py b/vnpy/api/fcoin/vnfcoin.py new file mode 100644 index 00000000..9d29b0e7 --- /dev/null +++ b/vnpy/api/fcoin/vnfcoin.py @@ -0,0 +1,294 @@ +# encoding: UTF-8 + +from __future__ import print_function +import hashlib +import hmac +import json +import ssl +import traceback +import base64 + +from queue import Queue, Empty +from multiprocessing.dummy import Pool +from time import time +from urlparse import urlparse +from copy import copy +from urllib import urlencode +from threading import Thread + +import requests +import websocket +from six.moves import input + + +REST_HOST = 'https://api.fcoin.com/v2' +WEBSOCKET_HOST = 'wss://api.fcoin.com/v2/ws' + + + + +######################################################################## +class FcoinRestApi(object): + """REST API""" + + #---------------------------------------------------------------------- + def __init__(self): + """Constructor""" + self.apiKey = '' + self.apiSecret = '' + + self.active = False + self.reqid = 0 + self.queue = Queue() + self.pool = None + self.sessionDict = {} # 会话对象字典 + + #---------------------------------------------------------------------- + def init(self, apiKey, apiSecret): + """初始化""" + self.apiKey = str(apiKey) + self.apiSecret = str(apiSecret) + + #---------------------------------------------------------------------- + def start(self, n=10): + """启动""" + if self.active: + return + + self.active = True + self.pool = Pool(n) + self.pool.map_async(self.run, range(n)) + + #---------------------------------------------------------------------- + def close(self): + """关闭""" + self.active = False + + if self.pool: + self.pool.close() + self.pool.join() + + #---------------------------------------------------------------------- + def addReq(self, method, path, callback, params=None, postdict=None): + """添加请求""" + self.reqid += 1 + req = (method, path, callback, params, postdict, self.reqid) + self.queue.put(req) + return self.reqid + + #---------------------------------------------------------------------- + def processReq(self, req, i): + """处理请求""" + method, path, callback, params, postdict, reqid = req + url = REST_HOST + path + timestamp = str(int(time()) * 1000) + + header = {} + header['FC-ACCESS-TIMESTAMP'] = timestamp + header['FC-ACCESS-KEY'] = self.apiKey + header['FC-ACCESS-SIGNATURE'] = self.generateSignature(method, url, timestamp, params, postdict) + + try: + # 使用长连接的session,比短连接的耗时缩短80% + session = self.sessionDict[i] + resp = session.request(method, url, headers=header, params=params, json=postdict) + #resp = requests.request(method, url, headers=header, params=params, data=postdict) + + #if method != 'GET': + #print '-' * 30 + #print 'method', method + #print 'url', url + #print 'header', header + #print 'params', params + #print 'postdict', postdict + + code = resp.status_code + d = resp.json() + + if code == 200: + callback(d, reqid) + else: + self.onError(code, d) + except Exception as e: + self.onError(type(e), e.message) + + #---------------------------------------------------------------------- + def run(self, i): + """连续运行""" + self.sessionDict[i] = requests.Session() + + while self.active: + try: + req = self.queue.get(timeout=1) + self.processReq(req, i) + except Empty: + pass + + #---------------------------------------------------------------------- + def generateSignature(self, method, path, timestamp, params=None, postdict=None): + """生成签名""" + # 对params在HTTP报文路径中,以请求字段方式序列化 + if params: + query = urlencode(sorted(params.items())) + path = path + '?' + query + + if postdict: + post = urlencode(sorted(postdict.items())) + else: + post = '' + + msg = method + path + timestamp + post + msg = base64.b64encode(msg) + + signature = hmac.new(self.apiSecret, msg, digestmod=hashlib.sha1).digest() + signature = base64.b64encode(signature) + + return signature + + #---------------------------------------------------------------------- + def onError(self, code, error): + """错误回调""" + print('on error') + print(code, error) + + #---------------------------------------------------------------------- + def onData(self, data, reqid): + """通用回调""" + print('on data') + print(data, reqid) + + +######################################################################## +class FcoinWebsocketApi(object): + """Websocket API""" + + #---------------------------------------------------------------------- + def __init__(self): + """Constructor""" + self.ws = None + self.thread = None + self.active = False + + #---------------------------------------------------------------------- + def start(self): + """启动""" + self.ws = websocket.create_connection(WEBSOCKET_HOST, + sslopt={'cert_reqs': ssl.CERT_NONE}) + + self.active = True + self.thread = Thread(target=self.run) + self.thread.start() + + self.onConnect() + + #---------------------------------------------------------------------- + def reconnect(self): + """重连""" + self.ws = websocket.create_connection(WEBSOCKET_HOST, + sslopt={'cert_reqs': ssl.CERT_NONE}) + + self.onConnect() + + #---------------------------------------------------------------------- + def run(self): + """运行""" + while self.active: + try: + stream = self.ws.recv() + data = json.loads(stream) + self.onData(data) + except: + msg = traceback.format_exc() + self.onError(msg) + self.reconnect() + + #---------------------------------------------------------------------- + def close(self): + """关闭""" + self.active = False + + if self.thread: + self.thread.join() + + #---------------------------------------------------------------------- + def onConnect(self): + """连接回调""" + print('connected') + + #---------------------------------------------------------------------- + def onData(self, data): + """数据回调""" + print('-' * 30) + l = data.keys() + l.sort() + for k in l: + print(k, data[k]) + + #---------------------------------------------------------------------- + def onError(self, msg): + """错误回调""" + print(msg) + + #---------------------------------------------------------------------- + def sendReq(self, req): + """发出请求""" + self.ws.send(json.dumps(req)) + + + + + +if __name__ == '__main__': + from datetime import datetime + from time import sleep + + API_KEY = '88893f839fbd49f4b5fcb03e7c15c015' + API_SECRET = 'ef383295cf4e4c128e6d18d7e9564b12' + + # REST测试 + rest = FcoinRestApi() + rest.init(API_KEY, API_SECRET) + rest.start(3) + + #rest.addReq('GET', '/accounts/balance', rest.onData) + + # 查委托 + #states = ['submitted', 'partial_filled', 'partial_canceled', + #'filled', 'canceled', 'pending_cancel'] + #req = { + #'symbol': 'ethusdt', + #'start': datetime.now().strftime('%Y%m%d'), + #'states': 'submitted', + #'limit': 500 + #} + + #for i in range(10): + #rest.addReq('GET', '/orders', rest.onData, params=req) + #sleep(2) + + req = { + 'symbol': 'ethusdt', + 'side': 'buy', + 'type': 'limit', + 'price': 300, + 'amount': 0.01 + } + rest.addReq('POST', '/orders', rest.onData, postdict=req) + #sleep(1) + #rest.addReq('POST', '/orders', rest.onData, params=req) + + ## WS测试 + #ws = FcoinWebsocketApi() + #ws.start() + + #req = { + #'cmd': 'sub', + #'args': ['depth.L20.btcusdt'], + #'id': 1 + #} + + #ws.sendReq(req) + + input() + + diff --git a/vnpy/api/huobi/__init__.py b/vnpy/api/huobi/__init__.py index 66fc387d..4f930268 100644 --- a/vnpy/api/huobi/__init__.py +++ b/vnpy/api/huobi/__init__.py @@ -1,3 +1,4 @@ # encoding: UTF-8 -from vnhuobi import TradeApi, DataApi \ No newline at end of file +from __future__ import absolute_import +from .vnhuobi import TradeApi, DataApi \ No newline at end of file diff --git a/vnpy/api/huobi/testmd.py b/vnpy/api/huobi/testmd.py index 89b320fc..ec44543a 100644 --- a/vnpy/api/huobi/testmd.py +++ b/vnpy/api/huobi/testmd.py @@ -5,7 +5,8 @@ #import zlib #import time -from vnhuobi import DataApi +from __future__ import absolute_import +from .vnhuobi import DataApi #if __name__ == '__main__': #while(1): diff --git a/vnpy/api/huobi/testtd.py b/vnpy/api/huobi/testtd.py index b3b1f8cd..8ce61ce1 100644 --- a/vnpy/api/huobi/testtd.py +++ b/vnpy/api/huobi/testtd.py @@ -1,6 +1,8 @@ # encoding: utf-8 -from vnhuobi import * +from __future__ import print_function +from __future__ import absolute_import +from .vnhuobi import * #---------------------------------------------------------------------- def testTrade(): @@ -15,9 +17,9 @@ def testTrade(): api.start() # 查询 - print api.getSymbols() - print api.getCurrencys() - print api.getTimestamp() + print(api.getSymbols()) + print(api.getCurrencys()) + print(api.getTimestamp()) #accountid = '' @@ -27,7 +29,7 @@ def testTrade(): #api.getAccountBalance(accountid) #api.getOrders(symbol, 'pre-submitted,submitted,partial-filled,partial-canceled,filled,canceled') #api.getOrders(symbol, 'filled') - print api.getMatchResults(symbol) + print(api.getMatchResults(symbol)) #api.getOrder('2440401255') #api.getMatchResult('2440401255') diff --git a/vnpy/api/huobi/vnhuobi.py b/vnpy/api/huobi/vnhuobi.py index 0f5f8a5a..514db58a 100644 --- a/vnpy/api/huobi/vnhuobi.py +++ b/vnpy/api/huobi/vnhuobi.py @@ -1,25 +1,28 @@ # encoding: utf-8 -import urllib -import hmac +from __future__ import print_function + import base64 import hashlib -import requests +import hmac +import json +import ssl import traceback +import urllib +import zlib from copy import copy from datetime import datetime -from threading import Thread -from queue import Queue, Empty from multiprocessing.dummy import Pool +from queue import Empty, Queue +from threading import Thread from time import sleep -import json -import zlib -from websocket import create_connection, _exceptions +import requests +from websocket import _exceptions, create_connection # 常量定义 -TIMEOUT = 5 +TIMEOUT = 10 HUOBI_API_HOST = "api.huobi.pro" HADAX_API_HOST = "api.hadax.com" LANG = 'zh-CN' @@ -419,71 +422,71 @@ class TradeApi(object): #---------------------------------------------------------------------- def onError(self, msg, reqid): """错误回调""" - print msg, reqid + print(msg, reqid) #---------------------------------------------------------------------- def onGetSymbols(self, data, reqid): """查询代码回调""" #print reqid, data for d in data: - print d + print(d) #---------------------------------------------------------------------- def onGetCurrencys(self, data, reqid): """查询货币回调""" - print reqid, data + print(reqid, data) #---------------------------------------------------------------------- def onGetTimestamp(self, data, reqid): """查询时间回调""" - print reqid, data + print(reqid, data) #---------------------------------------------------------------------- def onGetAccounts(self, data, reqid): """查询账户回调""" - print reqid, data + print(reqid, data) #---------------------------------------------------------------------- def onGetAccountBalance(self, data, reqid): """查询余额回调""" - print reqid, data + print(reqid, data) for d in data['data']['list']: - print d + print(d) #---------------------------------------------------------------------- def onGetOrders(self, data, reqid): """查询委托回调""" - print reqid, data + print(reqid, data) #---------------------------------------------------------------------- def onGetMatchResults(self, data, reqid): """查询成交回调""" - print reqid, data + print(reqid, data) #---------------------------------------------------------------------- def onGetOrder(self, data, reqid): """查询单一委托回调""" - print reqid, data + print(reqid, data) #---------------------------------------------------------------------- def onGetMatchResult(self, data, reqid): """查询单一成交回调""" - print reqid, data + print(reqid, data) #---------------------------------------------------------------------- def onPlaceOrder(self, data, reqid): """委托回调""" - print reqid, data + print(reqid, data) #---------------------------------------------------------------------- def onCancelOrder(self, data, reqid): """撤单回调""" - print reqid, data + print(reqid, data) #---------------------------------------------------------------------- def onBatchCancel(self, data, reqid): """批量撤单回调""" - print reqid, data + print(reqid, data) ######################################################################## @@ -503,8 +506,6 @@ class DataApi(object): self.subDict = {} self.url = '' - self.proxyHost = '' - self.proxyPort = 0 #---------------------------------------------------------------------- def run(self): @@ -531,12 +532,7 @@ class DataApi(object): def reconnect(self): """重连""" try: - if not self.proxyHost: - self.ws = create_connection(self.url) - else: - self.ws = create_connection(self.url, - http_proxy_host=self.proxyHost, - http_proxy_port=self.proxyPort) + self.ws = create_connection(self.url) return True except: msg = traceback.format_exc() @@ -552,20 +548,12 @@ class DataApi(object): self.subTopic(topic) #---------------------------------------------------------------------- - def connect(self, url, proxyHost='', proxyPort=0): + def connect(self, url): """连接""" self.url = url - self.proxyHost = proxyHost - self.proxyPort = proxyPort try: - if not self.proxyHost: - self.ws = create_connection(self.url) - else: - self.ws = create_connection(self.url, - http_proxy_host=self.proxyHost, - http_proxy_port=self.proxyPort) - + self.ws = create_connection(self.url, sslopt={'cert_reqs': ssl.CERT_NONE}) self.active = True self.thread.start() @@ -644,7 +632,7 @@ class DataApi(object): #---------------------------------------------------------------------- def onError(self, msg): """错误推送""" - print msg + print(msg) #---------------------------------------------------------------------- def onData(self, data): @@ -664,14 +652,14 @@ class DataApi(object): #---------------------------------------------------------------------- def onMarketDepth(self, data): """行情深度推送 """ - print data + print(data) #---------------------------------------------------------------------- def onTradeDetail(self, data): """成交细节推送""" - print data + print(data) #---------------------------------------------------------------------- def onMarketDetail(self, data): """市场细节推送""" - print data \ No newline at end of file + print(data) diff --git a/vnpy/api/ib/__init__.py b/vnpy/api/ib/__init__.py index 37b3bd13..75445132 100644 --- a/vnpy/api/ib/__init__.py +++ b/vnpy/api/ib/__init__.py @@ -1,3 +1,4 @@ # encoding: UTF-8 -from vnib import * \ No newline at end of file +from __future__ import absolute_import +from .vnib import * \ No newline at end of file diff --git a/vnpy/api/ib/build/CMakeCache.txt b/vnpy/api/ib/build/CMakeCache.txt deleted file mode 100644 index 9f6254c3..00000000 --- a/vnpy/api/ib/build/CMakeCache.txt +++ /dev/null @@ -1,393 +0,0 @@ -# This is the CMakeCache file. -# For build in directory: /home/vnpy/桌面/new/vn.ib/build -# It was generated by CMake: /usr/bin/cmake -# You can edit this file to change values found and used by cmake. -# If you do not want to change any of the values, simply exit the editor. -# If you do want to change a value, simply edit, save, and exit the editor. -# The syntax for the file is as follows: -# KEY:TYPE=VALUE -# KEY is the name of a variable in the cache. -# TYPE is a hint to GUIs for the type of VALUE, DO NOT EDIT TYPE!. -# VALUE is the current value for the KEY. - -######################## -# EXTERNAL cache entries -######################## - -//The threading library used by boost-thread -BOOST_THREAD_LIBRARY:FILEPATH=/usr/lib/x86_64-linux-gnu/libpthread.so - -//build ib -BUILD_IB:BOOL=ON - -//Boost atomic library (debug) -Boost_ATOMIC_LIBRARY_DEBUG:FILEPATH=/usr/lib/x86_64-linux-gnu/libboost_atomic.so - -//Boost atomic library (release) -Boost_ATOMIC_LIBRARY_RELEASE:FILEPATH=/usr/lib/x86_64-linux-gnu/libboost_atomic.so - -//Boost chrono library (debug) -Boost_CHRONO_LIBRARY_DEBUG:FILEPATH=/usr/lib/x86_64-linux-gnu/libboost_chrono.so - -//Boost chrono library (release) -Boost_CHRONO_LIBRARY_RELEASE:FILEPATH=/usr/lib/x86_64-linux-gnu/libboost_chrono.so - -//Boost date_time library (debug) -Boost_DATE_TIME_LIBRARY_DEBUG:FILEPATH=/usr/lib/x86_64-linux-gnu/libboost_date_time.so - -//Boost date_time library (release) -Boost_DATE_TIME_LIBRARY_RELEASE:FILEPATH=/usr/lib/x86_64-linux-gnu/libboost_date_time.so - -//The directory containing a CMake configuration file for Boost. -Boost_DIR:PATH=Boost_DIR-NOTFOUND - -//Path to a file. -Boost_INCLUDE_DIR:PATH=/usr/include - -//Boost library directory DEBUG -Boost_LIBRARY_DIR_DEBUG:PATH=/usr/lib/x86_64-linux-gnu - -//Boost library directory RELEASE -Boost_LIBRARY_DIR_RELEASE:PATH=/usr/lib/x86_64-linux-gnu - -//Boost python library (debug) -Boost_PYTHON_LIBRARY_DEBUG:FILEPATH=/usr/lib/x86_64-linux-gnu/libboost_python.so - -//Boost python library (release) -Boost_PYTHON_LIBRARY_RELEASE:FILEPATH=/usr/lib/x86_64-linux-gnu/libboost_python.so - -//Boost system library (debug) -Boost_SYSTEM_LIBRARY_DEBUG:FILEPATH=/usr/lib/x86_64-linux-gnu/libboost_system.so - -//Boost system library (release) -Boost_SYSTEM_LIBRARY_RELEASE:FILEPATH=/usr/lib/x86_64-linux-gnu/libboost_system.so - -//Boost thread library (debug) -Boost_THREAD_LIBRARY_DEBUG:FILEPATH=/usr/lib/x86_64-linux-gnu/libboost_thread.so - -//Boost thread library (release) -Boost_THREAD_LIBRARY_RELEASE:FILEPATH=/usr/lib/x86_64-linux-gnu/libboost_thread.so - -//Path to a program. -CMAKE_AR:FILEPATH=/usr/bin/ar - -//Choose the type of build, options are: None(CMAKE_CXX_FLAGS or -// CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel. -CMAKE_BUILD_TYPE:STRING= - -//Enable/Disable color output during build. -CMAKE_COLOR_MAKEFILE:BOOL=ON - -//CXX compiler -CMAKE_CXX_COMPILER:FILEPATH=/usr/bin/c++ - -//Flags used by the compiler during all build types. -CMAKE_CXX_FLAGS:STRING= - -//Flags used by the compiler during debug builds. -CMAKE_CXX_FLAGS_DEBUG:STRING=-g - -//Flags used by the compiler during release builds for minimum -// size. -CMAKE_CXX_FLAGS_MINSIZEREL:STRING=-Os -DNDEBUG - -//Flags used by the compiler during release builds. -CMAKE_CXX_FLAGS_RELEASE:STRING=-O3 -DNDEBUG - -//Flags used by the compiler during release builds with debug info. -CMAKE_CXX_FLAGS_RELWITHDEBINFO:STRING=-O2 -g -DNDEBUG - -//Flags used by the linker. -CMAKE_EXE_LINKER_FLAGS:STRING= - -//Flags used by the linker during debug builds. -CMAKE_EXE_LINKER_FLAGS_DEBUG:STRING= - -//Flags used by the linker during release minsize builds. -CMAKE_EXE_LINKER_FLAGS_MINSIZEREL:STRING= - -//Flags used by the linker during release builds. -CMAKE_EXE_LINKER_FLAGS_RELEASE:STRING= - -//Flags used by the linker during Release with Debug Info builds. -CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO:STRING= - -//Enable/Disable output of compile commands during generation. -CMAKE_EXPORT_COMPILE_COMMANDS:BOOL=OFF - -//Install path prefix, prepended onto install directories. -CMAKE_INSTALL_PREFIX:PATH=/usr/local - -//Path to a program. -CMAKE_LINKER:FILEPATH=/usr/bin/ld - -//Path to a program. -CMAKE_MAKE_PROGRAM:FILEPATH=/usr/bin/make - -//Flags used by the linker during the creation of modules. -CMAKE_MODULE_LINKER_FLAGS:STRING= - -//Flags used by the linker during debug builds. -CMAKE_MODULE_LINKER_FLAGS_DEBUG:STRING= - -//Flags used by the linker during release minsize builds. -CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL:STRING= - -//Flags used by the linker during release builds. -CMAKE_MODULE_LINKER_FLAGS_RELEASE:STRING= - -//Flags used by the linker during Release with Debug Info builds. -CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO:STRING= - -//Path to a program. -CMAKE_NM:FILEPATH=/usr/bin/nm - -//Path to a program. -CMAKE_OBJCOPY:FILEPATH=/usr/bin/objcopy - -//Path to a program. -CMAKE_OBJDUMP:FILEPATH=/usr/bin/objdump - -//Value Computed by CMake -CMAKE_PROJECT_NAME:STATIC=vn_ib_api - -//Path to a program. -CMAKE_RANLIB:FILEPATH=/usr/bin/ranlib - -//Flags used by the linker during the creation of dll's. -CMAKE_SHARED_LINKER_FLAGS:STRING= - -//Flags used by the linker during debug builds. -CMAKE_SHARED_LINKER_FLAGS_DEBUG:STRING= - -//Flags used by the linker during release minsize builds. -CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL:STRING= - -//Flags used by the linker during release builds. -CMAKE_SHARED_LINKER_FLAGS_RELEASE:STRING= - -//Flags used by the linker during Release with Debug Info builds. -CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO:STRING= - -//If set, runtime paths are not added when installing shared libraries, -// but are added when building. -CMAKE_SKIP_INSTALL_RPATH:BOOL=NO - -//If set, runtime paths are not added when using shared libraries. -CMAKE_SKIP_RPATH:BOOL=NO - -//Flags used by the linker during the creation of static libraries. -CMAKE_STATIC_LINKER_FLAGS:STRING= - -//Flags used by the linker during debug builds. -CMAKE_STATIC_LINKER_FLAGS_DEBUG:STRING= - -//Flags used by the linker during release minsize builds. -CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL:STRING= - -//Flags used by the linker during release builds. -CMAKE_STATIC_LINKER_FLAGS_RELEASE:STRING= - -//Flags used by the linker during Release with Debug Info builds. -CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO:STRING= - -//Path to a program. -CMAKE_STRIP:FILEPATH=/usr/bin/strip - -//If this value is on, makefiles will be generated without the -// .SILENT directive, and all commands will be echoed to the console -// during the make. This is useful for debugging only. With Visual -// Studio IDE projects all commands are done without /nologo. -CMAKE_VERBOSE_MAKEFILE:BOOL=FALSE - -//Path to a library. -IBAPI_LIBRARY:FILEPATH=/home/vnpy/桌面/new/vn.ib/ibapi/linux/build/lib/twsapi.so - -//Path to a library. -PYTHON_LIBRARY:FILEPATH=/home/vnpy/anaconda2/lib/libpython2.7.so - -//comiple 64bits -USE_64BITS:BOOL=ON - -//Value Computed by CMake -vn_ib_api_BINARY_DIR:STATIC=/home/vnpy/桌面/new/vn.ib/build - -//Value Computed by CMake -vn_ib_api_SOURCE_DIR:STATIC=/home/vnpy/桌面/new/vn.ib - -//Dependencies for the target -vnib_LIB_DEPENDS:STATIC=general;/home/vnpy/桌面/new/vn.ib/ibapi/linux/build/lib/twsapi.so;general;/usr/lib/x86_64-linux-gnu/libboost_python.so;general;/usr/lib/x86_64-linux-gnu/libboost_thread.so;general;/usr/lib/x86_64-linux-gnu/libboost_date_time.so;general;/usr/lib/x86_64-linux-gnu/libboost_system.so;general;/usr/lib/x86_64-linux-gnu/libboost_chrono.so;general;/usr/lib/x86_64-linux-gnu/libboost_atomic.so;general;/usr/lib/x86_64-linux-gnu/libpthread.so;general;/home/vnpy/anaconda2/lib/libpython2.7.so; - - -######################## -# INTERNAL cache entries -######################## - -//ADVANCED property for variable: Boost_ATOMIC_LIBRARY_DEBUG -Boost_ATOMIC_LIBRARY_DEBUG-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: Boost_ATOMIC_LIBRARY_RELEASE -Boost_ATOMIC_LIBRARY_RELEASE-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: Boost_CHRONO_LIBRARY_DEBUG -Boost_CHRONO_LIBRARY_DEBUG-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: Boost_CHRONO_LIBRARY_RELEASE -Boost_CHRONO_LIBRARY_RELEASE-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: Boost_DATE_TIME_LIBRARY_DEBUG -Boost_DATE_TIME_LIBRARY_DEBUG-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: Boost_DATE_TIME_LIBRARY_RELEASE -Boost_DATE_TIME_LIBRARY_RELEASE-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: Boost_DIR -Boost_DIR-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: Boost_INCLUDE_DIR -Boost_INCLUDE_DIR-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: Boost_LIBRARY_DIR_DEBUG -Boost_LIBRARY_DIR_DEBUG-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: Boost_LIBRARY_DIR_RELEASE -Boost_LIBRARY_DIR_RELEASE-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: Boost_PYTHON_LIBRARY_DEBUG -Boost_PYTHON_LIBRARY_DEBUG-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: Boost_PYTHON_LIBRARY_RELEASE -Boost_PYTHON_LIBRARY_RELEASE-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: Boost_SYSTEM_LIBRARY_DEBUG -Boost_SYSTEM_LIBRARY_DEBUG-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: Boost_SYSTEM_LIBRARY_RELEASE -Boost_SYSTEM_LIBRARY_RELEASE-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: Boost_THREAD_LIBRARY_DEBUG -Boost_THREAD_LIBRARY_DEBUG-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: Boost_THREAD_LIBRARY_RELEASE -Boost_THREAD_LIBRARY_RELEASE-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_AR -CMAKE_AR-ADVANCED:INTERNAL=1 -//This is the directory where this CMakeCache.txt was created -CMAKE_CACHEFILE_DIR:INTERNAL=/home/vnpy/桌面/new/vn.ib/build -//Major version of cmake used to create the current loaded cache -CMAKE_CACHE_MAJOR_VERSION:INTERNAL=3 -//Minor version of cmake used to create the current loaded cache -CMAKE_CACHE_MINOR_VERSION:INTERNAL=5 -//Patch version of cmake used to create the current loaded cache -CMAKE_CACHE_PATCH_VERSION:INTERNAL=1 -//ADVANCED property for variable: CMAKE_COLOR_MAKEFILE -CMAKE_COLOR_MAKEFILE-ADVANCED:INTERNAL=1 -//Path to CMake executable. -CMAKE_COMMAND:INTERNAL=/usr/bin/cmake -//Path to cpack program executable. -CMAKE_CPACK_COMMAND:INTERNAL=/usr/bin/cpack -//Path to ctest program executable. -CMAKE_CTEST_COMMAND:INTERNAL=/usr/bin/ctest -//ADVANCED property for variable: CMAKE_CXX_COMPILER -CMAKE_CXX_COMPILER-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_CXX_FLAGS -CMAKE_CXX_FLAGS-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_CXX_FLAGS_DEBUG -CMAKE_CXX_FLAGS_DEBUG-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_CXX_FLAGS_MINSIZEREL -CMAKE_CXX_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_CXX_FLAGS_RELEASE -CMAKE_CXX_FLAGS_RELEASE-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_CXX_FLAGS_RELWITHDEBINFO -CMAKE_CXX_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 -//Executable file format -CMAKE_EXECUTABLE_FORMAT:INTERNAL=ELF -//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS -CMAKE_EXE_LINKER_FLAGS-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_DEBUG -CMAKE_EXE_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_MINSIZEREL -CMAKE_EXE_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_RELEASE -CMAKE_EXE_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO -CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_EXPORT_COMPILE_COMMANDS -CMAKE_EXPORT_COMPILE_COMMANDS-ADVANCED:INTERNAL=1 -//Name of external makefile project generator. -CMAKE_EXTRA_GENERATOR:INTERNAL= -//Name of generator. -CMAKE_GENERATOR:INTERNAL=Unix Makefiles -//Name of generator platform. -CMAKE_GENERATOR_PLATFORM:INTERNAL= -//Name of generator toolset. -CMAKE_GENERATOR_TOOLSET:INTERNAL= -//Have symbol pthread_create -CMAKE_HAVE_LIBC_CREATE:INTERNAL= -//Have library pthreads -CMAKE_HAVE_PTHREADS_CREATE:INTERNAL= -//Have library pthread -CMAKE_HAVE_PTHREAD_CREATE:INTERNAL=1 -//Have include pthread.h -CMAKE_HAVE_PTHREAD_H:INTERNAL=1 -//Source directory with the top level CMakeLists.txt file for this -// project -CMAKE_HOME_DIRECTORY:INTERNAL=/home/vnpy/桌面/new/vn.ib -//Install .so files without execute permission. -CMAKE_INSTALL_SO_NO_EXE:INTERNAL=1 -//ADVANCED property for variable: CMAKE_LINKER -CMAKE_LINKER-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_MAKE_PROGRAM -CMAKE_MAKE_PROGRAM-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS -CMAKE_MODULE_LINKER_FLAGS-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_DEBUG -CMAKE_MODULE_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL -CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_RELEASE -CMAKE_MODULE_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO -CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_NM -CMAKE_NM-ADVANCED:INTERNAL=1 -//number of local generators -CMAKE_NUMBER_OF_MAKEFILES:INTERNAL=1 -//ADVANCED property for variable: CMAKE_OBJCOPY -CMAKE_OBJCOPY-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_OBJDUMP -CMAKE_OBJDUMP-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_RANLIB -CMAKE_RANLIB-ADVANCED:INTERNAL=1 -//Path to CMake installation. -CMAKE_ROOT:INTERNAL=/usr/share/cmake-3.5 -//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS -CMAKE_SHARED_LINKER_FLAGS-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_DEBUG -CMAKE_SHARED_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL -CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_RELEASE -CMAKE_SHARED_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO -CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_SKIP_INSTALL_RPATH -CMAKE_SKIP_INSTALL_RPATH-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_SKIP_RPATH -CMAKE_SKIP_RPATH-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS -CMAKE_STATIC_LINKER_FLAGS-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_DEBUG -CMAKE_STATIC_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL -CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_RELEASE -CMAKE_STATIC_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO -CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 -//ADVANCED property for variable: CMAKE_STRIP -CMAKE_STRIP-ADVANCED:INTERNAL=1 -//uname command -CMAKE_UNAME:INTERNAL=/bin/uname -//ADVANCED property for variable: CMAKE_VERBOSE_MAKEFILE -CMAKE_VERBOSE_MAKEFILE-ADVANCED:INTERNAL=1 -//Details about finding Threads -FIND_PACKAGE_MESSAGE_DETAILS_Threads:INTERNAL=[TRUE][v()] -//Components requested for this build tree. -_Boost_COMPONENTS_SEARCHED:INTERNAL=atomic;chrono;date_time;python;system;thread -//Last used Boost_INCLUDE_DIR value. -_Boost_INCLUDE_DIR_LAST:INTERNAL=/usr/include -//Last used Boost_LIBRARY_DIR_DEBUG value. -_Boost_LIBRARY_DIR_DEBUG_LAST:INTERNAL=/usr/lib/x86_64-linux-gnu -//Last used Boost_LIBRARY_DIR_RELEASE value. -_Boost_LIBRARY_DIR_RELEASE_LAST:INTERNAL=/usr/lib/x86_64-linux-gnu -//Last used Boost_NAMESPACE value. -_Boost_NAMESPACE_LAST:INTERNAL=boost -//Last used Boost_USE_MULTITHREADED value. -_Boost_USE_MULTITHREADED_LAST:INTERNAL=ON - diff --git a/vnpy/api/ib/build/CMakeFiles/3.5.1/CMakeCXXCompiler.cmake b/vnpy/api/ib/build/CMakeFiles/3.5.1/CMakeCXXCompiler.cmake deleted file mode 100644 index 013ee929..00000000 --- a/vnpy/api/ib/build/CMakeFiles/3.5.1/CMakeCXXCompiler.cmake +++ /dev/null @@ -1,68 +0,0 @@ -set(CMAKE_CXX_COMPILER "/usr/bin/c++") -set(CMAKE_CXX_COMPILER_ARG1 "") -set(CMAKE_CXX_COMPILER_ID "GNU") -set(CMAKE_CXX_COMPILER_VERSION "5.4.0") -set(CMAKE_CXX_COMPILER_WRAPPER "") -set(CMAKE_CXX_STANDARD_COMPUTED_DEFAULT "98") -set(CMAKE_CXX_COMPILE_FEATURES "cxx_template_template_parameters;cxx_alias_templates;cxx_alignas;cxx_alignof;cxx_attributes;cxx_auto_type;cxx_constexpr;cxx_decltype;cxx_decltype_incomplete_return_types;cxx_default_function_template_args;cxx_defaulted_functions;cxx_defaulted_move_initializers;cxx_delegating_constructors;cxx_deleted_functions;cxx_enum_forward_declarations;cxx_explicit_conversions;cxx_extended_friend_declarations;cxx_extern_templates;cxx_final;cxx_func_identifier;cxx_generalized_initializers;cxx_inheriting_constructors;cxx_inline_namespaces;cxx_lambdas;cxx_local_type_template_args;cxx_long_long_type;cxx_noexcept;cxx_nonstatic_member_init;cxx_nullptr;cxx_override;cxx_range_for;cxx_raw_string_literals;cxx_reference_qualified_functions;cxx_right_angle_brackets;cxx_rvalue_references;cxx_sizeof_member;cxx_static_assert;cxx_strong_enums;cxx_thread_local;cxx_trailing_return_types;cxx_unicode_literals;cxx_uniform_initialization;cxx_unrestricted_unions;cxx_user_literals;cxx_variadic_macros;cxx_variadic_templates;cxx_aggregate_default_initializers;cxx_attribute_deprecated;cxx_binary_literals;cxx_contextual_conversions;cxx_decltype_auto;cxx_digit_separators;cxx_generic_lambdas;cxx_lambda_init_captures;cxx_relaxed_constexpr;cxx_return_type_deduction;cxx_variable_templates") -set(CMAKE_CXX98_COMPILE_FEATURES "cxx_template_template_parameters") -set(CMAKE_CXX11_COMPILE_FEATURES "cxx_alias_templates;cxx_alignas;cxx_alignof;cxx_attributes;cxx_auto_type;cxx_constexpr;cxx_decltype;cxx_decltype_incomplete_return_types;cxx_default_function_template_args;cxx_defaulted_functions;cxx_defaulted_move_initializers;cxx_delegating_constructors;cxx_deleted_functions;cxx_enum_forward_declarations;cxx_explicit_conversions;cxx_extended_friend_declarations;cxx_extern_templates;cxx_final;cxx_func_identifier;cxx_generalized_initializers;cxx_inheriting_constructors;cxx_inline_namespaces;cxx_lambdas;cxx_local_type_template_args;cxx_long_long_type;cxx_noexcept;cxx_nonstatic_member_init;cxx_nullptr;cxx_override;cxx_range_for;cxx_raw_string_literals;cxx_reference_qualified_functions;cxx_right_angle_brackets;cxx_rvalue_references;cxx_sizeof_member;cxx_static_assert;cxx_strong_enums;cxx_thread_local;cxx_trailing_return_types;cxx_unicode_literals;cxx_uniform_initialization;cxx_unrestricted_unions;cxx_user_literals;cxx_variadic_macros;cxx_variadic_templates") -set(CMAKE_CXX14_COMPILE_FEATURES "cxx_aggregate_default_initializers;cxx_attribute_deprecated;cxx_binary_literals;cxx_contextual_conversions;cxx_decltype_auto;cxx_digit_separators;cxx_generic_lambdas;cxx_lambda_init_captures;cxx_relaxed_constexpr;cxx_return_type_deduction;cxx_variable_templates") - -set(CMAKE_CXX_PLATFORM_ID "Linux") -set(CMAKE_CXX_SIMULATE_ID "") -set(CMAKE_CXX_SIMULATE_VERSION "") - -set(CMAKE_AR "/usr/bin/ar") -set(CMAKE_RANLIB "/usr/bin/ranlib") -set(CMAKE_LINKER "/usr/bin/ld") -set(CMAKE_COMPILER_IS_GNUCXX 1) -set(CMAKE_CXX_COMPILER_LOADED 1) -set(CMAKE_CXX_COMPILER_WORKS TRUE) -set(CMAKE_CXX_ABI_COMPILED TRUE) -set(CMAKE_COMPILER_IS_MINGW ) -set(CMAKE_COMPILER_IS_CYGWIN ) -if(CMAKE_COMPILER_IS_CYGWIN) - set(CYGWIN 1) - set(UNIX 1) -endif() - -set(CMAKE_CXX_COMPILER_ENV_VAR "CXX") - -if(CMAKE_COMPILER_IS_MINGW) - set(MINGW 1) -endif() -set(CMAKE_CXX_COMPILER_ID_RUN 1) -set(CMAKE_CXX_IGNORE_EXTENSIONS inl;h;hpp;HPP;H;o;O;obj;OBJ;def;DEF;rc;RC) -set(CMAKE_CXX_SOURCE_FILE_EXTENSIONS C;M;c++;cc;cpp;cxx;mm;CPP) -set(CMAKE_CXX_LINKER_PREFERENCE 30) -set(CMAKE_CXX_LINKER_PREFERENCE_PROPAGATES 1) - -# Save compiler ABI information. -set(CMAKE_CXX_SIZEOF_DATA_PTR "8") -set(CMAKE_CXX_COMPILER_ABI "ELF") -set(CMAKE_CXX_LIBRARY_ARCHITECTURE "x86_64-linux-gnu") - -if(CMAKE_CXX_SIZEOF_DATA_PTR) - set(CMAKE_SIZEOF_VOID_P "${CMAKE_CXX_SIZEOF_DATA_PTR}") -endif() - -if(CMAKE_CXX_COMPILER_ABI) - set(CMAKE_INTERNAL_PLATFORM_ABI "${CMAKE_CXX_COMPILER_ABI}") -endif() - -if(CMAKE_CXX_LIBRARY_ARCHITECTURE) - set(CMAKE_LIBRARY_ARCHITECTURE "x86_64-linux-gnu") -endif() - -set(CMAKE_CXX_CL_SHOWINCLUDES_PREFIX "") -if(CMAKE_CXX_CL_SHOWINCLUDES_PREFIX) - set(CMAKE_CL_SHOWINCLUDES_PREFIX "${CMAKE_CXX_CL_SHOWINCLUDES_PREFIX}") -endif() - - - - -set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "stdc++;m;c") -set(CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES "/usr/lib/gcc/x86_64-linux-gnu/5;/usr/lib/x86_64-linux-gnu;/usr/lib;/lib/x86_64-linux-gnu;/lib") -set(CMAKE_CXX_IMPLICIT_LINK_FRAMEWORK_DIRECTORIES "") diff --git a/vnpy/api/ib/build/CMakeFiles/3.5.1/CMakeDetermineCompilerABI_CXX.bin b/vnpy/api/ib/build/CMakeFiles/3.5.1/CMakeDetermineCompilerABI_CXX.bin deleted file mode 100644 index fa406403..00000000 Binary files a/vnpy/api/ib/build/CMakeFiles/3.5.1/CMakeDetermineCompilerABI_CXX.bin and /dev/null differ diff --git a/vnpy/api/ib/build/CMakeFiles/3.5.1/CMakeSystem.cmake b/vnpy/api/ib/build/CMakeFiles/3.5.1/CMakeSystem.cmake deleted file mode 100644 index b40fa5e9..00000000 --- a/vnpy/api/ib/build/CMakeFiles/3.5.1/CMakeSystem.cmake +++ /dev/null @@ -1,15 +0,0 @@ -set(CMAKE_HOST_SYSTEM "Linux-4.4.0-47-generic") -set(CMAKE_HOST_SYSTEM_NAME "Linux") -set(CMAKE_HOST_SYSTEM_VERSION "4.4.0-47-generic") -set(CMAKE_HOST_SYSTEM_PROCESSOR "x86_64") - - - -set(CMAKE_SYSTEM "Linux-4.4.0-47-generic") -set(CMAKE_SYSTEM_NAME "Linux") -set(CMAKE_SYSTEM_VERSION "4.4.0-47-generic") -set(CMAKE_SYSTEM_PROCESSOR "x86_64") - -set(CMAKE_CROSSCOMPILING "FALSE") - -set(CMAKE_SYSTEM_LOADED 1) diff --git a/vnpy/api/ib/build/CMakeFiles/3.5.1/CompilerIdCXX/CMakeCXXCompilerId.cpp b/vnpy/api/ib/build/CMakeFiles/3.5.1/CompilerIdCXX/CMakeCXXCompilerId.cpp deleted file mode 100644 index e6d85363..00000000 --- a/vnpy/api/ib/build/CMakeFiles/3.5.1/CompilerIdCXX/CMakeCXXCompilerId.cpp +++ /dev/null @@ -1,533 +0,0 @@ -/* This source file must have a .cpp extension so that all C++ compilers - recognize the extension without flags. Borland does not know .cxx for - example. */ -#ifndef __cplusplus -# error "A C compiler has been selected for C++." -#endif - - -/* Version number components: V=Version, R=Revision, P=Patch - Version date components: YYYY=Year, MM=Month, DD=Day */ - -#if defined(__COMO__) -# define COMPILER_ID "Comeau" - /* __COMO_VERSION__ = VRR */ -# define COMPILER_VERSION_MAJOR DEC(__COMO_VERSION__ / 100) -# define COMPILER_VERSION_MINOR DEC(__COMO_VERSION__ % 100) - -#elif defined(__INTEL_COMPILER) || defined(__ICC) -# define COMPILER_ID "Intel" -# if defined(_MSC_VER) -# define SIMULATE_ID "MSVC" -# endif - /* __INTEL_COMPILER = VRP */ -# define COMPILER_VERSION_MAJOR DEC(__INTEL_COMPILER/100) -# define COMPILER_VERSION_MINOR DEC(__INTEL_COMPILER/10 % 10) -# if defined(__INTEL_COMPILER_UPDATE) -# define COMPILER_VERSION_PATCH DEC(__INTEL_COMPILER_UPDATE) -# else -# define COMPILER_VERSION_PATCH DEC(__INTEL_COMPILER % 10) -# endif -# if defined(__INTEL_COMPILER_BUILD_DATE) - /* __INTEL_COMPILER_BUILD_DATE = YYYYMMDD */ -# define COMPILER_VERSION_TWEAK DEC(__INTEL_COMPILER_BUILD_DATE) -# endif -# if defined(_MSC_VER) - /* _MSC_VER = VVRR */ -# define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) -# define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) -# endif - -#elif defined(__PATHCC__) -# define COMPILER_ID "PathScale" -# define COMPILER_VERSION_MAJOR DEC(__PATHCC__) -# define COMPILER_VERSION_MINOR DEC(__PATHCC_MINOR__) -# if defined(__PATHCC_PATCHLEVEL__) -# define COMPILER_VERSION_PATCH DEC(__PATHCC_PATCHLEVEL__) -# endif - -#elif defined(__BORLANDC__) && defined(__CODEGEARC_VERSION__) -# define COMPILER_ID "Embarcadero" -# define COMPILER_VERSION_MAJOR HEX(__CODEGEARC_VERSION__>>24 & 0x00FF) -# define COMPILER_VERSION_MINOR HEX(__CODEGEARC_VERSION__>>16 & 0x00FF) -# define COMPILER_VERSION_PATCH DEC(__CODEGEARC_VERSION__ & 0xFFFF) - -#elif defined(__BORLANDC__) -# define COMPILER_ID "Borland" - /* __BORLANDC__ = 0xVRR */ -# define COMPILER_VERSION_MAJOR HEX(__BORLANDC__>>8) -# define COMPILER_VERSION_MINOR HEX(__BORLANDC__ & 0xFF) - -#elif defined(__WATCOMC__) && __WATCOMC__ < 1200 -# define COMPILER_ID "Watcom" - /* __WATCOMC__ = VVRR */ -# define COMPILER_VERSION_MAJOR DEC(__WATCOMC__ / 100) -# define COMPILER_VERSION_MINOR DEC((__WATCOMC__ / 10) % 10) -# if (__WATCOMC__ % 10) > 0 -# define COMPILER_VERSION_PATCH DEC(__WATCOMC__ % 10) -# endif - -#elif defined(__WATCOMC__) -# define COMPILER_ID "OpenWatcom" - /* __WATCOMC__ = VVRP + 1100 */ -# define COMPILER_VERSION_MAJOR DEC((__WATCOMC__ - 1100) / 100) -# define COMPILER_VERSION_MINOR DEC((__WATCOMC__ / 10) % 10) -# if (__WATCOMC__ % 10) > 0 -# define COMPILER_VERSION_PATCH DEC(__WATCOMC__ % 10) -# endif - -#elif defined(__SUNPRO_CC) -# define COMPILER_ID "SunPro" -# if __SUNPRO_CC >= 0x5100 - /* __SUNPRO_CC = 0xVRRP */ -# define COMPILER_VERSION_MAJOR HEX(__SUNPRO_CC>>12) -# define COMPILER_VERSION_MINOR HEX(__SUNPRO_CC>>4 & 0xFF) -# define COMPILER_VERSION_PATCH HEX(__SUNPRO_CC & 0xF) -# else - /* __SUNPRO_CC = 0xVRP */ -# define COMPILER_VERSION_MAJOR HEX(__SUNPRO_CC>>8) -# define COMPILER_VERSION_MINOR HEX(__SUNPRO_CC>>4 & 0xF) -# define COMPILER_VERSION_PATCH HEX(__SUNPRO_CC & 0xF) -# endif - -#elif defined(__HP_aCC) -# define COMPILER_ID "HP" - /* __HP_aCC = VVRRPP */ -# define COMPILER_VERSION_MAJOR DEC(__HP_aCC/10000) -# define COMPILER_VERSION_MINOR DEC(__HP_aCC/100 % 100) -# define COMPILER_VERSION_PATCH DEC(__HP_aCC % 100) - -#elif defined(__DECCXX) -# define COMPILER_ID "Compaq" - /* __DECCXX_VER = VVRRTPPPP */ -# define COMPILER_VERSION_MAJOR DEC(__DECCXX_VER/10000000) -# define COMPILER_VERSION_MINOR DEC(__DECCXX_VER/100000 % 100) -# define COMPILER_VERSION_PATCH DEC(__DECCXX_VER % 10000) - -#elif defined(__IBMCPP__) && defined(__COMPILER_VER__) -# define COMPILER_ID "zOS" - /* __IBMCPP__ = VRP */ -# define COMPILER_VERSION_MAJOR DEC(__IBMCPP__/100) -# define COMPILER_VERSION_MINOR DEC(__IBMCPP__/10 % 10) -# define COMPILER_VERSION_PATCH DEC(__IBMCPP__ % 10) - -#elif defined(__IBMCPP__) && !defined(__COMPILER_VER__) && __IBMCPP__ >= 800 -# define COMPILER_ID "XL" - /* __IBMCPP__ = VRP */ -# define COMPILER_VERSION_MAJOR DEC(__IBMCPP__/100) -# define COMPILER_VERSION_MINOR DEC(__IBMCPP__/10 % 10) -# define COMPILER_VERSION_PATCH DEC(__IBMCPP__ % 10) - -#elif defined(__IBMCPP__) && !defined(__COMPILER_VER__) && __IBMCPP__ < 800 -# define COMPILER_ID "VisualAge" - /* __IBMCPP__ = VRP */ -# define COMPILER_VERSION_MAJOR DEC(__IBMCPP__/100) -# define COMPILER_VERSION_MINOR DEC(__IBMCPP__/10 % 10) -# define COMPILER_VERSION_PATCH DEC(__IBMCPP__ % 10) - -#elif defined(__PGI) -# define COMPILER_ID "PGI" -# define COMPILER_VERSION_MAJOR DEC(__PGIC__) -# define COMPILER_VERSION_MINOR DEC(__PGIC_MINOR__) -# if defined(__PGIC_PATCHLEVEL__) -# define COMPILER_VERSION_PATCH DEC(__PGIC_PATCHLEVEL__) -# endif - -#elif defined(_CRAYC) -# define COMPILER_ID "Cray" -# define COMPILER_VERSION_MAJOR DEC(_RELEASE_MAJOR) -# define COMPILER_VERSION_MINOR DEC(_RELEASE_MINOR) - -#elif defined(__TI_COMPILER_VERSION__) -# define COMPILER_ID "TI" - /* __TI_COMPILER_VERSION__ = VVVRRRPPP */ -# define COMPILER_VERSION_MAJOR DEC(__TI_COMPILER_VERSION__/1000000) -# define COMPILER_VERSION_MINOR DEC(__TI_COMPILER_VERSION__/1000 % 1000) -# define COMPILER_VERSION_PATCH DEC(__TI_COMPILER_VERSION__ % 1000) - -#elif defined(__FUJITSU) || defined(__FCC_VERSION) || defined(__fcc_version) -# define COMPILER_ID "Fujitsu" - -#elif defined(__SCO_VERSION__) -# define COMPILER_ID "SCO" - -#elif defined(__clang__) && defined(__apple_build_version__) -# define COMPILER_ID "AppleClang" -# if defined(_MSC_VER) -# define SIMULATE_ID "MSVC" -# endif -# define COMPILER_VERSION_MAJOR DEC(__clang_major__) -# define COMPILER_VERSION_MINOR DEC(__clang_minor__) -# define COMPILER_VERSION_PATCH DEC(__clang_patchlevel__) -# if defined(_MSC_VER) - /* _MSC_VER = VVRR */ -# define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) -# define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) -# endif -# define COMPILER_VERSION_TWEAK DEC(__apple_build_version__) - -#elif defined(__clang__) -# define COMPILER_ID "Clang" -# if defined(_MSC_VER) -# define SIMULATE_ID "MSVC" -# endif -# define COMPILER_VERSION_MAJOR DEC(__clang_major__) -# define COMPILER_VERSION_MINOR DEC(__clang_minor__) -# define COMPILER_VERSION_PATCH DEC(__clang_patchlevel__) -# if defined(_MSC_VER) - /* _MSC_VER = VVRR */ -# define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) -# define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) -# endif - -#elif defined(__GNUC__) -# define COMPILER_ID "GNU" -# define COMPILER_VERSION_MAJOR DEC(__GNUC__) -# if defined(__GNUC_MINOR__) -# define COMPILER_VERSION_MINOR DEC(__GNUC_MINOR__) -# endif -# if defined(__GNUC_PATCHLEVEL__) -# define COMPILER_VERSION_PATCH DEC(__GNUC_PATCHLEVEL__) -# endif - -#elif defined(_MSC_VER) -# define COMPILER_ID "MSVC" - /* _MSC_VER = VVRR */ -# define COMPILER_VERSION_MAJOR DEC(_MSC_VER / 100) -# define COMPILER_VERSION_MINOR DEC(_MSC_VER % 100) -# if defined(_MSC_FULL_VER) -# if _MSC_VER >= 1400 - /* _MSC_FULL_VER = VVRRPPPPP */ -# define COMPILER_VERSION_PATCH DEC(_MSC_FULL_VER % 100000) -# else - /* _MSC_FULL_VER = VVRRPPPP */ -# define COMPILER_VERSION_PATCH DEC(_MSC_FULL_VER % 10000) -# endif -# endif -# if defined(_MSC_BUILD) -# define COMPILER_VERSION_TWEAK DEC(_MSC_BUILD) -# endif - -#elif defined(__VISUALDSPVERSION__) || defined(__ADSPBLACKFIN__) || defined(__ADSPTS__) || defined(__ADSP21000__) -# define COMPILER_ID "ADSP" -#if defined(__VISUALDSPVERSION__) - /* __VISUALDSPVERSION__ = 0xVVRRPP00 */ -# define COMPILER_VERSION_MAJOR HEX(__VISUALDSPVERSION__>>24) -# define COMPILER_VERSION_MINOR HEX(__VISUALDSPVERSION__>>16 & 0xFF) -# define COMPILER_VERSION_PATCH HEX(__VISUALDSPVERSION__>>8 & 0xFF) -#endif - -#elif defined(__IAR_SYSTEMS_ICC__ ) || defined(__IAR_SYSTEMS_ICC) -# define COMPILER_ID "IAR" - -#elif defined(__ARMCC_VERSION) -# define COMPILER_ID "ARMCC" -#if __ARMCC_VERSION >= 1000000 - /* __ARMCC_VERSION = VRRPPPP */ - # define COMPILER_VERSION_MAJOR DEC(__ARMCC_VERSION/1000000) - # define COMPILER_VERSION_MINOR DEC(__ARMCC_VERSION/10000 % 100) - # define COMPILER_VERSION_PATCH DEC(__ARMCC_VERSION % 10000) -#else - /* __ARMCC_VERSION = VRPPPP */ - # define COMPILER_VERSION_MAJOR DEC(__ARMCC_VERSION/100000) - # define COMPILER_VERSION_MINOR DEC(__ARMCC_VERSION/10000 % 10) - # define COMPILER_VERSION_PATCH DEC(__ARMCC_VERSION % 10000) -#endif - - -#elif defined(_SGI_COMPILER_VERSION) || defined(_COMPILER_VERSION) -# define COMPILER_ID "MIPSpro" -# if defined(_SGI_COMPILER_VERSION) - /* _SGI_COMPILER_VERSION = VRP */ -# define COMPILER_VERSION_MAJOR DEC(_SGI_COMPILER_VERSION/100) -# define COMPILER_VERSION_MINOR DEC(_SGI_COMPILER_VERSION/10 % 10) -# define COMPILER_VERSION_PATCH DEC(_SGI_COMPILER_VERSION % 10) -# else - /* _COMPILER_VERSION = VRP */ -# define COMPILER_VERSION_MAJOR DEC(_COMPILER_VERSION/100) -# define COMPILER_VERSION_MINOR DEC(_COMPILER_VERSION/10 % 10) -# define COMPILER_VERSION_PATCH DEC(_COMPILER_VERSION % 10) -# endif - - -/* These compilers are either not known or too old to define an - identification macro. Try to identify the platform and guess that - it is the native compiler. */ -#elif defined(__sgi) -# define COMPILER_ID "MIPSpro" - -#elif defined(__hpux) || defined(__hpua) -# define COMPILER_ID "HP" - -#else /* unknown compiler */ -# define COMPILER_ID "" -#endif - -/* Construct the string literal in pieces to prevent the source from - getting matched. Store it in a pointer rather than an array - because some compilers will just produce instructions to fill the - array rather than assigning a pointer to a static array. */ -char const* info_compiler = "INFO" ":" "compiler[" COMPILER_ID "]"; -#ifdef SIMULATE_ID -char const* info_simulate = "INFO" ":" "simulate[" SIMULATE_ID "]"; -#endif - -#ifdef __QNXNTO__ -char const* qnxnto = "INFO" ":" "qnxnto[]"; -#endif - -#if defined(__CRAYXE) || defined(__CRAYXC) -char const *info_cray = "INFO" ":" "compiler_wrapper[CrayPrgEnv]"; -#endif - -#define STRINGIFY_HELPER(X) #X -#define STRINGIFY(X) STRINGIFY_HELPER(X) - -/* Identify known platforms by name. */ -#if defined(__linux) || defined(__linux__) || defined(linux) -# define PLATFORM_ID "Linux" - -#elif defined(__CYGWIN__) -# define PLATFORM_ID "Cygwin" - -#elif defined(__MINGW32__) -# define PLATFORM_ID "MinGW" - -#elif defined(__APPLE__) -# define PLATFORM_ID "Darwin" - -#elif defined(_WIN32) || defined(__WIN32__) || defined(WIN32) -# define PLATFORM_ID "Windows" - -#elif defined(__FreeBSD__) || defined(__FreeBSD) -# define PLATFORM_ID "FreeBSD" - -#elif defined(__NetBSD__) || defined(__NetBSD) -# define PLATFORM_ID "NetBSD" - -#elif defined(__OpenBSD__) || defined(__OPENBSD) -# define PLATFORM_ID "OpenBSD" - -#elif defined(__sun) || defined(sun) -# define PLATFORM_ID "SunOS" - -#elif defined(_AIX) || defined(__AIX) || defined(__AIX__) || defined(__aix) || defined(__aix__) -# define PLATFORM_ID "AIX" - -#elif defined(__sgi) || defined(__sgi__) || defined(_SGI) -# define PLATFORM_ID "IRIX" - -#elif defined(__hpux) || defined(__hpux__) -# define PLATFORM_ID "HP-UX" - -#elif defined(__HAIKU__) -# define PLATFORM_ID "Haiku" - -#elif defined(__BeOS) || defined(__BEOS__) || defined(_BEOS) -# define PLATFORM_ID "BeOS" - -#elif defined(__QNX__) || defined(__QNXNTO__) -# define PLATFORM_ID "QNX" - -#elif defined(__tru64) || defined(_tru64) || defined(__TRU64__) -# define PLATFORM_ID "Tru64" - -#elif defined(__riscos) || defined(__riscos__) -# define PLATFORM_ID "RISCos" - -#elif defined(__sinix) || defined(__sinix__) || defined(__SINIX__) -# define PLATFORM_ID "SINIX" - -#elif defined(__UNIX_SV__) -# define PLATFORM_ID "UNIX_SV" - -#elif defined(__bsdos__) -# define PLATFORM_ID "BSDOS" - -#elif defined(_MPRAS) || defined(MPRAS) -# define PLATFORM_ID "MP-RAS" - -#elif defined(__osf) || defined(__osf__) -# define PLATFORM_ID "OSF1" - -#elif defined(_SCO_SV) || defined(SCO_SV) || defined(sco_sv) -# define PLATFORM_ID "SCO_SV" - -#elif defined(__ultrix) || defined(__ultrix__) || defined(_ULTRIX) -# define PLATFORM_ID "ULTRIX" - -#elif defined(__XENIX__) || defined(_XENIX) || defined(XENIX) -# define PLATFORM_ID "Xenix" - -#elif defined(__WATCOMC__) -# if defined(__LINUX__) -# define PLATFORM_ID "Linux" - -# elif defined(__DOS__) -# define PLATFORM_ID "DOS" - -# elif defined(__OS2__) -# define PLATFORM_ID "OS2" - -# elif defined(__WINDOWS__) -# define PLATFORM_ID "Windows3x" - -# else /* unknown platform */ -# define PLATFORM_ID "" -# endif - -#else /* unknown platform */ -# define PLATFORM_ID "" - -#endif - -/* For windows compilers MSVC and Intel we can determine - the architecture of the compiler being used. This is because - the compilers do not have flags that can change the architecture, - but rather depend on which compiler is being used -*/ -#if defined(_WIN32) && defined(_MSC_VER) -# if defined(_M_IA64) -# define ARCHITECTURE_ID "IA64" - -# elif defined(_M_X64) || defined(_M_AMD64) -# define ARCHITECTURE_ID "x64" - -# elif defined(_M_IX86) -# define ARCHITECTURE_ID "X86" - -# elif defined(_M_ARM) -# if _M_ARM == 4 -# define ARCHITECTURE_ID "ARMV4I" -# elif _M_ARM == 5 -# define ARCHITECTURE_ID "ARMV5I" -# else -# define ARCHITECTURE_ID "ARMV" STRINGIFY(_M_ARM) -# endif - -# elif defined(_M_MIPS) -# define ARCHITECTURE_ID "MIPS" - -# elif defined(_M_SH) -# define ARCHITECTURE_ID "SHx" - -# else /* unknown architecture */ -# define ARCHITECTURE_ID "" -# endif - -#elif defined(__WATCOMC__) -# if defined(_M_I86) -# define ARCHITECTURE_ID "I86" - -# elif defined(_M_IX86) -# define ARCHITECTURE_ID "X86" - -# else /* unknown architecture */ -# define ARCHITECTURE_ID "" -# endif - -#else -# define ARCHITECTURE_ID "" -#endif - -/* Convert integer to decimal digit literals. */ -#define DEC(n) \ - ('0' + (((n) / 10000000)%10)), \ - ('0' + (((n) / 1000000)%10)), \ - ('0' + (((n) / 100000)%10)), \ - ('0' + (((n) / 10000)%10)), \ - ('0' + (((n) / 1000)%10)), \ - ('0' + (((n) / 100)%10)), \ - ('0' + (((n) / 10)%10)), \ - ('0' + ((n) % 10)) - -/* Convert integer to hex digit literals. */ -#define HEX(n) \ - ('0' + ((n)>>28 & 0xF)), \ - ('0' + ((n)>>24 & 0xF)), \ - ('0' + ((n)>>20 & 0xF)), \ - ('0' + ((n)>>16 & 0xF)), \ - ('0' + ((n)>>12 & 0xF)), \ - ('0' + ((n)>>8 & 0xF)), \ - ('0' + ((n)>>4 & 0xF)), \ - ('0' + ((n) & 0xF)) - -/* Construct a string literal encoding the version number components. */ -#ifdef COMPILER_VERSION_MAJOR -char const info_version[] = { - 'I', 'N', 'F', 'O', ':', - 'c','o','m','p','i','l','e','r','_','v','e','r','s','i','o','n','[', - COMPILER_VERSION_MAJOR, -# ifdef COMPILER_VERSION_MINOR - '.', COMPILER_VERSION_MINOR, -# ifdef COMPILER_VERSION_PATCH - '.', COMPILER_VERSION_PATCH, -# ifdef COMPILER_VERSION_TWEAK - '.', COMPILER_VERSION_TWEAK, -# endif -# endif -# endif - ']','\0'}; -#endif - -/* Construct a string literal encoding the version number components. */ -#ifdef SIMULATE_VERSION_MAJOR -char const info_simulate_version[] = { - 'I', 'N', 'F', 'O', ':', - 's','i','m','u','l','a','t','e','_','v','e','r','s','i','o','n','[', - SIMULATE_VERSION_MAJOR, -# ifdef SIMULATE_VERSION_MINOR - '.', SIMULATE_VERSION_MINOR, -# ifdef SIMULATE_VERSION_PATCH - '.', SIMULATE_VERSION_PATCH, -# ifdef SIMULATE_VERSION_TWEAK - '.', SIMULATE_VERSION_TWEAK, -# endif -# endif -# endif - ']','\0'}; -#endif - -/* Construct the string literal in pieces to prevent the source from - getting matched. Store it in a pointer rather than an array - because some compilers will just produce instructions to fill the - array rather than assigning a pointer to a static array. */ -char const* info_platform = "INFO" ":" "platform[" PLATFORM_ID "]"; -char const* info_arch = "INFO" ":" "arch[" ARCHITECTURE_ID "]"; - - - - -const char* info_language_dialect_default = "INFO" ":" "dialect_default[" -#if __cplusplus >= 201402L - "14" -#elif __cplusplus >= 201103L - "11" -#else - "98" -#endif -"]"; - -/*--------------------------------------------------------------------------*/ - -int main(int argc, char* argv[]) -{ - int require = 0; - require += info_compiler[argc]; - require += info_platform[argc]; -#ifdef COMPILER_VERSION_MAJOR - require += info_version[argc]; -#endif -#ifdef SIMULATE_ID - require += info_simulate[argc]; -#endif -#ifdef SIMULATE_VERSION_MAJOR - require += info_simulate_version[argc]; -#endif -#if defined(__CRAYXE) || defined(__CRAYXC) - require += info_cray[argc]; -#endif - require += info_language_dialect_default[argc]; - (void)argv; - return require; -} diff --git a/vnpy/api/ib/build/CMakeFiles/3.5.1/CompilerIdCXX/a.out b/vnpy/api/ib/build/CMakeFiles/3.5.1/CompilerIdCXX/a.out deleted file mode 100644 index b9caff71..00000000 Binary files a/vnpy/api/ib/build/CMakeFiles/3.5.1/CompilerIdCXX/a.out and /dev/null differ diff --git a/vnpy/api/ib/build/CMakeFiles/CMakeDirectoryInformation.cmake b/vnpy/api/ib/build/CMakeFiles/CMakeDirectoryInformation.cmake deleted file mode 100644 index 584e4405..00000000 --- a/vnpy/api/ib/build/CMakeFiles/CMakeDirectoryInformation.cmake +++ /dev/null @@ -1,16 +0,0 @@ -# CMAKE generated file: DO NOT EDIT! -# Generated by "Unix Makefiles" Generator, CMake Version 3.5 - -# Relative path conversion top directories. -set(CMAKE_RELATIVE_PATH_TOP_SOURCE "/home/vnpy/桌面/new/vn.ib") -set(CMAKE_RELATIVE_PATH_TOP_BINARY "/home/vnpy/桌面/new/vn.ib/build") - -# Force unix paths in dependencies. -set(CMAKE_FORCE_UNIX_PATHS 1) - - -# The C and CXX include file regular expressions for this directory. -set(CMAKE_C_INCLUDE_REGEX_SCAN "^.*$") -set(CMAKE_C_INCLUDE_REGEX_COMPLAIN "^$") -set(CMAKE_CXX_INCLUDE_REGEX_SCAN ${CMAKE_C_INCLUDE_REGEX_SCAN}) -set(CMAKE_CXX_INCLUDE_REGEX_COMPLAIN ${CMAKE_C_INCLUDE_REGEX_COMPLAIN}) diff --git a/vnpy/api/ib/build/CMakeFiles/CMakeError.log b/vnpy/api/ib/build/CMakeFiles/CMakeError.log deleted file mode 100644 index 56fde335..00000000 --- a/vnpy/api/ib/build/CMakeFiles/CMakeError.log +++ /dev/null @@ -1,55 +0,0 @@ -Determining if the pthread_create exist failed with the following output: -Change Dir: /home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp - -Run Build Command:"/usr/bin/make" "cmTC_deeb6/fast" -/usr/bin/make -f CMakeFiles/cmTC_deeb6.dir/build.make CMakeFiles/cmTC_deeb6.dir/build -make[1]: Entering directory '/home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp' -Building CXX object CMakeFiles/cmTC_deeb6.dir/CheckSymbolExists.cxx.o -/usr/bin/c++ -fPIC -std=c++11 -o CMakeFiles/cmTC_deeb6.dir/CheckSymbolExists.cxx.o -c /home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp/CheckSymbolExists.cxx -Linking CXX executable cmTC_deeb6 -/usr/bin/cmake -E cmake_link_script CMakeFiles/cmTC_deeb6.dir/link.txt --verbose=1 -/usr/bin/c++ -fPIC -std=c++11 CMakeFiles/cmTC_deeb6.dir/CheckSymbolExists.cxx.o -o cmTC_deeb6 -rdynamic -CMakeFiles/cmTC_deeb6.dir/CheckSymbolExists.cxx.o:在函数‘main’中: -CheckSymbolExists.cxx:(.text+0x1b):对‘pthread_create’未定义的引用 -collect2: error: ld returned 1 exit status -CMakeFiles/cmTC_deeb6.dir/build.make:97: recipe for target 'cmTC_deeb6' failed -make[1]: *** [cmTC_deeb6] Error 1 -make[1]: Leaving directory '/home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp' -Makefile:126: recipe for target 'cmTC_deeb6/fast' failed -make: *** [cmTC_deeb6/fast] Error 2 - -File /home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp/CheckSymbolExists.cxx: -/* */ -#include - -int main(int argc, char** argv) -{ - (void)argv; -#ifndef pthread_create - return ((int*)(&pthread_create))[argc]; -#else - (void)argc; - return 0; -#endif -} - -Determining if the function pthread_create exists in the pthreads failed with the following output: -Change Dir: /home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp - -Run Build Command:"/usr/bin/make" "cmTC_704c9/fast" -/usr/bin/make -f CMakeFiles/cmTC_704c9.dir/build.make CMakeFiles/cmTC_704c9.dir/build -make[1]: Entering directory '/home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp' -Building CXX object CMakeFiles/cmTC_704c9.dir/CheckFunctionExists.cxx.o -/usr/bin/c++ -fPIC -std=c++11 -DCHECK_FUNCTION_EXISTS=pthread_create -o CMakeFiles/cmTC_704c9.dir/CheckFunctionExists.cxx.o -c /home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CheckLibraryExists/CheckFunctionExists.cxx -Linking CXX executable cmTC_704c9 -/usr/bin/cmake -E cmake_link_script CMakeFiles/cmTC_704c9.dir/link.txt --verbose=1 -/usr/bin/c++ -fPIC -std=c++11 -DCHECK_FUNCTION_EXISTS=pthread_create CMakeFiles/cmTC_704c9.dir/CheckFunctionExists.cxx.o -o cmTC_704c9 -rdynamic -lpthreads -/usr/bin/ld: 找不到 -lpthreads -collect2: error: ld returned 1 exit status -CMakeFiles/cmTC_704c9.dir/build.make:97: recipe for target 'cmTC_704c9' failed -make[1]: *** [cmTC_704c9] Error 1 -make[1]: Leaving directory '/home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp' -Makefile:126: recipe for target 'cmTC_704c9/fast' failed -make: *** [cmTC_704c9/fast] Error 2 - - diff --git a/vnpy/api/ib/build/CMakeFiles/CMakeOutput.log b/vnpy/api/ib/build/CMakeFiles/CMakeOutput.log deleted file mode 100644 index 6eb2c134..00000000 --- a/vnpy/api/ib/build/CMakeFiles/CMakeOutput.log +++ /dev/null @@ -1,384 +0,0 @@ -The system is: Linux - 4.4.0-47-generic - x86_64 -Compiling the CXX compiler identification source file "CMakeCXXCompilerId.cpp" succeeded. -Compiler: /usr/bin/c++ -Build flags: -Id flags: - -The output was: -0 - - -Compilation of the CXX compiler identification source "CMakeCXXCompilerId.cpp" produced "a.out" - -The CXX compiler identification is GNU, found in "/home/vnpy/桌面/new/vn.ib/build/CMakeFiles/3.5.1/CompilerIdCXX/a.out" - -Determining if the CXX compiler works passed with the following output: -Change Dir: /home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp - -Run Build Command:"/usr/bin/make" "cmTC_37c25/fast" -/usr/bin/make -f CMakeFiles/cmTC_37c25.dir/build.make CMakeFiles/cmTC_37c25.dir/build -make[1]: Entering directory '/home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp' -Building CXX object CMakeFiles/cmTC_37c25.dir/testCXXCompiler.cxx.o -/usr/bin/c++ -o CMakeFiles/cmTC_37c25.dir/testCXXCompiler.cxx.o -c /home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp/testCXXCompiler.cxx -Linking CXX executable cmTC_37c25 -/usr/bin/cmake -E cmake_link_script CMakeFiles/cmTC_37c25.dir/link.txt --verbose=1 -/usr/bin/c++ CMakeFiles/cmTC_37c25.dir/testCXXCompiler.cxx.o -o cmTC_37c25 -rdynamic -make[1]: Leaving directory '/home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp' - - -Detecting CXX compiler ABI info compiled with the following output: -Change Dir: /home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp - -Run Build Command:"/usr/bin/make" "cmTC_40a3c/fast" -/usr/bin/make -f CMakeFiles/cmTC_40a3c.dir/build.make CMakeFiles/cmTC_40a3c.dir/build -make[1]: Entering directory '/home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp' -Building CXX object CMakeFiles/cmTC_40a3c.dir/CMakeCXXCompilerABI.cpp.o -/usr/bin/c++ -o CMakeFiles/cmTC_40a3c.dir/CMakeCXXCompilerABI.cpp.o -c /usr/share/cmake-3.5/Modules/CMakeCXXCompilerABI.cpp -Linking CXX executable cmTC_40a3c -/usr/bin/cmake -E cmake_link_script CMakeFiles/cmTC_40a3c.dir/link.txt --verbose=1 -/usr/bin/c++ -v CMakeFiles/cmTC_40a3c.dir/CMakeCXXCompilerABI.cpp.o -o cmTC_40a3c -rdynamic -Using built-in specs. -COLLECT_GCC=/usr/bin/c++ -COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/5/lto-wrapper -Target: x86_64-linux-gnu -Configured with: ../src/configure -v --with-pkgversion='Ubuntu 5.4.0-6ubuntu1~16.04.4' --with-bugurl=file:///usr/share/doc/gcc-5/README.Bugs --enable-languages=c,ada,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-5 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-5-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-5-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-5-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu -Thread model: posix -gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.4) -COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/5/:/usr/lib/gcc/x86_64-linux-gnu/5/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/5/:/usr/lib/gcc/x86_64-linux-gnu/ -LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/5/:/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/5/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/5/../../../:/lib/:/usr/lib/ -COLLECT_GCC_OPTIONS='-v' '-o' 'cmTC_40a3c' '-rdynamic' '-shared-libgcc' '-mtune=generic' '-march=x86-64' - /usr/lib/gcc/x86_64-linux-gnu/5/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/5/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/5/lto-wrapper -plugin-opt=-fresolution=/tmp/cctb0mbm.res -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc --sysroot=/ --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed -export-dynamic -dynamic-linker /lib64/ld-linux-x86-64.so.2 -z relro -o cmTC_40a3c /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/5/crtbegin.o -L/usr/lib/gcc/x86_64-linux-gnu/5 -L/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/5/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/5/../../.. CMakeFiles/cmTC_40a3c.dir/CMakeCXXCompilerABI.cpp.o -lstdc++ -lm -lgcc_s -lgcc -lc -lgcc_s -lgcc /usr/lib/gcc/x86_64-linux-gnu/5/crtend.o /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crtn.o -make[1]: Leaving directory '/home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp' - - -Parsed CXX implicit link information from above output: - link line regex: [^( *|.*[/\])(ld|([^/\]+-)?ld|collect2)[^/\]*( |$)] - ignore line: [Change Dir: /home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp] - ignore line: [] - ignore line: [Run Build Command:"/usr/bin/make" "cmTC_40a3c/fast"] - ignore line: [/usr/bin/make -f CMakeFiles/cmTC_40a3c.dir/build.make CMakeFiles/cmTC_40a3c.dir/build] - ignore line: [make[1]: Entering directory '/home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp'] - ignore line: [Building CXX object CMakeFiles/cmTC_40a3c.dir/CMakeCXXCompilerABI.cpp.o] - ignore line: [/usr/bin/c++ -o CMakeFiles/cmTC_40a3c.dir/CMakeCXXCompilerABI.cpp.o -c /usr/share/cmake-3.5/Modules/CMakeCXXCompilerABI.cpp] - ignore line: [Linking CXX executable cmTC_40a3c] - ignore line: [/usr/bin/cmake -E cmake_link_script CMakeFiles/cmTC_40a3c.dir/link.txt --verbose=1] - ignore line: [/usr/bin/c++ -v CMakeFiles/cmTC_40a3c.dir/CMakeCXXCompilerABI.cpp.o -o cmTC_40a3c -rdynamic ] - ignore line: [Using built-in specs.] - ignore line: [COLLECT_GCC=/usr/bin/c++] - ignore line: [COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/5/lto-wrapper] - ignore line: [Target: x86_64-linux-gnu] - ignore line: [Configured with: ../src/configure -v --with-pkgversion='Ubuntu 5.4.0-6ubuntu1~16.04.4' --with-bugurl=file:///usr/share/doc/gcc-5/README.Bugs --enable-languages=c,ada,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-5 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-5-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-5-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-5-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu] - ignore line: [Thread model: posix] - ignore line: [gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.4) ] - ignore line: [COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/5/:/usr/lib/gcc/x86_64-linux-gnu/5/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/5/:/usr/lib/gcc/x86_64-linux-gnu/] - ignore line: [LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/5/:/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/5/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/5/../../../:/lib/:/usr/lib/] - ignore line: [COLLECT_GCC_OPTIONS='-v' '-o' 'cmTC_40a3c' '-rdynamic' '-shared-libgcc' '-mtune=generic' '-march=x86-64'] - link line: [ /usr/lib/gcc/x86_64-linux-gnu/5/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/5/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/5/lto-wrapper -plugin-opt=-fresolution=/tmp/cctb0mbm.res -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc --sysroot=/ --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed -export-dynamic -dynamic-linker /lib64/ld-linux-x86-64.so.2 -z relro -o cmTC_40a3c /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/5/crtbegin.o -L/usr/lib/gcc/x86_64-linux-gnu/5 -L/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/5/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/5/../../.. CMakeFiles/cmTC_40a3c.dir/CMakeCXXCompilerABI.cpp.o -lstdc++ -lm -lgcc_s -lgcc -lc -lgcc_s -lgcc /usr/lib/gcc/x86_64-linux-gnu/5/crtend.o /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crtn.o] - arg [/usr/lib/gcc/x86_64-linux-gnu/5/collect2] ==> ignore - arg [-plugin] ==> ignore - arg [/usr/lib/gcc/x86_64-linux-gnu/5/liblto_plugin.so] ==> ignore - arg [-plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/5/lto-wrapper] ==> ignore - arg [-plugin-opt=-fresolution=/tmp/cctb0mbm.res] ==> ignore - arg [-plugin-opt=-pass-through=-lgcc_s] ==> ignore - arg [-plugin-opt=-pass-through=-lgcc] ==> ignore - arg [-plugin-opt=-pass-through=-lc] ==> ignore - arg [-plugin-opt=-pass-through=-lgcc_s] ==> ignore - arg [-plugin-opt=-pass-through=-lgcc] ==> ignore - arg [--sysroot=/] ==> ignore - arg [--build-id] ==> ignore - arg [--eh-frame-hdr] ==> ignore - arg [-m] ==> ignore - arg [elf_x86_64] ==> ignore - arg [--hash-style=gnu] ==> ignore - arg [--as-needed] ==> ignore - arg [-export-dynamic] ==> ignore - arg [-dynamic-linker] ==> ignore - arg [/lib64/ld-linux-x86-64.so.2] ==> ignore - arg [-zrelro] ==> ignore - arg [-o] ==> ignore - arg [cmTC_40a3c] ==> ignore - arg [/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o] ==> ignore - arg [/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o] ==> ignore - arg [/usr/lib/gcc/x86_64-linux-gnu/5/crtbegin.o] ==> ignore - arg [-L/usr/lib/gcc/x86_64-linux-gnu/5] ==> dir [/usr/lib/gcc/x86_64-linux-gnu/5] - arg [-L/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu] ==> dir [/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu] - arg [-L/usr/lib/gcc/x86_64-linux-gnu/5/../../../../lib] ==> dir [/usr/lib/gcc/x86_64-linux-gnu/5/../../../../lib] - arg [-L/lib/x86_64-linux-gnu] ==> dir [/lib/x86_64-linux-gnu] - arg [-L/lib/../lib] ==> dir [/lib/../lib] - arg [-L/usr/lib/x86_64-linux-gnu] ==> dir [/usr/lib/x86_64-linux-gnu] - arg [-L/usr/lib/../lib] ==> dir [/usr/lib/../lib] - arg [-L/usr/lib/gcc/x86_64-linux-gnu/5/../../..] ==> dir [/usr/lib/gcc/x86_64-linux-gnu/5/../../..] - arg [CMakeFiles/cmTC_40a3c.dir/CMakeCXXCompilerABI.cpp.o] ==> ignore - arg [-lstdc++] ==> lib [stdc++] - arg [-lm] ==> lib [m] - arg [-lgcc_s] ==> lib [gcc_s] - arg [-lgcc] ==> lib [gcc] - arg [-lc] ==> lib [c] - arg [-lgcc_s] ==> lib [gcc_s] - arg [-lgcc] ==> lib [gcc] - arg [/usr/lib/gcc/x86_64-linux-gnu/5/crtend.o] ==> ignore - arg [/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crtn.o] ==> ignore - remove lib [gcc_s] - remove lib [gcc] - remove lib [gcc_s] - remove lib [gcc] - collapse library dir [/usr/lib/gcc/x86_64-linux-gnu/5] ==> [/usr/lib/gcc/x86_64-linux-gnu/5] - collapse library dir [/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu] ==> [/usr/lib/x86_64-linux-gnu] - collapse library dir [/usr/lib/gcc/x86_64-linux-gnu/5/../../../../lib] ==> [/usr/lib] - collapse library dir [/lib/x86_64-linux-gnu] ==> [/lib/x86_64-linux-gnu] - collapse library dir [/lib/../lib] ==> [/lib] - collapse library dir [/usr/lib/x86_64-linux-gnu] ==> [/usr/lib/x86_64-linux-gnu] - collapse library dir [/usr/lib/../lib] ==> [/usr/lib] - collapse library dir [/usr/lib/gcc/x86_64-linux-gnu/5/../../..] ==> [/usr/lib] - implicit libs: [stdc++;m;c] - implicit dirs: [/usr/lib/gcc/x86_64-linux-gnu/5;/usr/lib/x86_64-linux-gnu;/usr/lib;/lib/x86_64-linux-gnu;/lib] - implicit fwks: [] - - - - -Detecting CXX [-std=c++14] compiler features compiled with the following output: -Change Dir: /home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp - -Run Build Command:"/usr/bin/make" "cmTC_ce700/fast" -/usr/bin/make -f CMakeFiles/cmTC_ce700.dir/build.make CMakeFiles/cmTC_ce700.dir/build -make[1]: Entering directory '/home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp' -Building CXX object CMakeFiles/cmTC_ce700.dir/feature_tests.cxx.o -/usr/bin/c++ -std=c++14 -o CMakeFiles/cmTC_ce700.dir/feature_tests.cxx.o -c /home/vnpy/桌面/new/vn.ib/build/CMakeFiles/feature_tests.cxx -Linking CXX executable cmTC_ce700 -/usr/bin/cmake -E cmake_link_script CMakeFiles/cmTC_ce700.dir/link.txt --verbose=1 -/usr/bin/c++ CMakeFiles/cmTC_ce700.dir/feature_tests.cxx.o -o cmTC_ce700 -rdynamic -make[1]: Leaving directory '/home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp' - - - Feature record: CXX_FEATURE:1cxx_aggregate_default_initializers - Feature record: CXX_FEATURE:1cxx_alias_templates - Feature record: CXX_FEATURE:1cxx_alignas - Feature record: CXX_FEATURE:1cxx_alignof - Feature record: CXX_FEATURE:1cxx_attributes - Feature record: CXX_FEATURE:1cxx_attribute_deprecated - Feature record: CXX_FEATURE:1cxx_auto_type - Feature record: CXX_FEATURE:1cxx_binary_literals - Feature record: CXX_FEATURE:1cxx_constexpr - Feature record: CXX_FEATURE:1cxx_contextual_conversions - Feature record: CXX_FEATURE:1cxx_decltype - Feature record: CXX_FEATURE:1cxx_decltype_auto - Feature record: CXX_FEATURE:1cxx_decltype_incomplete_return_types - Feature record: CXX_FEATURE:1cxx_default_function_template_args - Feature record: CXX_FEATURE:1cxx_defaulted_functions - Feature record: CXX_FEATURE:1cxx_defaulted_move_initializers - Feature record: CXX_FEATURE:1cxx_delegating_constructors - Feature record: CXX_FEATURE:1cxx_deleted_functions - Feature record: CXX_FEATURE:1cxx_digit_separators - Feature record: CXX_FEATURE:1cxx_enum_forward_declarations - Feature record: CXX_FEATURE:1cxx_explicit_conversions - Feature record: CXX_FEATURE:1cxx_extended_friend_declarations - Feature record: CXX_FEATURE:1cxx_extern_templates - Feature record: CXX_FEATURE:1cxx_final - Feature record: CXX_FEATURE:1cxx_func_identifier - Feature record: CXX_FEATURE:1cxx_generalized_initializers - Feature record: CXX_FEATURE:1cxx_generic_lambdas - Feature record: CXX_FEATURE:1cxx_inheriting_constructors - Feature record: CXX_FEATURE:1cxx_inline_namespaces - Feature record: CXX_FEATURE:1cxx_lambdas - Feature record: CXX_FEATURE:1cxx_lambda_init_captures - Feature record: CXX_FEATURE:1cxx_local_type_template_args - Feature record: CXX_FEATURE:1cxx_long_long_type - Feature record: CXX_FEATURE:1cxx_noexcept - Feature record: CXX_FEATURE:1cxx_nonstatic_member_init - Feature record: CXX_FEATURE:1cxx_nullptr - Feature record: CXX_FEATURE:1cxx_override - Feature record: CXX_FEATURE:1cxx_range_for - Feature record: CXX_FEATURE:1cxx_raw_string_literals - Feature record: CXX_FEATURE:1cxx_reference_qualified_functions - Feature record: CXX_FEATURE:1cxx_relaxed_constexpr - Feature record: CXX_FEATURE:1cxx_return_type_deduction - Feature record: CXX_FEATURE:1cxx_right_angle_brackets - Feature record: CXX_FEATURE:1cxx_rvalue_references - Feature record: CXX_FEATURE:1cxx_sizeof_member - Feature record: CXX_FEATURE:1cxx_static_assert - Feature record: CXX_FEATURE:1cxx_strong_enums - Feature record: CXX_FEATURE:1cxx_template_template_parameters - Feature record: CXX_FEATURE:1cxx_thread_local - Feature record: CXX_FEATURE:1cxx_trailing_return_types - Feature record: CXX_FEATURE:1cxx_unicode_literals - Feature record: CXX_FEATURE:1cxx_uniform_initialization - Feature record: CXX_FEATURE:1cxx_unrestricted_unions - Feature record: CXX_FEATURE:1cxx_user_literals - Feature record: CXX_FEATURE:1cxx_variable_templates - Feature record: CXX_FEATURE:1cxx_variadic_macros - Feature record: CXX_FEATURE:1cxx_variadic_templates - - -Detecting CXX [-std=c++11] compiler features compiled with the following output: -Change Dir: /home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp - -Run Build Command:"/usr/bin/make" "cmTC_1d72c/fast" -/usr/bin/make -f CMakeFiles/cmTC_1d72c.dir/build.make CMakeFiles/cmTC_1d72c.dir/build -make[1]: Entering directory '/home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp' -Building CXX object CMakeFiles/cmTC_1d72c.dir/feature_tests.cxx.o -/usr/bin/c++ -std=c++11 -o CMakeFiles/cmTC_1d72c.dir/feature_tests.cxx.o -c /home/vnpy/桌面/new/vn.ib/build/CMakeFiles/feature_tests.cxx -Linking CXX executable cmTC_1d72c -/usr/bin/cmake -E cmake_link_script CMakeFiles/cmTC_1d72c.dir/link.txt --verbose=1 -/usr/bin/c++ CMakeFiles/cmTC_1d72c.dir/feature_tests.cxx.o -o cmTC_1d72c -rdynamic -make[1]: Leaving directory '/home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp' - - - Feature record: CXX_FEATURE:0cxx_aggregate_default_initializers - Feature record: CXX_FEATURE:1cxx_alias_templates - Feature record: CXX_FEATURE:1cxx_alignas - Feature record: CXX_FEATURE:1cxx_alignof - Feature record: CXX_FEATURE:1cxx_attributes - Feature record: CXX_FEATURE:0cxx_attribute_deprecated - Feature record: CXX_FEATURE:1cxx_auto_type - Feature record: CXX_FEATURE:0cxx_binary_literals - Feature record: CXX_FEATURE:1cxx_constexpr - Feature record: CXX_FEATURE:0cxx_contextual_conversions - Feature record: CXX_FEATURE:1cxx_decltype - Feature record: CXX_FEATURE:0cxx_decltype_auto - Feature record: CXX_FEATURE:1cxx_decltype_incomplete_return_types - Feature record: CXX_FEATURE:1cxx_default_function_template_args - Feature record: CXX_FEATURE:1cxx_defaulted_functions - Feature record: CXX_FEATURE:1cxx_defaulted_move_initializers - Feature record: CXX_FEATURE:1cxx_delegating_constructors - Feature record: CXX_FEATURE:1cxx_deleted_functions - Feature record: CXX_FEATURE:0cxx_digit_separators - Feature record: CXX_FEATURE:1cxx_enum_forward_declarations - Feature record: CXX_FEATURE:1cxx_explicit_conversions - Feature record: CXX_FEATURE:1cxx_extended_friend_declarations - Feature record: CXX_FEATURE:1cxx_extern_templates - Feature record: CXX_FEATURE:1cxx_final - Feature record: CXX_FEATURE:1cxx_func_identifier - Feature record: CXX_FEATURE:1cxx_generalized_initializers - Feature record: CXX_FEATURE:0cxx_generic_lambdas - Feature record: CXX_FEATURE:1cxx_inheriting_constructors - Feature record: CXX_FEATURE:1cxx_inline_namespaces - Feature record: CXX_FEATURE:1cxx_lambdas - Feature record: CXX_FEATURE:0cxx_lambda_init_captures - Feature record: CXX_FEATURE:1cxx_local_type_template_args - Feature record: CXX_FEATURE:1cxx_long_long_type - Feature record: CXX_FEATURE:1cxx_noexcept - Feature record: CXX_FEATURE:1cxx_nonstatic_member_init - Feature record: CXX_FEATURE:1cxx_nullptr - Feature record: CXX_FEATURE:1cxx_override - Feature record: CXX_FEATURE:1cxx_range_for - Feature record: CXX_FEATURE:1cxx_raw_string_literals - Feature record: CXX_FEATURE:1cxx_reference_qualified_functions - Feature record: CXX_FEATURE:0cxx_relaxed_constexpr - Feature record: CXX_FEATURE:0cxx_return_type_deduction - Feature record: CXX_FEATURE:1cxx_right_angle_brackets - Feature record: CXX_FEATURE:1cxx_rvalue_references - Feature record: CXX_FEATURE:1cxx_sizeof_member - Feature record: CXX_FEATURE:1cxx_static_assert - Feature record: CXX_FEATURE:1cxx_strong_enums - Feature record: CXX_FEATURE:1cxx_template_template_parameters - Feature record: CXX_FEATURE:1cxx_thread_local - Feature record: CXX_FEATURE:1cxx_trailing_return_types - Feature record: CXX_FEATURE:1cxx_unicode_literals - Feature record: CXX_FEATURE:1cxx_uniform_initialization - Feature record: CXX_FEATURE:1cxx_unrestricted_unions - Feature record: CXX_FEATURE:1cxx_user_literals - Feature record: CXX_FEATURE:0cxx_variable_templates - Feature record: CXX_FEATURE:1cxx_variadic_macros - Feature record: CXX_FEATURE:1cxx_variadic_templates - - -Detecting CXX [-std=c++98] compiler features compiled with the following output: -Change Dir: /home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp - -Run Build Command:"/usr/bin/make" "cmTC_ee307/fast" -/usr/bin/make -f CMakeFiles/cmTC_ee307.dir/build.make CMakeFiles/cmTC_ee307.dir/build -make[1]: Entering directory '/home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp' -Building CXX object CMakeFiles/cmTC_ee307.dir/feature_tests.cxx.o -/usr/bin/c++ -std=c++98 -o CMakeFiles/cmTC_ee307.dir/feature_tests.cxx.o -c /home/vnpy/桌面/new/vn.ib/build/CMakeFiles/feature_tests.cxx -Linking CXX executable cmTC_ee307 -/usr/bin/cmake -E cmake_link_script CMakeFiles/cmTC_ee307.dir/link.txt --verbose=1 -/usr/bin/c++ CMakeFiles/cmTC_ee307.dir/feature_tests.cxx.o -o cmTC_ee307 -rdynamic -make[1]: Leaving directory '/home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp' - - - Feature record: CXX_FEATURE:0cxx_aggregate_default_initializers - Feature record: CXX_FEATURE:0cxx_alias_templates - Feature record: CXX_FEATURE:0cxx_alignas - Feature record: CXX_FEATURE:0cxx_alignof - Feature record: CXX_FEATURE:0cxx_attributes - Feature record: CXX_FEATURE:0cxx_attribute_deprecated - Feature record: CXX_FEATURE:0cxx_auto_type - Feature record: CXX_FEATURE:0cxx_binary_literals - Feature record: CXX_FEATURE:0cxx_constexpr - Feature record: CXX_FEATURE:0cxx_contextual_conversions - Feature record: CXX_FEATURE:0cxx_decltype - Feature record: CXX_FEATURE:0cxx_decltype_auto - Feature record: CXX_FEATURE:0cxx_decltype_incomplete_return_types - Feature record: CXX_FEATURE:0cxx_default_function_template_args - Feature record: CXX_FEATURE:0cxx_defaulted_functions - Feature record: CXX_FEATURE:0cxx_defaulted_move_initializers - Feature record: CXX_FEATURE:0cxx_delegating_constructors - Feature record: CXX_FEATURE:0cxx_deleted_functions - Feature record: CXX_FEATURE:0cxx_digit_separators - Feature record: CXX_FEATURE:0cxx_enum_forward_declarations - Feature record: CXX_FEATURE:0cxx_explicit_conversions - Feature record: CXX_FEATURE:0cxx_extended_friend_declarations - Feature record: CXX_FEATURE:0cxx_extern_templates - Feature record: CXX_FEATURE:0cxx_final - Feature record: CXX_FEATURE:0cxx_func_identifier - Feature record: CXX_FEATURE:0cxx_generalized_initializers - Feature record: CXX_FEATURE:0cxx_generic_lambdas - Feature record: CXX_FEATURE:0cxx_inheriting_constructors - Feature record: CXX_FEATURE:0cxx_inline_namespaces - Feature record: CXX_FEATURE:0cxx_lambdas - Feature record: CXX_FEATURE:0cxx_lambda_init_captures - Feature record: CXX_FEATURE:0cxx_local_type_template_args - Feature record: CXX_FEATURE:0cxx_long_long_type - Feature record: CXX_FEATURE:0cxx_noexcept - Feature record: CXX_FEATURE:0cxx_nonstatic_member_init - Feature record: CXX_FEATURE:0cxx_nullptr - Feature record: CXX_FEATURE:0cxx_override - Feature record: CXX_FEATURE:0cxx_range_for - Feature record: CXX_FEATURE:0cxx_raw_string_literals - Feature record: CXX_FEATURE:0cxx_reference_qualified_functions - Feature record: CXX_FEATURE:0cxx_relaxed_constexpr - Feature record: CXX_FEATURE:0cxx_return_type_deduction - Feature record: CXX_FEATURE:0cxx_right_angle_brackets - Feature record: CXX_FEATURE:0cxx_rvalue_references - Feature record: CXX_FEATURE:0cxx_sizeof_member - Feature record: CXX_FEATURE:0cxx_static_assert - Feature record: CXX_FEATURE:0cxx_strong_enums - Feature record: CXX_FEATURE:1cxx_template_template_parameters - Feature record: CXX_FEATURE:0cxx_thread_local - Feature record: CXX_FEATURE:0cxx_trailing_return_types - Feature record: CXX_FEATURE:0cxx_unicode_literals - Feature record: CXX_FEATURE:0cxx_uniform_initialization - Feature record: CXX_FEATURE:0cxx_unrestricted_unions - Feature record: CXX_FEATURE:0cxx_user_literals - Feature record: CXX_FEATURE:0cxx_variable_templates - Feature record: CXX_FEATURE:0cxx_variadic_macros - Feature record: CXX_FEATURE:0cxx_variadic_templates -Determining if the include file pthread.h exists passed with the following output: -Change Dir: /home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp - -Run Build Command:"/usr/bin/make" "cmTC_e6946/fast" -/usr/bin/make -f CMakeFiles/cmTC_e6946.dir/build.make CMakeFiles/cmTC_e6946.dir/build -make[1]: Entering directory '/home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp' -Building CXX object CMakeFiles/cmTC_e6946.dir/CheckIncludeFile.cxx.o -/usr/bin/c++ -fPIC -std=c++11 -o CMakeFiles/cmTC_e6946.dir/CheckIncludeFile.cxx.o -c /home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp/CheckIncludeFile.cxx -Linking CXX executable cmTC_e6946 -/usr/bin/cmake -E cmake_link_script CMakeFiles/cmTC_e6946.dir/link.txt --verbose=1 -/usr/bin/c++ -fPIC -std=c++11 CMakeFiles/cmTC_e6946.dir/CheckIncludeFile.cxx.o -o cmTC_e6946 -rdynamic -make[1]: Leaving directory '/home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp' - - -Determining if the function pthread_create exists in the pthread passed with the following output: -Change Dir: /home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp - -Run Build Command:"/usr/bin/make" "cmTC_239ea/fast" -/usr/bin/make -f CMakeFiles/cmTC_239ea.dir/build.make CMakeFiles/cmTC_239ea.dir/build -make[1]: Entering directory '/home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp' -Building CXX object CMakeFiles/cmTC_239ea.dir/CheckFunctionExists.cxx.o -/usr/bin/c++ -fPIC -std=c++11 -DCHECK_FUNCTION_EXISTS=pthread_create -o CMakeFiles/cmTC_239ea.dir/CheckFunctionExists.cxx.o -c /home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CheckLibraryExists/CheckFunctionExists.cxx -Linking CXX executable cmTC_239ea -/usr/bin/cmake -E cmake_link_script CMakeFiles/cmTC_239ea.dir/link.txt --verbose=1 -/usr/bin/c++ -fPIC -std=c++11 -DCHECK_FUNCTION_EXISTS=pthread_create CMakeFiles/cmTC_239ea.dir/CheckFunctionExists.cxx.o -o cmTC_239ea -rdynamic -lpthread -make[1]: Leaving directory '/home/vnpy/桌面/new/vn.ib/build/CMakeFiles/CMakeTmp' - - diff --git a/vnpy/api/ib/build/CMakeFiles/CheckLibraryExists/CheckFunctionExists.cxx b/vnpy/api/ib/build/CMakeFiles/CheckLibraryExists/CheckFunctionExists.cxx deleted file mode 100644 index fd29618c..00000000 --- a/vnpy/api/ib/build/CMakeFiles/CheckLibraryExists/CheckFunctionExists.cxx +++ /dev/null @@ -1,26 +0,0 @@ -#ifdef CHECK_FUNCTION_EXISTS - -#ifdef __cplusplus -extern "C" -#endif -char CHECK_FUNCTION_EXISTS(); -#ifdef __CLASSIC_C__ -int main(){ - int ac; - char*av[]; -#else -int main(int ac, char*av[]){ -#endif - CHECK_FUNCTION_EXISTS(); - if(ac > 1000) - { - return *av[0]; - } - return 0; -} - -#else /* CHECK_FUNCTION_EXISTS */ - -# error "CHECK_FUNCTION_EXISTS has to specify the function" - -#endif /* CHECK_FUNCTION_EXISTS */ diff --git a/vnpy/api/ib/build/CMakeFiles/Makefile.cmake b/vnpy/api/ib/build/CMakeFiles/Makefile.cmake deleted file mode 100644 index 3d95def3..00000000 --- a/vnpy/api/ib/build/CMakeFiles/Makefile.cmake +++ /dev/null @@ -1,108 +0,0 @@ -# CMAKE generated file: DO NOT EDIT! -# Generated by "Unix Makefiles" Generator, CMake Version 3.5 - -# The generator used is: -set(CMAKE_DEPENDS_GENERATOR "Unix Makefiles") - -# The top level Makefile was generated from the following files: -set(CMAKE_MAKEFILE_DEPENDS - "CMakeCache.txt" - "../CMakeLists.txt" - "CMakeFiles/3.5.1/CMakeCXXCompiler.cmake" - "CMakeFiles/3.5.1/CMakeSystem.cmake" - "CMakeFiles/CheckLibraryExists/CheckFunctionExists.cxx" - "CMakeFiles/feature_tests.cxx" - "/usr/share/cmake-3.5/Modules/CMakeCXXCompiler.cmake.in" - "/usr/share/cmake-3.5/Modules/CMakeCXXCompilerABI.cpp" - "/usr/share/cmake-3.5/Modules/CMakeCXXInformation.cmake" - "/usr/share/cmake-3.5/Modules/CMakeCommonLanguageInclude.cmake" - "/usr/share/cmake-3.5/Modules/CMakeCompilerIdDetection.cmake" - "/usr/share/cmake-3.5/Modules/CMakeConfigurableFile.in" - "/usr/share/cmake-3.5/Modules/CMakeDetermineCXXCompiler.cmake" - "/usr/share/cmake-3.5/Modules/CMakeDetermineCompileFeatures.cmake" - "/usr/share/cmake-3.5/Modules/CMakeDetermineCompiler.cmake" - "/usr/share/cmake-3.5/Modules/CMakeDetermineCompilerABI.cmake" - "/usr/share/cmake-3.5/Modules/CMakeDetermineCompilerId.cmake" - "/usr/share/cmake-3.5/Modules/CMakeDetermineSystem.cmake" - "/usr/share/cmake-3.5/Modules/CMakeFindBinUtils.cmake" - "/usr/share/cmake-3.5/Modules/CMakeFindDependencyMacro.cmake" - "/usr/share/cmake-3.5/Modules/CMakeGenericSystem.cmake" - "/usr/share/cmake-3.5/Modules/CMakeLanguageInformation.cmake" - "/usr/share/cmake-3.5/Modules/CMakeParseArguments.cmake" - "/usr/share/cmake-3.5/Modules/CMakeParseImplicitLinkInfo.cmake" - "/usr/share/cmake-3.5/Modules/CMakeSystem.cmake.in" - "/usr/share/cmake-3.5/Modules/CMakeSystemSpecificInformation.cmake" - "/usr/share/cmake-3.5/Modules/CMakeSystemSpecificInitialize.cmake" - "/usr/share/cmake-3.5/Modules/CMakeTestCXXCompiler.cmake" - "/usr/share/cmake-3.5/Modules/CMakeTestCompilerCommon.cmake" - "/usr/share/cmake-3.5/Modules/CMakeUnixFindMake.cmake" - "/usr/share/cmake-3.5/Modules/CheckFunctionExists.c" - "/usr/share/cmake-3.5/Modules/CheckIncludeFile.cxx.in" - "/usr/share/cmake-3.5/Modules/CheckIncludeFileCXX.cmake" - "/usr/share/cmake-3.5/Modules/CheckLibraryExists.cmake" - "/usr/share/cmake-3.5/Modules/CheckSymbolExists.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/ADSP-DetermineCompiler.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/ARMCC-DetermineCompiler.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/AppleClang-DetermineCompiler.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/Borland-DetermineCompiler.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/Clang-DetermineCompiler.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/Clang-DetermineCompilerInternal.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/Comeau-CXX-DetermineCompiler.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/Compaq-CXX-DetermineCompiler.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/Cray-DetermineCompiler.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/Embarcadero-DetermineCompiler.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/Fujitsu-DetermineCompiler.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/GHS-DetermineCompiler.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/GNU-CXX-FeatureTests.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/GNU-CXX.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/GNU-DetermineCompiler.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/GNU.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/HP-CXX-DetermineCompiler.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/IAR-DetermineCompiler.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/IBMCPP-CXX-DetermineVersionInternal.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/Intel-DetermineCompiler.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/MIPSpro-DetermineCompiler.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/MSVC-DetermineCompiler.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/OpenWatcom-DetermineCompiler.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/PGI-DetermineCompiler.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/PathScale-DetermineCompiler.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/SCO-DetermineCompiler.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/SunPro-CXX-DetermineCompiler.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/TI-DetermineCompiler.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/VisualAge-CXX-DetermineCompiler.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/Watcom-DetermineCompiler.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/XL-CXX-DetermineCompiler.cmake" - "/usr/share/cmake-3.5/Modules/Compiler/zOS-CXX-DetermineCompiler.cmake" - "/usr/share/cmake-3.5/Modules/FindBoost.cmake" - "/usr/share/cmake-3.5/Modules/FindPackageHandleStandardArgs.cmake" - "/usr/share/cmake-3.5/Modules/FindPackageMessage.cmake" - "/usr/share/cmake-3.5/Modules/FindThreads.cmake" - "/usr/share/cmake-3.5/Modules/Internal/FeatureTesting.cmake" - "/usr/share/cmake-3.5/Modules/MultiArchCross.cmake" - "/usr/share/cmake-3.5/Modules/Platform/Linux-CXX.cmake" - "/usr/share/cmake-3.5/Modules/Platform/Linux-GNU-CXX.cmake" - "/usr/share/cmake-3.5/Modules/Platform/Linux-GNU.cmake" - "/usr/share/cmake-3.5/Modules/Platform/Linux.cmake" - "/usr/share/cmake-3.5/Modules/Platform/UnixPaths.cmake" - ) - -# The corresponding makefile is: -set(CMAKE_MAKEFILE_OUTPUTS - "Makefile" - "CMakeFiles/cmake.check_cache" - ) - -# Byproducts of CMake generate step: -set(CMAKE_MAKEFILE_PRODUCTS - "CMakeFiles/3.5.1/CMakeSystem.cmake" - "CMakeFiles/3.5.1/CMakeCXXCompiler.cmake" - "CMakeFiles/3.5.1/CMakeCXXCompiler.cmake" - "CMakeFiles/CheckLibraryExists/CheckFunctionExists.cxx" - "CMakeFiles/CheckLibraryExists/CheckFunctionExists.cxx" - "CMakeFiles/CMakeDirectoryInformation.cmake" - ) - -# Dependency information for all targets: -set(CMAKE_DEPEND_INFO_FILES - "CMakeFiles/vnib.dir/DependInfo.cmake" - ) diff --git a/vnpy/api/ib/build/CMakeFiles/Makefile2 b/vnpy/api/ib/build/CMakeFiles/Makefile2 deleted file mode 100644 index b21f6988..00000000 --- a/vnpy/api/ib/build/CMakeFiles/Makefile2 +++ /dev/null @@ -1,108 +0,0 @@ -# CMAKE generated file: DO NOT EDIT! -# Generated by "Unix Makefiles" Generator, CMake Version 3.5 - -# Default target executed when no arguments are given to make. -default_target: all - -.PHONY : default_target - -# The main recursive all target -all: - -.PHONY : all - -# The main recursive preinstall target -preinstall: - -.PHONY : preinstall - -#============================================================================= -# Special targets provided by cmake. - -# Disable implicit rules so canonical targets will work. -.SUFFIXES: - - -# Remove some rules from gmake that .SUFFIXES does not remove. -SUFFIXES = - -.SUFFIXES: .hpux_make_needs_suffix_list - - -# Suppress display of executed commands. -$(VERBOSE).SILENT: - - -# A target that is always out of date. -cmake_force: - -.PHONY : cmake_force - -#============================================================================= -# Set environment variables for the build. - -# The shell in which to execute make rules. -SHELL = /bin/sh - -# The CMake executable. -CMAKE_COMMAND = /usr/bin/cmake - -# The command to remove a file. -RM = /usr/bin/cmake -E remove -f - -# Escaping for special characters. -EQUALS = = - -# The top-level source directory on which CMake was run. -CMAKE_SOURCE_DIR = /home/vnpy/桌面/new/vn.ib - -# The top-level build directory on which CMake was run. -CMAKE_BINARY_DIR = /home/vnpy/桌面/new/vn.ib/build - -#============================================================================= -# Target rules for target CMakeFiles/vnib.dir - -# All Build rule for target. -CMakeFiles/vnib.dir/all: - $(MAKE) -f CMakeFiles/vnib.dir/build.make CMakeFiles/vnib.dir/depend - $(MAKE) -f CMakeFiles/vnib.dir/build.make CMakeFiles/vnib.dir/build - @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --progress-dir=/home/vnpy/桌面/new/vn.ib/build/CMakeFiles --progress-num=1,2 "Built target vnib" -.PHONY : CMakeFiles/vnib.dir/all - -# Include target in all. -all: CMakeFiles/vnib.dir/all - -.PHONY : all - -# Build rule for subdir invocation for target. -CMakeFiles/vnib.dir/rule: cmake_check_build_system - $(CMAKE_COMMAND) -E cmake_progress_start /home/vnpy/桌面/new/vn.ib/build/CMakeFiles 2 - $(MAKE) -f CMakeFiles/Makefile2 CMakeFiles/vnib.dir/all - $(CMAKE_COMMAND) -E cmake_progress_start /home/vnpy/桌面/new/vn.ib/build/CMakeFiles 0 -.PHONY : CMakeFiles/vnib.dir/rule - -# Convenience name for target. -vnib: CMakeFiles/vnib.dir/rule - -.PHONY : vnib - -# clean rule for target. -CMakeFiles/vnib.dir/clean: - $(MAKE) -f CMakeFiles/vnib.dir/build.make CMakeFiles/vnib.dir/clean -.PHONY : CMakeFiles/vnib.dir/clean - -# clean rule for target. -clean: CMakeFiles/vnib.dir/clean - -.PHONY : clean - -#============================================================================= -# Special targets to cleanup operation of make. - -# Special rule to run CMake to check the build system integrity. -# No rule that depends on this can have commands that come from listfiles -# because they might be regenerated. -cmake_check_build_system: - $(CMAKE_COMMAND) -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0 -.PHONY : cmake_check_build_system - diff --git a/vnpy/api/ib/build/CMakeFiles/TargetDirectories.txt b/vnpy/api/ib/build/CMakeFiles/TargetDirectories.txt deleted file mode 100644 index 91621b39..00000000 --- a/vnpy/api/ib/build/CMakeFiles/TargetDirectories.txt +++ /dev/null @@ -1,3 +0,0 @@ -/home/vnpy/桌面/new/vn.ib/build/CMakeFiles/edit_cache.dir -/home/vnpy/桌面/new/vn.ib/build/CMakeFiles/rebuild_cache.dir -/home/vnpy/桌面/new/vn.ib/build/CMakeFiles/vnib.dir diff --git a/vnpy/api/ib/build/CMakeFiles/cmake.check_cache b/vnpy/api/ib/build/CMakeFiles/cmake.check_cache deleted file mode 100644 index 3dccd731..00000000 --- a/vnpy/api/ib/build/CMakeFiles/cmake.check_cache +++ /dev/null @@ -1 +0,0 @@ -# This file is generated by cmake for dependency checking of the CMakeCache.txt file diff --git a/vnpy/api/ib/build/CMakeFiles/feature_tests.bin b/vnpy/api/ib/build/CMakeFiles/feature_tests.bin deleted file mode 100644 index 4c5b3641..00000000 Binary files a/vnpy/api/ib/build/CMakeFiles/feature_tests.bin and /dev/null differ diff --git a/vnpy/api/ib/build/CMakeFiles/feature_tests.cxx b/vnpy/api/ib/build/CMakeFiles/feature_tests.cxx deleted file mode 100644 index b93418c6..00000000 --- a/vnpy/api/ib/build/CMakeFiles/feature_tests.cxx +++ /dev/null @@ -1,405 +0,0 @@ - - const char features[] = {"\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 500 && __cplusplus >= 201402L -"1" -#else -"0" -#endif -"cxx_aggregate_default_initializers\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 407 && __cplusplus >= 201103L -"1" -#else -"0" -#endif -"cxx_alias_templates\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 408 && __cplusplus >= 201103L -"1" -#else -"0" -#endif -"cxx_alignas\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 408 && __cplusplus >= 201103L -"1" -#else -"0" -#endif -"cxx_alignof\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 408 && __cplusplus >= 201103L -"1" -#else -"0" -#endif -"cxx_attributes\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 409 && __cplusplus > 201103L -"1" -#else -"0" -#endif -"cxx_attribute_deprecated\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 404 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_auto_type\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 409 && __cplusplus > 201103L -"1" -#else -"0" -#endif -"cxx_binary_literals\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 406 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_constexpr\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 409 && __cplusplus > 201103L -"1" -#else -"0" -#endif -"cxx_contextual_conversions\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 404 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_decltype\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 409 && __cplusplus > 201103L -"1" -#else -"0" -#endif -"cxx_decltype_auto\n" -"CXX_FEATURE:" -#if ((__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) >= 40801) && __cplusplus >= 201103L -"1" -#else -"0" -#endif -"cxx_decltype_incomplete_return_types\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 404 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_default_function_template_args\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 404 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_defaulted_functions\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 406 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_defaulted_move_initializers\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 407 && __cplusplus >= 201103L -"1" -#else -"0" -#endif -"cxx_delegating_constructors\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 404 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_deleted_functions\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 409 && __cplusplus > 201103L -"1" -#else -"0" -#endif -"cxx_digit_separators\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 406 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_enum_forward_declarations\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 405 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_explicit_conversions\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 407 && __cplusplus >= 201103L -"1" -#else -"0" -#endif -"cxx_extended_friend_declarations\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 404 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_extern_templates\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 407 && __cplusplus >= 201103L -"1" -#else -"0" -#endif -"cxx_final\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 404 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_func_identifier\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 404 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_generalized_initializers\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 409 && __cplusplus > 201103L -"1" -#else -"0" -#endif -"cxx_generic_lambdas\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 408 && __cplusplus >= 201103L -"1" -#else -"0" -#endif -"cxx_inheriting_constructors\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 404 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_inline_namespaces\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 405 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_lambdas\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 409 && __cplusplus > 201103L -"1" -#else -"0" -#endif -"cxx_lambda_init_captures\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 405 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_local_type_template_args\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 404 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_long_long_type\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 406 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_noexcept\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 407 && __cplusplus >= 201103L -"1" -#else -"0" -#endif -"cxx_nonstatic_member_init\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 406 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_nullptr\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 407 && __cplusplus >= 201103L -"1" -#else -"0" -#endif -"cxx_override\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 406 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_range_for\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 405 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_raw_string_literals\n" -"CXX_FEATURE:" -#if ((__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) >= 40801) && __cplusplus >= 201103L -"1" -#else -"0" -#endif -"cxx_reference_qualified_functions\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 500 && __cplusplus >= 201402L -"1" -#else -"0" -#endif -"cxx_relaxed_constexpr\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 409 && __cplusplus > 201103L -"1" -#else -"0" -#endif -"cxx_return_type_deduction\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 404 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_right_angle_brackets\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 404 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_rvalue_references\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 404 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_sizeof_member\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 404 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_static_assert\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 404 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_strong_enums\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 404 && __cplusplus -"1" -#else -"0" -#endif -"cxx_template_template_parameters\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 408 && __cplusplus >= 201103L -"1" -#else -"0" -#endif -"cxx_thread_local\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 404 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_trailing_return_types\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 404 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_unicode_literals\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 404 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_uniform_initialization\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 406 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_unrestricted_unions\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 407 && __cplusplus >= 201103L -"1" -#else -"0" -#endif -"cxx_user_literals\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 500 && __cplusplus >= 201402L -"1" -#else -"0" -#endif -"cxx_variable_templates\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 404 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_variadic_macros\n" -"CXX_FEATURE:" -#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 404 && (__cplusplus >= 201103L || (defined(__GXX_EXPERIMENTAL_CXX0X__) && __GXX_EXPERIMENTAL_CXX0X__)) -"1" -#else -"0" -#endif -"cxx_variadic_templates\n" - -}; - -int main(int argc, char** argv) { (void)argv; return features[argc]; } diff --git a/vnpy/api/ib/build/CMakeFiles/progress.marks b/vnpy/api/ib/build/CMakeFiles/progress.marks deleted file mode 100644 index 0cfbf088..00000000 --- a/vnpy/api/ib/build/CMakeFiles/progress.marks +++ /dev/null @@ -1 +0,0 @@ -2 diff --git a/vnpy/api/ib/build/CMakeFiles/vnib.dir/CXX.includecache b/vnpy/api/ib/build/CMakeFiles/vnib.dir/CXX.includecache deleted file mode 100644 index 57162ad0..00000000 --- a/vnpy/api/ib/build/CMakeFiles/vnib.dir/CXX.includecache +++ /dev/null @@ -1,198 +0,0 @@ -#IncludeRegexLine: ^[ ]*#[ ]*(include|import)[ ]*[<"]([^">]+)([">]) - -#IncludeRegexScan: ^.*$ - -#IncludeRegexComplain: ^$ - -#IncludeRegexTransform: - -.././ibapi/linux/client/CommissionReport.h - -.././ibapi/linux/client/CommonDefs.h - -.././ibapi/linux/client/Contract.h -TagValue.h -.././ibapi/linux/client/TagValue.h - -.././ibapi/linux/client/EClient.h -memory -- -string -- -vector -- -iosfwd -- -CommonDefs.h -.././ibapi/linux/client/CommonDefs.h -TagValue.h -.././ibapi/linux/client/TagValue.h - -.././ibapi/linux/client/EClientMsgSink.h - -.././ibapi/linux/client/EClientSocket.h -EClient.h -.././ibapi/linux/client/EClient.h -EClientMsgSink.h -.././ibapi/linux/client/EClientMsgSink.h -ESocket.h -.././ibapi/linux/client/ESocket.h - -.././ibapi/linux/client/EDecoder.h -Contract.h -.././ibapi/linux/client/Contract.h - -.././ibapi/linux/client/EMutex.h -StdAfx.h -.././ibapi/linux/client/StdAfx.h - -.././ibapi/linux/client/EReader.h -StdAfx.h -.././ibapi/linux/client/StdAfx.h -EDecoder.h -.././ibapi/linux/client/EDecoder.h -EMutex.h -.././ibapi/linux/client/EMutex.h -EReaderOSSignal.h -.././ibapi/linux/client/EReaderOSSignal.h - -.././ibapi/linux/client/EReaderOSSignal.h -EReaderSignal.h -.././ibapi/linux/client/EReaderSignal.h -StdAfx.h -.././ibapi/linux/client/StdAfx.h -stdexcept -- - -.././ibapi/linux/client/EReaderSignal.h - -.././ibapi/linux/client/ESocket.h -ETransport.h -.././ibapi/linux/client/ETransport.h - -.././ibapi/linux/client/ETransport.h - -.././ibapi/linux/client/EWrapper.h -CommonDefs.h -.././ibapi/linux/client/CommonDefs.h -SoftDollarTier.h -.././ibapi/linux/client/SoftDollarTier.h -string -- -set -- - -.././ibapi/linux/client/Execution.h - -.././ibapi/linux/client/IExternalizable.h -ios -- - -.././ibapi/linux/client/Order.h -TagValue.h -.././ibapi/linux/client/TagValue.h -OrderCondition.h -.././ibapi/linux/client/OrderCondition.h -SoftDollarTier.h -.././ibapi/linux/client/SoftDollarTier.h -float.h -- -limits.h -- - -.././ibapi/linux/client/OrderCondition.h -IExternalizable.h -.././ibapi/linux/client/IExternalizable.h -shared_ptr.h -.././ibapi/linux/client/shared_ptr.h - -.././ibapi/linux/client/OrderState.h -Order.h -.././ibapi/linux/client/Order.h - -.././ibapi/linux/client/ScannerSubscription.h -float.h -- -limits.h -- - -.././ibapi/linux/client/SoftDollarTier.h - -.././ibapi/linux/client/StdAfx.h -WinSock2.h -- -Windows.h -- -unistd.h -- -pthread.h -- -string -- -deque -- -vector -- -algorithm -- - -.././ibapi/linux/client/TagValue.h -shared_ptr.h -.././ibapi/linux/client/shared_ptr.h -string -- -vector -- - -.././ibapi/linux/client/shared_ptr.h - -/home/vnpy/桌面/new/vn.ib/vnib/vnib/vnib.cpp -StdAfx.h -/home/vnpy/桌面/new/vn.ib/vnib/vnib/StdAfx.h -vnib.h -/home/vnpy/桌面/new/vn.ib/vnib/vnib/vnib.h -Contract.h -/home/vnpy/桌面/new/vn.ib/vnib/vnib/Contract.h -OrderState.h -/home/vnpy/桌面/new/vn.ib/vnib/vnib/OrderState.h -Execution.h -/home/vnpy/桌面/new/vn.ib/vnib/vnib/Execution.h -CommissionReport.h -/home/vnpy/桌面/new/vn.ib/vnib/vnib/CommissionReport.h -ScannerSubscription.h -/home/vnpy/桌面/new/vn.ib/vnib/vnib/ScannerSubscription.h -TagValue.h -/home/vnpy/桌面/new/vn.ib/vnib/vnib/TagValue.h -Order.h -/home/vnpy/桌面/new/vn.ib/vnib/vnib/Order.h - -/home/vnpy/桌面/new/vn.ib/vnib/vnib/vnib.h -StdAfx.h -/home/vnpy/桌面/new/vn.ib/vnib/vnib/StdAfx.h -string -- -boost/python/module.hpp -- -boost/python/def.hpp -- -boost/python/object.hpp -- -boost/python/register_ptr_to_python.hpp -- -boost/python/suite/indexing/vector_indexing_suite.hpp -- -boost/python.hpp -- -boost/thread.hpp -- -boost/bind.hpp -- -boost/shared_ptr.hpp -- -EWrapper.h -/home/vnpy/桌面/new/vn.ib/vnib/vnib/EWrapper.h -EClientSocket.h -/home/vnpy/桌面/new/vn.ib/vnib/vnib/EClientSocket.h -EReader.h -/home/vnpy/桌面/new/vn.ib/vnib/vnib/EReader.h - diff --git a/vnpy/api/ib/build/CMakeFiles/vnib.dir/DependInfo.cmake b/vnpy/api/ib/build/CMakeFiles/vnib.dir/DependInfo.cmake deleted file mode 100644 index 793abe4d..00000000 --- a/vnpy/api/ib/build/CMakeFiles/vnib.dir/DependInfo.cmake +++ /dev/null @@ -1,29 +0,0 @@ -# The set of languages for which implicit dependencies are needed: -set(CMAKE_DEPENDS_LANGUAGES - "CXX" - ) -# The set of files for implicit dependencies of each language: -set(CMAKE_DEPENDS_CHECK_CXX - "/home/vnpy/桌面/new/vn.ib/vnib/vnib/vnib.cpp" "/home/vnpy/桌面/new/vn.ib/build/CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o" - ) -set(CMAKE_CXX_COMPILER_ID "GNU") - -# Preprocessor definitions for this target. -set(CMAKE_TARGET_DEFINITIONS_CXX - "BUILD_IB" - "USE_64BITS" - ) - -# The include file search paths: -set(CMAKE_CXX_TARGET_INCLUDE_PATH - ".././ibapi/linux/client" - "../IB_PATH" - "/usr/include/python2.7" - ) - -# Targets to which this target links. -set(CMAKE_TARGET_LINKED_INFO_FILES - ) - -# Fortran module output directory. -set(CMAKE_Fortran_TARGET_MODULE_DIR "") diff --git a/vnpy/api/ib/build/CMakeFiles/vnib.dir/build.make b/vnpy/api/ib/build/CMakeFiles/vnib.dir/build.make deleted file mode 100644 index db4cfe94..00000000 --- a/vnpy/api/ib/build/CMakeFiles/vnib.dir/build.make +++ /dev/null @@ -1,122 +0,0 @@ -# CMAKE generated file: DO NOT EDIT! -# Generated by "Unix Makefiles" Generator, CMake Version 3.5 - -# Delete rule output on recipe failure. -.DELETE_ON_ERROR: - - -#============================================================================= -# Special targets provided by cmake. - -# Disable implicit rules so canonical targets will work. -.SUFFIXES: - - -# Remove some rules from gmake that .SUFFIXES does not remove. -SUFFIXES = - -.SUFFIXES: .hpux_make_needs_suffix_list - - -# Suppress display of executed commands. -$(VERBOSE).SILENT: - - -# A target that is always out of date. -cmake_force: - -.PHONY : cmake_force - -#============================================================================= -# Set environment variables for the build. - -# The shell in which to execute make rules. -SHELL = /bin/sh - -# The CMake executable. -CMAKE_COMMAND = /usr/bin/cmake - -# The command to remove a file. -RM = /usr/bin/cmake -E remove -f - -# Escaping for special characters. -EQUALS = = - -# The top-level source directory on which CMake was run. -CMAKE_SOURCE_DIR = /home/vnpy/桌面/new/vn.ib - -# The top-level build directory on which CMake was run. -CMAKE_BINARY_DIR = /home/vnpy/桌面/new/vn.ib/build - -# Include any dependencies generated for this target. -include CMakeFiles/vnib.dir/depend.make - -# Include the progress variables for this target. -include CMakeFiles/vnib.dir/progress.make - -# Include the compile flags for this target's objects. -include CMakeFiles/vnib.dir/flags.make - -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o: CMakeFiles/vnib.dir/flags.make -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o: ../vnib/vnib/vnib.cpp - @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green --progress-dir=/home/vnpy/桌面/new/vn.ib/build/CMakeFiles --progress-num=$(CMAKE_PROGRESS_1) "Building CXX object CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o" - /usr/bin/c++ $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS) -o CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o -c /home/vnpy/桌面/new/vn.ib/vnib/vnib/vnib.cpp - -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.i: cmake_force - @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green "Preprocessing CXX source to CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.i" - /usr/bin/c++ $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS) -E /home/vnpy/桌面/new/vn.ib/vnib/vnib/vnib.cpp > CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.i - -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.s: cmake_force - @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green "Compiling CXX source to assembly CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.s" - /usr/bin/c++ $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS) -S /home/vnpy/桌面/new/vn.ib/vnib/vnib/vnib.cpp -o CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.s - -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o.requires: - -.PHONY : CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o.requires - -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o.provides: CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o.requires - $(MAKE) -f CMakeFiles/vnib.dir/build.make CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o.provides.build -.PHONY : CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o.provides - -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o.provides.build: CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o - - -# Object files for target vnib -vnib_OBJECTS = \ -"CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o" - -# External object files for target vnib -vnib_EXTERNAL_OBJECTS = - -lib/vnib.so: CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o -lib/vnib.so: CMakeFiles/vnib.dir/build.make -lib/vnib.so: ../ibapi/linux/build/lib/twsapi.so -lib/vnib.so: /usr/lib/x86_64-linux-gnu/libboost_python.so -lib/vnib.so: /usr/lib/x86_64-linux-gnu/libboost_thread.so -lib/vnib.so: /usr/lib/x86_64-linux-gnu/libboost_date_time.so -lib/vnib.so: /usr/lib/x86_64-linux-gnu/libboost_system.so -lib/vnib.so: /usr/lib/x86_64-linux-gnu/libboost_chrono.so -lib/vnib.so: /usr/lib/x86_64-linux-gnu/libboost_atomic.so -lib/vnib.so: /usr/lib/x86_64-linux-gnu/libpthread.so -lib/vnib.so: /home/vnpy/anaconda2/lib/libpython2.7.so -lib/vnib.so: CMakeFiles/vnib.dir/link.txt - @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green --bold --progress-dir=/home/vnpy/桌面/new/vn.ib/build/CMakeFiles --progress-num=$(CMAKE_PROGRESS_2) "Linking CXX shared library lib/vnib.so" - $(CMAKE_COMMAND) -E cmake_link_script CMakeFiles/vnib.dir/link.txt --verbose=$(VERBOSE) - -# Rule to build all files generated by this target. -CMakeFiles/vnib.dir/build: lib/vnib.so - -.PHONY : CMakeFiles/vnib.dir/build - -CMakeFiles/vnib.dir/requires: CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o.requires - -.PHONY : CMakeFiles/vnib.dir/requires - -CMakeFiles/vnib.dir/clean: - $(CMAKE_COMMAND) -P CMakeFiles/vnib.dir/cmake_clean.cmake -.PHONY : CMakeFiles/vnib.dir/clean - -CMakeFiles/vnib.dir/depend: - cd /home/vnpy/桌面/new/vn.ib/build && $(CMAKE_COMMAND) -E cmake_depends "Unix Makefiles" /home/vnpy/桌面/new/vn.ib /home/vnpy/桌面/new/vn.ib /home/vnpy/桌面/new/vn.ib/build /home/vnpy/桌面/new/vn.ib/build /home/vnpy/桌面/new/vn.ib/build/CMakeFiles/vnib.dir/DependInfo.cmake --color=$(COLOR) -.PHONY : CMakeFiles/vnib.dir/depend - diff --git a/vnpy/api/ib/build/CMakeFiles/vnib.dir/cmake_clean.cmake b/vnpy/api/ib/build/CMakeFiles/vnib.dir/cmake_clean.cmake deleted file mode 100644 index 882648d8..00000000 --- a/vnpy/api/ib/build/CMakeFiles/vnib.dir/cmake_clean.cmake +++ /dev/null @@ -1,10 +0,0 @@ -file(REMOVE_RECURSE - "CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o" - "lib/vnib.pdb" - "lib/vnib.so" -) - -# Per-language clean rules from dependency scanning. -foreach(lang CXX) - include(CMakeFiles/vnib.dir/cmake_clean_${lang}.cmake OPTIONAL) -endforeach() diff --git a/vnpy/api/ib/build/CMakeFiles/vnib.dir/depend.internal b/vnpy/api/ib/build/CMakeFiles/vnib.dir/depend.internal deleted file mode 100644 index b4529621..00000000 --- a/vnpy/api/ib/build/CMakeFiles/vnib.dir/depend.internal +++ /dev/null @@ -1,30 +0,0 @@ -# CMAKE generated file: DO NOT EDIT! -# Generated by "Unix Makefiles" Generator, CMake Version 3.5 - -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o - .././ibapi/linux/client/CommissionReport.h - .././ibapi/linux/client/CommonDefs.h - .././ibapi/linux/client/Contract.h - .././ibapi/linux/client/EClient.h - .././ibapi/linux/client/EClientMsgSink.h - .././ibapi/linux/client/EClientSocket.h - .././ibapi/linux/client/EDecoder.h - .././ibapi/linux/client/EMutex.h - .././ibapi/linux/client/EReader.h - .././ibapi/linux/client/EReaderOSSignal.h - .././ibapi/linux/client/EReaderSignal.h - .././ibapi/linux/client/ESocket.h - .././ibapi/linux/client/ETransport.h - .././ibapi/linux/client/EWrapper.h - .././ibapi/linux/client/Execution.h - .././ibapi/linux/client/IExternalizable.h - .././ibapi/linux/client/Order.h - .././ibapi/linux/client/OrderCondition.h - .././ibapi/linux/client/OrderState.h - .././ibapi/linux/client/ScannerSubscription.h - .././ibapi/linux/client/SoftDollarTier.h - .././ibapi/linux/client/StdAfx.h - .././ibapi/linux/client/TagValue.h - .././ibapi/linux/client/shared_ptr.h - /home/vnpy/桌面/new/vn.ib/vnib/vnib/vnib.cpp - /home/vnpy/桌面/new/vn.ib/vnib/vnib/vnib.h diff --git a/vnpy/api/ib/build/CMakeFiles/vnib.dir/depend.make b/vnpy/api/ib/build/CMakeFiles/vnib.dir/depend.make deleted file mode 100644 index cb336b8a..00000000 --- a/vnpy/api/ib/build/CMakeFiles/vnib.dir/depend.make +++ /dev/null @@ -1,30 +0,0 @@ -# CMAKE generated file: DO NOT EDIT! -# Generated by "Unix Makefiles" Generator, CMake Version 3.5 - -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o: .././ibapi/linux/client/CommissionReport.h -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o: .././ibapi/linux/client/CommonDefs.h -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o: .././ibapi/linux/client/Contract.h -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o: .././ibapi/linux/client/EClient.h -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o: .././ibapi/linux/client/EClientMsgSink.h -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o: .././ibapi/linux/client/EClientSocket.h -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o: .././ibapi/linux/client/EDecoder.h -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o: .././ibapi/linux/client/EMutex.h -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o: .././ibapi/linux/client/EReader.h -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o: .././ibapi/linux/client/EReaderOSSignal.h -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o: .././ibapi/linux/client/EReaderSignal.h -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o: .././ibapi/linux/client/ESocket.h -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o: .././ibapi/linux/client/ETransport.h -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o: .././ibapi/linux/client/EWrapper.h -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o: .././ibapi/linux/client/Execution.h -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o: .././ibapi/linux/client/IExternalizable.h -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o: .././ibapi/linux/client/Order.h -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o: .././ibapi/linux/client/OrderCondition.h -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o: .././ibapi/linux/client/OrderState.h -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o: .././ibapi/linux/client/ScannerSubscription.h -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o: .././ibapi/linux/client/SoftDollarTier.h -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o: .././ibapi/linux/client/StdAfx.h -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o: .././ibapi/linux/client/TagValue.h -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o: .././ibapi/linux/client/shared_ptr.h -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o: ../vnib/vnib/vnib.cpp -CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o: ../vnib/vnib/vnib.h - diff --git a/vnpy/api/ib/build/CMakeFiles/vnib.dir/flags.make b/vnpy/api/ib/build/CMakeFiles/vnib.dir/flags.make deleted file mode 100644 index bae3cb48..00000000 --- a/vnpy/api/ib/build/CMakeFiles/vnib.dir/flags.make +++ /dev/null @@ -1,10 +0,0 @@ -# CMAKE generated file: DO NOT EDIT! -# Generated by "Unix Makefiles" Generator, CMake Version 3.5 - -# compile CXX with /usr/bin/c++ -CXX_FLAGS = -fPIC -std=c++11 -O3 -DNDEBUG -fPIC - -CXX_DEFINES = -DBUILD_IB -DUSE_64BITS -Dvnib_EXPORTS - -CXX_INCLUDES = -I/home/vnpy/桌面/new/vn.ib/./ibapi/linux/client -I/home/vnpy/桌面/new/vn.ib/IB_PATH -I/usr/include/python2.7 - diff --git a/vnpy/api/ib/build/CMakeFiles/vnib.dir/link.txt b/vnpy/api/ib/build/CMakeFiles/vnib.dir/link.txt deleted file mode 100644 index 60fd0138..00000000 --- a/vnpy/api/ib/build/CMakeFiles/vnib.dir/link.txt +++ /dev/null @@ -1 +0,0 @@ -/usr/bin/c++ -fPIC -fPIC -std=c++11 -O3 -DNDEBUG -shared -Wl,-soname,vnib.so -o lib/vnib.so CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o ../ibapi/linux/build/lib/twsapi.so -lboost_python -lboost_thread -lboost_date_time -lboost_system -lboost_chrono -lboost_atomic -lpthread /home/vnpy/anaconda2/lib/libpython2.7.so -Wl,-rpath,/home/vnpy/桌面/new/vn.ib/ibapi/linux/build/lib:/home/vnpy/anaconda2/lib diff --git a/vnpy/api/ib/build/CMakeFiles/vnib.dir/progress.make b/vnpy/api/ib/build/CMakeFiles/vnib.dir/progress.make deleted file mode 100644 index abadeb0c..00000000 --- a/vnpy/api/ib/build/CMakeFiles/vnib.dir/progress.make +++ /dev/null @@ -1,3 +0,0 @@ -CMAKE_PROGRESS_1 = 1 -CMAKE_PROGRESS_2 = 2 - diff --git a/vnpy/api/ib/build/CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o b/vnpy/api/ib/build/CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o deleted file mode 100644 index 267c381a..00000000 Binary files a/vnpy/api/ib/build/CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o and /dev/null differ diff --git a/vnpy/api/ib/build/Makefile b/vnpy/api/ib/build/Makefile deleted file mode 100644 index fddffe1b..00000000 --- a/vnpy/api/ib/build/Makefile +++ /dev/null @@ -1,178 +0,0 @@ -# CMAKE generated file: DO NOT EDIT! -# Generated by "Unix Makefiles" Generator, CMake Version 3.5 - -# Default target executed when no arguments are given to make. -default_target: all - -.PHONY : default_target - -# Allow only one "make -f Makefile2" at a time, but pass parallelism. -.NOTPARALLEL: - - -#============================================================================= -# Special targets provided by cmake. - -# Disable implicit rules so canonical targets will work. -.SUFFIXES: - - -# Remove some rules from gmake that .SUFFIXES does not remove. -SUFFIXES = - -.SUFFIXES: .hpux_make_needs_suffix_list - - -# Suppress display of executed commands. -$(VERBOSE).SILENT: - - -# A target that is always out of date. -cmake_force: - -.PHONY : cmake_force - -#============================================================================= -# Set environment variables for the build. - -# The shell in which to execute make rules. -SHELL = /bin/sh - -# The CMake executable. -CMAKE_COMMAND = /usr/bin/cmake - -# The command to remove a file. -RM = /usr/bin/cmake -E remove -f - -# Escaping for special characters. -EQUALS = = - -# The top-level source directory on which CMake was run. -CMAKE_SOURCE_DIR = /home/vnpy/桌面/new/vn.ib - -# The top-level build directory on which CMake was run. -CMAKE_BINARY_DIR = /home/vnpy/桌面/new/vn.ib/build - -#============================================================================= -# Targets provided globally by CMake. - -# Special rule for the target edit_cache -edit_cache: - @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "No interactive CMake dialog available..." - /usr/bin/cmake -E echo No\ interactive\ CMake\ dialog\ available. -.PHONY : edit_cache - -# Special rule for the target edit_cache -edit_cache/fast: edit_cache - -.PHONY : edit_cache/fast - -# Special rule for the target rebuild_cache -rebuild_cache: - @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running CMake to regenerate build system..." - /usr/bin/cmake -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) -.PHONY : rebuild_cache - -# Special rule for the target rebuild_cache -rebuild_cache/fast: rebuild_cache - -.PHONY : rebuild_cache/fast - -# The main all target -all: cmake_check_build_system - $(CMAKE_COMMAND) -E cmake_progress_start /home/vnpy/桌面/new/vn.ib/build/CMakeFiles /home/vnpy/桌面/new/vn.ib/build/CMakeFiles/progress.marks - $(MAKE) -f CMakeFiles/Makefile2 all - $(CMAKE_COMMAND) -E cmake_progress_start /home/vnpy/桌面/new/vn.ib/build/CMakeFiles 0 -.PHONY : all - -# The main clean target -clean: - $(MAKE) -f CMakeFiles/Makefile2 clean -.PHONY : clean - -# The main clean target -clean/fast: clean - -.PHONY : clean/fast - -# Prepare targets for installation. -preinstall: all - $(MAKE) -f CMakeFiles/Makefile2 preinstall -.PHONY : preinstall - -# Prepare targets for installation. -preinstall/fast: - $(MAKE) -f CMakeFiles/Makefile2 preinstall -.PHONY : preinstall/fast - -# clear depends -depend: - $(CMAKE_COMMAND) -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 1 -.PHONY : depend - -#============================================================================= -# Target rules for targets named vnib - -# Build rule for target. -vnib: cmake_check_build_system - $(MAKE) -f CMakeFiles/Makefile2 vnib -.PHONY : vnib - -# fast build rule for target. -vnib/fast: - $(MAKE) -f CMakeFiles/vnib.dir/build.make CMakeFiles/vnib.dir/build -.PHONY : vnib/fast - -vnib/vnib/vnib.o: vnib/vnib/vnib.cpp.o - -.PHONY : vnib/vnib/vnib.o - -# target to build an object file -vnib/vnib/vnib.cpp.o: - $(MAKE) -f CMakeFiles/vnib.dir/build.make CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.o -.PHONY : vnib/vnib/vnib.cpp.o - -vnib/vnib/vnib.i: vnib/vnib/vnib.cpp.i - -.PHONY : vnib/vnib/vnib.i - -# target to preprocess a source file -vnib/vnib/vnib.cpp.i: - $(MAKE) -f CMakeFiles/vnib.dir/build.make CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.i -.PHONY : vnib/vnib/vnib.cpp.i - -vnib/vnib/vnib.s: vnib/vnib/vnib.cpp.s - -.PHONY : vnib/vnib/vnib.s - -# target to generate assembly for a file -vnib/vnib/vnib.cpp.s: - $(MAKE) -f CMakeFiles/vnib.dir/build.make CMakeFiles/vnib.dir/vnib/vnib/vnib.cpp.s -.PHONY : vnib/vnib/vnib.cpp.s - -# Help Target -help: - @echo "The following are some of the valid targets for this Makefile:" - @echo "... all (the default if no target is provided)" - @echo "... clean" - @echo "... depend" - @echo "... edit_cache" - @echo "... rebuild_cache" - @echo "... vnib" - @echo "... vnib/vnib/vnib.o" - @echo "... vnib/vnib/vnib.i" - @echo "... vnib/vnib/vnib.s" -.PHONY : help - - - -#============================================================================= -# Special targets to cleanup operation of make. - -# Special rule to run CMake to check the build system integrity. -# No rule that depends on this can have commands that come from listfiles -# because they might be regenerated. -cmake_check_build_system: - $(CMAKE_COMMAND) -H$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0 -.PHONY : cmake_check_build_system - diff --git a/vnpy/api/ib/build/cmake_install.cmake b/vnpy/api/ib/build/cmake_install.cmake deleted file mode 100644 index 2de8dd80..00000000 --- a/vnpy/api/ib/build/cmake_install.cmake +++ /dev/null @@ -1,44 +0,0 @@ -# Install script for directory: /home/vnpy/桌面/new/vn.ib - -# Set the install prefix -if(NOT DEFINED CMAKE_INSTALL_PREFIX) - set(CMAKE_INSTALL_PREFIX "/usr/local") -endif() -string(REGEX REPLACE "/$" "" CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") - -# Set the install configuration name. -if(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME) - if(BUILD_TYPE) - string(REGEX REPLACE "^[^A-Za-z0-9_]+" "" - CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}") - else() - set(CMAKE_INSTALL_CONFIG_NAME "Release") - endif() - message(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"") -endif() - -# Set the component getting installed. -if(NOT CMAKE_INSTALL_COMPONENT) - if(COMPONENT) - message(STATUS "Install component: \"${COMPONENT}\"") - set(CMAKE_INSTALL_COMPONENT "${COMPONENT}") - else() - set(CMAKE_INSTALL_COMPONENT) - endif() -endif() - -# Install shared libraries without execute permission? -if(NOT DEFINED CMAKE_INSTALL_SO_NO_EXE) - set(CMAKE_INSTALL_SO_NO_EXE "1") -endif() - -if(CMAKE_INSTALL_COMPONENT) - set(CMAKE_INSTALL_MANIFEST "install_manifest_${CMAKE_INSTALL_COMPONENT}.txt") -else() - set(CMAKE_INSTALL_MANIFEST "install_manifest.txt") -endif() - -string(REPLACE ";" "\n" CMAKE_INSTALL_MANIFEST_CONTENT - "${CMAKE_INSTALL_MANIFEST_FILES}") -file(WRITE "/home/vnpy/桌面/new/vn.ib/build/${CMAKE_INSTALL_MANIFEST}" - "${CMAKE_INSTALL_MANIFEST_CONTENT}") diff --git a/vnpy/api/ib/build/lib/vnib.so b/vnpy/api/ib/build/lib/vnib.so deleted file mode 100644 index ead8808b..00000000 Binary files a/vnpy/api/ib/build/lib/vnib.so and /dev/null differ diff --git a/vnpy/api/ib/test/test.py b/vnpy/api/ib/test/test.py index 6f4ab062..acb39a12 100644 --- a/vnpy/api/ib/test/test.py +++ b/vnpy/api/ib/test/test.py @@ -1,15 +1,18 @@ # encoding: UTF-8 +from __future__ import print_function import sys from time import sleep +from six.moves import input + from vnib import IbApi ######################################################################## class TestApi(IbApi): - print sys._getframe().f_code.co_name + print(sys._getframe().f_code.co_name) #---------------------------------------------------------------------- def __init__(self): @@ -18,283 +21,283 @@ class TestApi(IbApi): #---------------------------------------------------------------------- def nextValidId(self, orderId): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def currentTime(self, time): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def connectAck(self): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def error(self, id_, errorCode, errorString): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def accountSummary(self, reqId, account, tag, value, curency): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def accountSummaryEnd(self, reqId): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def tickPrice(self, tickerId, field, price, canAutoExecute): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def tickSize(self, tickerId, field, size): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def tickOptionComputation(self, tickerId, tickType, impliedVol, delta, optPrice, pvDividend, gamma, vega, theta, undPrice): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def tickGeneric(self, tickerId, tickType, value): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def tickString(self, tickerId, tickType, value): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def tickEFP(self, tickerId, tickType, basisPoints, formattedBasisPoints, totalDividends, holdDays, futureLastTradeDate, dividendImpact, dividendsToLastTradeDate): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def orderStatus(self, orderId, status, filled, remaining, avgFillPrice, permId, parentId, lastFillPrice, clientId, whyHeld): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def openOrder(self, orderId, contract, order, orderState): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def openOrderEnd(self): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def winError(self, str_, lastError): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def connectionClosed(self): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def updateAccountValue(self, key, val, currency, accountName): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def updatePortfolio(self, contract, position, marketPrice, marketValue, averageCost, unrealizedPNL, realizedPNL, accountName): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def updateAccountTime(self, timeStamp): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def accountDownloadEnd(self, accountName): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def contractDetails(self, reqId, contractDetails): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def bondContractDetails(self, reqId, contractDetails): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def contractDetailsEnd(self, reqId): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def execDetails(self, reqId, contract, execution): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def execDetailsEnd(self, reqId): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def updateMktDepth(self, id_, position, operation, side, price, size): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def updateMktDepthL2(self, id_, position, marketMaker, operation, side, price, size): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def updateNewsBulletin(self, msgId, msgType, newsMessage, originExch): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def managedAccounts(self, accountsList): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def receiveFA(self, pFaDataType, cxml): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def historicalData(self, reqId, date, open_, high, low, close, volume, barCount, WAP, hasGaps): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def scannerParameters(self, xml): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def scannerData(self, reqId, rank, contractDetails, distance, benchmark, projection, legsStr): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def scannerDataEnd(self, reqId): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def realtimeBar(self, reqId, time, open_, high, low, close, volume, wap, count): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def fundamentalData(self, reqId, data): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def deltaNeutralValidation(self, reqId, underComp): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def tickSnapshotEnd(self, reqId): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def marketDataType(self, reqId, marketDataType): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def commissionReport(self, commissionReport): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def position(self, account, contract, position, avgCost): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def positionEnd(self): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def verifyMessageAPI(self, apiData): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def verifyCompleted(self, isSuccessful, errorText): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def displayGroupList(self, reqId, groups): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def displayGroupUpdated(self, reqId, contractInfo): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def verifyAndAuthMessageAPI(self, apiData, xyzChallange): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def verifyAndAuthCompleted(self, isSuccessful, errorText): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def positionMulti(self, reqId, account, modelCode, contract, pos, avgCost): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def positionMultiEnd(self, reqId): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def accountUpdateMulti(self, reqId, account, modelCode, key, value, currency): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def accountUpdateMultiEnd(self, reqId): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def securityDefinitionOptionalParameter(self, reqId, exchange, underlyingConId, tradingClass, multiplier, expirations, strikes): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def securityDefinitionOptionalParameterEnd(self, reqId): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def softDollarTiers(self, reqId, tiers): - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) @@ -302,14 +305,14 @@ if __name__ == '__main__': api = TestApi() n = api.eConnect('127.0.0.1', 7497, 123, False) - print n + print(n) #t = api.TwsConnectionTime() #print t # sleep(1) - print 'req time' + print('req time') api.reqCurrentTime() # @@ -319,6 +322,6 @@ if __name__ == '__main__': #print 'disconnect' #api.eDisconnect() - raw_input() + input() + - \ No newline at end of file diff --git a/vnpy/api/lbank/__init__.py b/vnpy/api/lbank/__init__.py index 2e68766f..080efd11 100644 --- a/vnpy/api/lbank/__init__.py +++ b/vnpy/api/lbank/__init__.py @@ -1,4 +1,4 @@ # encoding: UTF-8 from __future__ import absolute_import -from .vnlbank import LbankApi \ No newline at end of file +from .vnlbank import LbankRestApi, LbankWebsocketApi \ No newline at end of file diff --git a/vnpy/api/lbank/test.py b/vnpy/api/lbank/test.py index 463830e1..5391f15d 100644 --- a/vnpy/api/lbank/test.py +++ b/vnpy/api/lbank/test.py @@ -1,50 +1,67 @@ -# encoding: utf-8 - -from __future__ import absolute_import -from time import time, sleep +# encoding: UTF-8 from six.moves import input +from time import time -from .vnlbank import LbankApi +from vnlbank import LbankRestApi, LbankWebsocketApi + +API_KEY = '132a36ce-ad1c-409a-b48c-09b7877ae49b' +SECRET_KEY = '319320BF875297E7F4050E1195B880E8' + + +#---------------------------------------------------------------------- +def restTest(): + """""" + # 创建API对象并初始化 + api = LbankRestApi() + api.init(API_KEY, SECRET_KEY) + api.start(1) + + # 测试 + #api.addReq('GET', '/currencyPairs.do', {}, api.onData) + #api.addReq('GET', '/accuracy.do', {}, api.onData) + + #api.addReq('GET', '/ticker.do', {'symbol': 'eth_btc'}, api.onData) + #api.addReq('GET', '/depth.do', {'symbol': 'eth_btc', 'size': '5'}, api.onData) + + #api.addReq('post', '/user_info.do', {}, api.onData) + + req = { + 'symbol': 'sc_btc', + 'current_page': '1', + 'page_length': '50' + } + api.addReq('POST', '/orders_info_no_deal.do', req, api.onData) + + # 阻塞 + input() + + +#---------------------------------------------------------------------- +def wsTest(): + """""" + ws = LbankWebsocketApi() + ws.start() + + channels = [ + 'lh_sub_spot_eth_btc_depth_20', + 'lh_sub_spot_eth_btc_trades', + 'lh_sub_spot_eth_btc_ticker' + ] + + for channel in channels: + req = { + 'event': 'addChannel', + 'channel': channel + } + ws.sendReq(req) + + + # 阻塞 + input() if __name__ == '__main__': - apiKey = '' - secretKey = '' + restTest() - # 创建API对象并初始化 - api = LbankApi() - api.DEBUG = True - api.init(apiKey, secretKey, 2) - - # 查询行情 - api.getTicker('btc_cny') - - # 查询深度 - api.getDepth('btc_cny', '60', '1') - - # 查询历史成交 - #api.getTrades('btc_cny', '1', str(int(time()))) - - # 查询K线 - #t = int(time()) - #sleep(300) - #api.getKline('btc_cny', '20', 'minute1', str(t)) - - # 查询账户 - #api.getUserInfo() - - # 发送委托 - #api.createOrder('btc_cny', 'sell', '8000', '0.001') - - # 撤单 - #api.cancelOrder('btc_cny', '725bd2da-73aa-419f-8090-f68488074e8f') - - # 查询委托 - #api.getOrdersInfo('btc_cny', '725bd2da-73aa-419f-8090-f68488074e8f') - - # 查询委托历史 - #api.getOrdersInfoHistory('btc_cny', '0', '1', '100') - - # 阻塞 - input() + #wsTest() \ No newline at end of file diff --git a/vnpy/api/lbank/vnlbank.py b/vnpy/api/lbank/vnlbank.py index d0f9158a..1d04ecf6 100644 --- a/vnpy/api/lbank/vnlbank.py +++ b/vnpy/api/lbank/vnlbank.py @@ -3,306 +3,223 @@ from __future__ import print_function import urllib import hashlib +import ssl +import json +import traceback import requests from Queue import Queue, Empty from threading import Thread -from time import sleep +from multiprocessing.dummy import Pool +from time import time + +import websocket -API_ROOT ="https://api.lbank.info/v1/" - -FUNCTION_TICKER = ('ticker.do', 'get') -FUNCTION_DEPTH = ('depth.do', 'get') -FUNCTION_TRADES = ('trades.do', 'get') -FUNCTION_KLINE = ('kline.do', 'get') - -FUNCTION_USERINFO = ('user_info.do', 'post') -FUNCTION_CREATEORDER = ('create_order.do', 'post') -FUNCTION_CANCELORDER = ('cancel_order.do', 'post') -FUNCTION_ORDERSINFO = ('orders_info.do', 'post') -FUNCTION_ORDERSINFOHISTORY = ('orders_info_history.do', 'post') +REST_HOST = "https://api.lbank.info/v1" +WEBSOCKET_HOST = 'ws://api.lbank.info/ws' -#---------------------------------------------------------------------- -def signature(params, secretKey): - """生成签名""" - params = sorted(params.iteritems(), key=lambda d:d[0], reverse=False) - params.append(('secret_key', secretKey)) - message = urllib.urlencode(params) - - m = hashlib.md5() - m.update(message) - m.digest() - - sig=m.hexdigest() - return sig - ######################################################################## -class LbankApi(object): +class LbankRestApi(object): """""" - DEBUG = True - + #---------------------------------------------------------------------- def __init__(self): """Constructor""" self.apiKey = '' self.secretKey = '' - self.interval = 1 # 每次请求的间隔等待 self.active = False # API工作状态 self.reqID = 0 # 请求编号 - self.reqQueue = Queue() # 请求队列 - self.reqThread = Thread(target=self.processQueue) # 请求处理线程 - + self.queue = Queue() # 请求队列 + self.pool = None # 线程池 + self.sessionDict = {} # 连接池 + #---------------------------------------------------------------------- - def init(self, apiKey, secretKey, interval): + def init(self, apiKey, secretKey): """初始化""" self.apiKey = apiKey self.secretKey = secretKey - self.interval = interval - - self.active = True - self.reqThread.start() #---------------------------------------------------------------------- - def exit(self): + def start(self, n=10): + """""" + if self.active: + return + + self.active = True + self.pool = Pool(n) + self.pool.map_async(self.run, range(n)) + + #---------------------------------------------------------------------- + def close(self): """退出""" self.active = False - if self.reqThread.isAlive(): - self.reqThread.join() + if self.pool: + self.pool.close() + self.pool.join() #---------------------------------------------------------------------- - def processRequest(self, req): + def processReq(self, req, i): """处理请求""" # 读取方法和参数 - api, method = req['function'] - params = req['params'] - url = API_ROOT + api + method, path, params, callback, reqID = req + url = REST_HOST + path # 在参数中增加必须的字段 params['api_key'] = self.apiKey - - # 添加签名 - sign = signature(params, self.secretKey) - params['sign'] = sign + params['sign'] = self.generateSignature(params) # 发送请求 payload = urllib.urlencode(params) - - r = requests.request(method, url, params=payload) - if r.status_code == 200: - data = r.json() - return data - else: - return None + + try: + # 使用会话重用技术,请求延时降低80% + session = self.sessionDict[i] + resp = session.request(method, url, params=payload) + #resp = requests.request(method, url, params=payload) + + code = resp.status_code + d = resp.json() + + if code == 200: + callback(d, reqID) + else: + self.onError(code, str(d)) + + except Exception as e: + self.onError(type(e), e.message) #---------------------------------------------------------------------- - def processQueue(self): - """处理请求队列中的请求""" + def run(self, i): + """连续运行""" + self.sessionDict[i] = requests.Session() + while self.active: try: - req = self.reqQueue.get(block=True, timeout=1) # 获取请求的阻塞为一秒 - callback = req['callback'] - reqID = req['reqID'] - - data = self.processRequest(req) - - # 请求失败 - if data is None: - error = u'请求失败' - self.onError(error, req, reqID) - elif 'error_code' in data: - error = u'请求出错,错误代码:%s' % data['error_code'] - self.onError(error, req, reqID) - # 请求成功 - else: - if self.DEBUG: - print(callback.__name__) - callback(data, req, reqID) - - # 流控等待 - sleep(self.interval) - + req = self.queue.get(block=True, timeout=1) # 获取请求的阻塞为一秒 + self.processReq(req, i) except Empty: pass #---------------------------------------------------------------------- - def sendRequest(self, function, params, callback): + def addReq(self, method, path, params, callback): """发送请求""" # 请求编号加1 self.reqID += 1 # 生成请求字典并放入队列中 - req = {} - req['function'] = function - req['params'] = params - req['callback'] = callback - req['reqID'] = self.reqID - self.reqQueue.put(req) + req = (method, path, params, callback, self.reqID) + self.queue.put(req) # 返回请求编号 return self.reqID - + #---------------------------------------------------------------------- - def onError(self, error, req, reqID): + def generateSignature(self, params): + """生成签名""" + params = sorted(params.iteritems(), key=lambda d:d[0], reverse=False) + params.append(('secret_key', self.secretKey)) + message = urllib.urlencode(params) + + m = hashlib.md5() + m.update(message) + m.digest() + + sig = m.hexdigest() + return sig + + #---------------------------------------------------------------------- + def onError(self, code, msg): """错误推送""" - print(error, req, reqID) + print(code, msg) - ############################################### - # 行情接口 - ############################################### + #---------------------------------------------------------------------- + def onData(self, data, reqID): + """""" + print(data, reqID) + + +######################################################################## +class LbankWebsocketApi(object): + """Websocket API""" + + #---------------------------------------------------------------------- + def __init__(self): + """Constructor""" + self.ws = None + self.thread = None + self.active = False #---------------------------------------------------------------------- - def getTicker(self, symbol): - """查询行情""" - function = FUNCTION_TICKER - params = {'symbol': symbol} - callback = self.onGetTicker - return self.sendRequest(function, params, callback) - - # ---------------------------------------------------------------------- - def getDepth(self, symbol, size, merge): - """查询深度""" - function = FUNCTION_DEPTH - params = { - 'symbol': symbol, - 'size': size, - 'mege': merge - } - callback = self.onGetDepth - return self.sendRequest(function, params, callback) - - # ---------------------------------------------------------------------- - def getTrades(self, symbol, size, time): - """查询历史成交""" - function = FUNCTION_TRADES - params = { - 'symbol': symbol, - 'size': size, - 'time': time - } - callback = self.onGetTrades - return self.sendRequest(function, params, callback) - - # ---------------------------------------------------------------------- - def getKline(self, symbol, size, type_, time): - """查询K线""" - function = FUNCTION_KLINE - params = { - 'symbol': symbol, - 'size': size, - 'type': type_, - 'time': time - } - callback = self.onGetKline - return self.sendRequest(function, params, callback) - + def start(self): + """启动""" + self.ws = websocket.create_connection(WEBSOCKET_HOST, + sslopt={'cert_reqs': ssl.CERT_NONE}) + + self.active = True + self.thread = Thread(target=self.run) + self.thread.start() + + self.onConnect() + #---------------------------------------------------------------------- - def onGetTicker(self, data, req, reqID): - """查询行情回调""" - print(data, reqID) + def reconnect(self): + """重连""" + self.ws = websocket.create_connection(WEBSOCKET_HOST, + sslopt={'cert_reqs': ssl.CERT_NONE}) + + self.onConnect() + + #---------------------------------------------------------------------- + def run(self): + """运行""" + while self.active: + try: + stream = self.ws.recv() + data = json.loads(stream) + self.onData(data) + except: + msg = traceback.format_exc() + print(msg) + self.onError(msg) + self.reconnect() + + #---------------------------------------------------------------------- + def close(self): + """关闭""" + self.active = False + + if self.thread: + self.ws.shutdown() + self.thread.join() + + #---------------------------------------------------------------------- + def onConnect(self): + """连接回调""" + print('connected') + + #---------------------------------------------------------------------- + def onData(self, data): + """数据回调""" + print('-' * 30) + l = data.keys() + l.sort() + for k in l: + print(k, data[k]) + + #---------------------------------------------------------------------- + def onError(self, msg): + """错误回调""" + print(msg) + + #---------------------------------------------------------------------- + def sendReq(self, req): + """发出请求""" + self.ws.send(json.dumps(req)) - # ---------------------------------------------------------------------- - def onGetDepth(self, data, req, reqID): - """查询深度回调""" - print(data, reqID) - - # ---------------------------------------------------------------------- - def onGetTrades(self, data, req, reqID): - """查询历史成交""" - print(data, reqID) - - # ---------------------------------------------------------------------- - def onGetKline(self, data, req, reqID): - """查询K线回报""" - print(data, reqID) - - ############################################### - # 交易接口 - ############################################### - - # ---------------------------------------------------------------------- - def getUserInfo(self): - """查询账户信息""" - function = FUNCTION_USERINFO - params = {} - callback = self.onGetUserInfo - return self.sendRequest(function, params, callback) - - # ---------------------------------------------------------------------- - def createOrder(self, symbol, type_, price, amount): - """发送委托""" - function = FUNCTION_CREATEORDER - params = { - 'symbol': symbol, - 'type': type_, - 'price': price, - 'amount': amount - } - callback = self.onCreateOrder - return self.sendRequest(function, params, callback) - - # ---------------------------------------------------------------------- - def cancelOrder(self, symbol, orderId): - """撤单""" - function = FUNCTION_CANCELORDER - params = { - 'symbol': symbol, - 'order_id': orderId - } - callback = self.onCancelOrder - return self.sendRequest(function, params, callback) - - # ---------------------------------------------------------------------- - def getOrdersInfo(self, symbol, orderId): - """查询委托""" - function = FUNCTION_ORDERSINFO - params = { - 'symbol': symbol, - 'order_id': orderId - } - callback = self.onGetOrdersInfo - return self.sendRequest(function, params, callback) - - # ---------------------------------------------------------------------- - def getOrdersInfoHistory(self, symbol, status, currentPage, pageLength): - """撤单""" - function = FUNCTION_ORDERSINFOHISTORY - params = { - 'symbol': symbol, - 'status': status, - 'current_page': currentPage, - 'page_length': pageLength - } - callback = self.onGetOrdersInfoHistory - return self.sendRequest(function, params, callback) - - # ---------------------------------------------------------------------- - def onGetUserInfo(self, data, req, reqID): - """查询账户信息""" - print(data, reqID) - - # ---------------------------------------------------------------------- - def onCreateOrder(self, data, req, reqID): - """委托回报""" - print(data, reqID) - - # ---------------------------------------------------------------------- - def onCancelOrder(self, data, req, reqID): - """撤单回报""" - print(data, reqID) - - # ---------------------------------------------------------------------- - def onGetOrdersInfo(self, data, req, reqID): - """查询委托回报""" - print(data, reqID) - - # ---------------------------------------------------------------------- - def onGetOrdersInfoHistory(self, data, req, reqID): - """撤单回报""" - print(data, reqID) diff --git a/vnpy/api/oanda/README.md b/vnpy/api/oanda/README.md deleted file mode 100644 index 01522ddd..00000000 --- a/vnpy/api/oanda/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# vn.oanda - -### 简介 -OANDA外汇交易接口,基于REST API开发,实现了以下功能: - -1. 发送、修改、撤销委托 - -2. 查询委托、持仓(按照每笔成交算)、汇总持仓(按照单一货币对算)、资金、成交历史 - -3. 实时行情和成交推送 - -4. 获取Forex Lab中的日历、订单簿、历史持仓比、价差、交易商持仓、Autochartist - -目前该API尚处于测试阶段,如果发现bug或者需要改进的地方请在github上开issue给我。 - -### 特点 -相比较于[OANDA官网](http://developer.oanda.com/rest-live/sample-code/)上贴出的一些Python API(如pyoanda、oanda-trading-environment等),vn.oanda的一些不同: - -1. 面向对象的API设计,接近CTP API的结构,对于国内用户而言更容易上手 - -2. 三个独立的工作线程,分别处理:用户请求(如发送委托等)、行情推送、事件推送(如成交事件等),提供更高的性能 - -3. 参考CTP API的设计,主动函数调用的结果通过异步(回调函数)的方式推送到程序中,适用于开发真正可靠的实盘交易程序(pyoanda里使用的同步阻塞工作模式在实盘应用中的风险:想象你的交易程序发送委托请求后,因为网络问题不能立即返回,因此主线程阻塞导致界面卡死或者背后的策略引擎线程卡死,对新的行情事件完全失去响应) - -### Quick Start -1. 安装Anaconda 2.7 32位 - -2. 前往[OANDA](http://www.oanda.com)注册一个fxTrade practice测试账户(注意国家不要选中国,会无法申请API token,作者测试英国可以) - -3. 在网站登陆后,进入Manage Funds,记录下自己的Account Number - -4. 回到上一个界面,左侧有个Manage API Access(在Recent Logins上方,没有的就是第一步国家选错了),进入后生成token - -5. 下载vn.oanda到本地后,打开test.py,修改token和accountId为你的信息 - -6. 将test.py中想要测试的功能取消注释,开始使用吧! - -### API版本 -日期:2016-02-27 - -链接:[http://developer.oanda.com/rest-live/introduction/](http://developer.oanda.com/rest-live/introduction/) - diff --git a/vnpy/api/oanda/__init__.py b/vnpy/api/oanda/__init__.py deleted file mode 100644 index 68530d94..00000000 --- a/vnpy/api/oanda/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# encoding: UTF-8 - -from __future__ import absolute_import -from .vnoanda import OandaApi \ No newline at end of file diff --git a/vnpy/api/oanda/test.py b/vnpy/api/oanda/test.py deleted file mode 100644 index 11e721c4..00000000 --- a/vnpy/api/oanda/test.py +++ /dev/null @@ -1,106 +0,0 @@ -# encoding: utf-8 - -from __future__ import absolute_import -from .vnoanda import OandaApi - - -if __name__ == '__main__': - token = '' - accountId = '' - - api = OandaApi() - api.DEBUG = True - - api.init('practice', token, accountId) - - # 获取交易合约列表,通过 - #api.getInstruments({'accountId': accountId}) - - # 获取价格,通过 - #api.getPrices({'instruments': 'EUR_USD'}) - - # 获取历史数据,失败 - #api.getPriceHisory({'instrument': 'EUR_USD', - #'granularity': 'D', - #'candleFormat': 'midpoint', - #'count': '50'}) - - # 查询用户的所有账户,通过 - #api.getAccounts() - - # 查询账户信息,通过 - #api.getAccountInfo() - - # 查询委托数据,通过 - #api.getOrders({}) - - # 发送委托,通过 - #api.sendOrder({'instrument': 'EUR_USD', - #'units': '10000', - #'side': 'buy', - #'type': 'market'}) - - # 查询委托数据,通过 - #api.getOrderInfo('123') - - # 修改委托,通过 - #api.modifyOrder({'units': '10000', - #'side': 'buy', - #'type': 'market'}, '123') - - # 撤销委托,通过 - #api.cancelOrder('123') - - # 查询所有持仓,通过 - #api.getTrades({}) - - # 查询持仓数据,通过 - #api.getTradeInfo('10125150909') - - # 修改持仓,通过 - #api.modifyTrade({'trailingStop': '150'}, '10125150909') - - # 平仓,通过 - #api.closeTrade('10125150909') - - # 查询汇总持仓,通过 - #api.getPositions() - - # 查询汇总持仓细节,通过 - #api.getPositionInfo('EUR_USD') - - # 平仓汇总持仓,通过 - #api.closePosition('EUR_USD') - - # 查询账户资金变动,通过 - #api.getTransactions({}) - - # 查询资金变动信息,通过 - #api.getTransactionInfo('10135713982') - - # 查询账户变动历史,部分通过,某些情况下可能触发JSONDecodeError - #api.getAccountHistory() - - # 查询财经日历,通过 - #api.getCalendar({'period': '604800'}) - - # 查询历史持仓比,通过 - #api.getPositionRatios({'instrument': 'EUR_USD', - #'period': '604800'}) - - # 查询历史价差,通过 - #api.getSpreads({'instrument': 'EUR_USD', - #'period': '604800'}) - - # 查询交易商持仓,通过 - #api.getCommitments({'instrument': 'EUR_USD'}) - - # 查询订单簿,通过 - #api.getOrderbook({'instrument': 'EUR_USD', - #'period': '604800'}) - - # 查询Autochartist,失败,OANDA服务器报错 - #api.getAutochartist({'instrument': 'EUR_USD'}) - - # 阻塞 - input() diff --git a/vnpy/api/oanda/vnoanda.py b/vnpy/api/oanda/vnoanda.py deleted file mode 100644 index 2f5f63fb..00000000 --- a/vnpy/api/oanda/vnoanda.py +++ /dev/null @@ -1,610 +0,0 @@ -# encoding: utf-8 - -from __future__ import print_function -import json -import requests -from Queue import Queue, Empty -from threading import Thread - - -API_SETTING = {} -API_SETTING['practice'] = {'rest': 'https://api-fxpractice.oanda.com', - 'stream': 'https://stream-fxpractice.oanda.com'} -API_SETTING['trade'] = {'rest': 'https://api-fxtrade.oanda.com', - 'stream': 'https://stream-fxtrade.oanda.com'} - - -FUNCTIONCODE_GETINSTRUMENTS = 0 -FUNCTIONCODE_GETPRICES = 1 -FUNCTIONCODE_GETPRICEHISTORY = 2 -FUNCTIONCODE_GETACCOUNTS = 3 -FUNCTIONCODE_GETACCOUNTINFO = 4 -FUNCTIONCODE_GETORDERS = 5 -FUNCTIONCODE_SENDORDER = 6 -FUNCTIONCODE_GETORDERINFO = 7 -FUNCTIONCODE_MODIFYORDER = 8 -FUNCTIONCODE_CANCELORDER = 9 -FUNCTIONCODE_GETTRADES = 10 -FUNCTIONCODE_GETTRADEINFO = 11 -FUNCTIONCODE_MODIFYTRADE= 12 -FUNCTIONCODE_CLOSETRADE = 13 -FUNCTIONCODE_GETPOSITIONS = 14 -FUNCTIONCODE_GETPOSITIONINFO= 15 -FUNCTIONCODE_CLOSEPOSITION = 16 -FUNCTIONCODE_GETTRANSACTIONS = 17 -FUNCTIONCODE_GETTRANSACTIONINFO = 18 -FUNCTIONCODE_GETACCOUNTHISTORY = 19 -FUNCTIONCODE_GETCALENDAR = 20 -FUNCTIONCODE_GETPOSITIONRATIOS = 21 -FUNCTIONCODE_GETSPREADS = 22 -FUNCTIONCODE_GETCOMMIMENTS = 23 -FUNCTIONCODE_GETORDERBOOK = 24 -FUNCTIONCODE_GETAUTOCHARTIST = 25 -FUNCTIONCODE_STREAMPRICES = 26 -FUNCTIONCODE_STREAMEVENTS = 27 - - -######################################################################## -class OandaApi(object): - """""" - DEBUG = False - - #---------------------------------------------------------------------- - def __init__(self): - """Constructor""" - self.token = '' - self.accountId = '' - self.headers = {} - self.restDomain = '' - self.streamDomain = '' - self.session = None - - self.functionSetting = {} - - self.active = False # API的工作状态 - - self.reqID = 0 # 请求编号 - self.reqQueue = Queue() # 请求队列 - self.reqThread = Thread(target=self.processQueue) # 请求处理线程 - - self.streamPricesThread = Thread(target=self.processStreamPrices) # 实时行情线程 - self.streamEventsThread = Thread(target=self.processStreamEvents) # 实时事件线程(成交等) - - #---------------------------------------------------------------------- - def init(self, settingName, token, accountId): - """初始化接口""" - self.restDomain = API_SETTING[settingName]['rest'] - self.streamDomain = API_SETTING[settingName]['stream'] - self.session = requests.Session() - - self.token = token - self.accountId = accountId - - self.headers['Authorization'] = 'Bearer ' + self.token - - self.initFunctionSetting(FUNCTIONCODE_GETINSTRUMENTS, {'path': '/v1/instruments', - 'method': 'GET'}) - - self.initFunctionSetting(FUNCTIONCODE_GETPRICES, {'path': '/v1/prices', - 'method': 'GET'}) - - self.initFunctionSetting(FUNCTIONCODE_GETPRICEHISTORY, {'path': '/v1/candles', - 'method': 'GET'}) - - self.initFunctionSetting(FUNCTIONCODE_GETACCOUNTS, {'path': '/v1/accounts', - 'method': 'GET'}) - - self.initFunctionSetting(FUNCTIONCODE_GETACCOUNTINFO, {'path': '/v1/accounts/%s' %self.accountId, - 'method': 'GET'}) - - self.initFunctionSetting(FUNCTIONCODE_GETORDERS, {'path': '/v1/accounts/%s/orders' %self.accountId, - 'method': 'GET'}) - - self.initFunctionSetting(FUNCTIONCODE_SENDORDER, {'path': '/v1/accounts/%s/orders' %self.accountId, - 'method': 'POST'}) - - self.initFunctionSetting(FUNCTIONCODE_GETORDERINFO, {'path': '/v1/accounts/%s/orders' %self.accountId, - 'method': 'GET'}) - - self.initFunctionSetting(FUNCTIONCODE_MODIFYORDER, {'path': '/v1/accounts/%s/orders' %self.accountId, - 'method': 'PATCH'}) - - self.initFunctionSetting(FUNCTIONCODE_CANCELORDER, {'path': '/v1/accounts/%s/orders' %self.accountId, - 'method': 'DELETE'}) - - self.initFunctionSetting(FUNCTIONCODE_GETTRADES, {'path': '/v1/accounts/%s/trades' %self.accountId, - 'method': 'GET'}) - - self.initFunctionSetting(FUNCTIONCODE_GETTRADEINFO, {'path': '/v1/accounts/%s/trades' %self.accountId, - 'method': 'GET'}) - - self.initFunctionSetting(FUNCTIONCODE_MODIFYTRADE, {'path': '/v1/accounts/%s/trades' %self.accountId, - 'method': 'PATCH'}) - - self.initFunctionSetting(FUNCTIONCODE_CLOSETRADE, {'path': '/v1/accounts/%s/trades' %self.accountId, - 'method': 'DELETE'}) - - self.initFunctionSetting(FUNCTIONCODE_GETPOSITIONS, {'path': '/v1/accounts/%s/positions' %self.accountId, - 'method': 'GET'}) - - self.initFunctionSetting(FUNCTIONCODE_GETPOSITIONINFO, {'path': '/v1/accounts/%s/positions' %self.accountId, - 'method': 'GET'}) - - self.initFunctionSetting(FUNCTIONCODE_CLOSEPOSITION, {'path': '/v1/accounts/%s/positions' %self.accountId, - 'method': 'DELETE'}) - - self.initFunctionSetting(FUNCTIONCODE_GETTRANSACTIONS, {'path': '/v1/accounts/%s/transactions' %self.accountId, - 'method': 'GET'}) - - self.initFunctionSetting(FUNCTIONCODE_GETTRANSACTIONINFO, {'path': '/v1/accounts/%s/transactions' %self.accountId, - 'method': 'GET'}) - - self.initFunctionSetting(FUNCTIONCODE_GETACCOUNTHISTORY, {'path': '/v1/accounts/%s/alltransactions' %self.accountId, - 'method': 'GET'}) - - self.initFunctionSetting(FUNCTIONCODE_GETCALENDAR, {'path': '/labs/v1/calendar', - 'method': 'GET'}) - - self.initFunctionSetting(FUNCTIONCODE_GETPOSITIONRATIOS, {'path': '/labs/v1/historical_position_ratios', - 'method': 'GET'}) - - self.initFunctionSetting(FUNCTIONCODE_GETSPREADS, {'path': '/labs/v1/spreads', - 'method': 'GET'}) - - self.initFunctionSetting(FUNCTIONCODE_GETCOMMIMENTS, {'path': '/labs/v1/commitments', - 'method': 'GET'}) - - self.initFunctionSetting(FUNCTIONCODE_GETORDERBOOK, {'path': '/labs/v1/orderbook_data', - 'method': 'GET'}) - - self.initFunctionSetting(FUNCTIONCODE_GETAUTOCHARTIST, {'path': '/labs/v1/autochartist', - 'method': 'GET'}) - - self.initFunctionSetting(FUNCTIONCODE_GETAUTOCHARTIST, {'path': '/labs/v1/autochartist', - 'method': 'GET'}) - - self.initFunctionSetting(FUNCTIONCODE_STREAMPRICES, {'path': '/v1/prices', - 'method': 'GET'}) - - self.initFunctionSetting(FUNCTIONCODE_STREAMEVENTS, {'path': '/v1/events', - 'method': 'GET'}) - - - self.active = True - self.reqThread.start() - self.streamEventsThread.start() - self.streamPricesThread.start() - - #---------------------------------------------------------------------- - def exit(self): - """退出接口""" - if self.active: - self.active = False - self.reqThread.join() - - #---------------------------------------------------------------------- - def initFunctionSetting(self, code, setting): - """初始化API功能字典""" - self.functionSetting[code] = setting - - #---------------------------------------------------------------------- - def processRequest(self, req): - """发送请求并通过回调函数推送数据结果""" - url = req['url'] - method = req['method'] - params = req['params'] - - stream = False - if 'stream' in req: - stream = req['stream'] - - if method in ['GET', 'DELETE']: - myreq = requests.Request(method, url, headers=self.headers, params=params) - elif method in ['POST', 'PATCH']: - myreq = requests.Request(method, url, headers=self.headers, data=params) - pre = myreq.prepare() - - r = None - error = None - - try: - r = self.session.send(pre, stream=stream) - except Exception as e: - error = e - - return r, error - - #---------------------------------------------------------------------- - def processQueue(self): - """处理请求队列中的请求""" - while self.active: - try: - req = self.reqQueue.get(block=True, timeout=1) # 获取请求的阻塞为一秒 - callback = req['callback'] - reqID = req['reqID'] - - r, error = self.processRequest(req) - - if r: - try: - data = r.json() - if self.DEBUG: - print(callback.__name__) - callback(data, reqID) - except Exception as e: - self.onError(str(e), reqID) - else: - self.onError(error, reqID) - except Empty: - pass - - #---------------------------------------------------------------------- - def sendRequest(self, code, params, callback, optional=''): - """发送请求""" - setting = self.functionSetting[code] - - url = self.restDomain + setting['path'] - if optional: - url = url + '/' + optional - - self.reqID += 1 - - req = {'url': url, - 'method': setting['method'], - 'params': params, - 'callback': callback, - 'reqID': self.reqID} - self.reqQueue.put(req) - - return self.reqID - - #---------------------------------------------------------------------- - def onError(self, error, reqID): - """错误信息回调""" - print(error, reqID) - - #---------------------------------------------------------------------- - def getInstruments(self, params): - """查询可交易的合约列表""" - return self.sendRequest(FUNCTIONCODE_GETINSTRUMENTS, params, self.onGetInstruments) - - #---------------------------------------------------------------------- - def onGetInstruments(self, data, reqID): - """回调函数""" - print(data, reqID) - - #---------------------------------------------------------------------- - def getPrices(self, params): - """查询价格""" - return self.sendRequest(FUNCTIONCODE_GETPRICES, params, self.onGetPrices) - - #---------------------------------------------------------------------- - def onGetPrices(self, data, reqID): - """回调函数""" - print(data, reqID) - - #---------------------------------------------------------------------- - def getPriceHisory(self, params): - """查询历史价格数据""" - return self.sendRequest(FUNCTIONCODE_GETPRICEHISTORY, params, self.onGetPriceHistory) - - #---------------------------------------------------------------------- - def onGetPriceHistory(self, data, reqID): - """回调函数""" - print(data, reqID) - - #---------------------------------------------------------------------- - def getAccounts(self): - """查询用户的所有账户""" - return self.sendRequest(FUNCTIONCODE_GETACCOUNTS, {}, self.onGetAccounts) - - #---------------------------------------------------------------------- - def onGetAccounts(self, data, reqID): - """回调函数""" - print(data, reqID) - - #---------------------------------------------------------------------- - def getAccountInfo(self): - """查询账户数据""" - return self.sendRequest(FUNCTIONCODE_GETACCOUNTINFO, {}, self.onGetAccountInfo) - - #---------------------------------------------------------------------- - def onGetAccountInfo(self, data, reqID): - """回调函数""" - print(data, reqID) - - #---------------------------------------------------------------------- - def getOrders(self, params): - """查询所有委托""" - return self.sendRequest(FUNCTIONCODE_GETORDERS, params, self.onGetOrders) - - #---------------------------------------------------------------------- - def onGetOrders(self, data, reqID): - """回调函数""" - print(data, reqID) - - #---------------------------------------------------------------------- - def sendOrder(self, params): - """发送委托""" - return self.sendRequest(FUNCTIONCODE_SENDORDER, params, self.onSendOrder) - - #---------------------------------------------------------------------- - def onSendOrder(self, data, reqID): - """回调函数""" - print(data, reqID) - - #---------------------------------------------------------------------- - def getOrderInfo(self, optional): - """查询委托信息""" - return self.sendRequest(FUNCTIONCODE_GETORDERINFO, {}, self.onGetOrderInfo, optional) - - #---------------------------------------------------------------------- - def onGetOrderInfo(self, data, reqID): - """回调函数""" - print(data, reqID) - - #---------------------------------------------------------------------- - def modifyOrder(self, params, optional): - """修改委托""" - return self.sendRequest(FUNCTIONCODE_MODIFYORDER, params, self.onModifyOrder, optional) - - #---------------------------------------------------------------------- - def onModifyOrder(self, data, reqID): - """回调函数""" - print(data, reqID) - - #---------------------------------------------------------------------- - def cancelOrder(self, optional): - """查询委托信息""" - return self.sendRequest(FUNCTIONCODE_CANCELORDER, {}, self.onCancelOrder, optional) - - #---------------------------------------------------------------------- - def onCancelOrder(self, data, reqID): - """回调函数""" - print(data, reqID) - - #---------------------------------------------------------------------- - def getTrades(self, params): - """查询所有仓位""" - return self.sendRequest(FUNCTIONCODE_GETTRADES, params, self.onGetTrades) - - #---------------------------------------------------------------------- - def onGetTrades(self, data, reqID): - """回调函数""" - print(data, reqID) - - #---------------------------------------------------------------------- - def getTradeInfo(self, optional): - """查询仓位信息""" - return self.sendRequest(FUNCTIONCODE_GETTRADEINFO, {}, self.onGetTradeInfo, optional) - - #---------------------------------------------------------------------- - def onGetTradeInfo(self, data, reqID): - """回调函数""" - print(data, reqID) - - #---------------------------------------------------------------------- - def modifyTrade(self, params, optional): - """修改仓位""" - return self.sendRequest(FUNCTIONCODE_MODIFYTRADE, params, self.onModifyTrade, optional) - - #---------------------------------------------------------------------- - def onModifyTrade(self, data, reqID): - """回调函数""" - print(data, reqID) - - #---------------------------------------------------------------------- - def closeTrade(self, optional): - """平仓""" - return self.sendRequest(FUNCTIONCODE_CLOSETRADE, {}, self.onCloseTrade, optional) - - #---------------------------------------------------------------------- - def onCloseTrade(self, data, reqID): - """回调函数""" - print(data, reqID) - - #---------------------------------------------------------------------- - def getPositions(self): - """查询所有汇总仓位""" - return self.sendRequest(FUNCTIONCODE_GETPOSITIONS, {}, self.onGetPositions) - - #---------------------------------------------------------------------- - def onGetPositions(self, data, reqID): - """回调函数""" - print(data, reqID) - - #---------------------------------------------------------------------- - def getPositionInfo(self, optional): - """查询汇总仓位信息""" - return self.sendRequest(FUNCTIONCODE_GETPOSITIONINFO, {}, self.onGetPositionInfo, optional) - - #---------------------------------------------------------------------- - def onGetPositionInfo(self, data, reqID): - """回调函数""" - print(data, reqID) - - #---------------------------------------------------------------------- - def closePosition(self, optional): - """平仓汇总仓位信息""" - return self.sendRequest(FUNCTIONCODE_CLOSEPOSITION, {}, self.onClosePosition, optional) - - #---------------------------------------------------------------------- - def onClosePosition(self, data, reqID): - """回调函数""" - print(data, reqID) - - - #---------------------------------------------------------------------- - def getTransactions(self, params): - """查询所有资金变动""" - return self.sendRequest(FUNCTIONCODE_GETTRANSACTIONS, params, self.onGetTransactions) - - #---------------------------------------------------------------------- - def onGetTransactions(self, data, reqID): - """回调函数""" - print(data, reqID) - - #---------------------------------------------------------------------- - def getTransactionInfo(self, optional): - """查询资金变动信息""" - return self.sendRequest(FUNCTIONCODE_GETTRANSACTIONINFO, {}, self.onGetTransactionInfo, optional) - - #---------------------------------------------------------------------- - def onGetTransactionInfo(self, data, reqID): - """回调函数""" - print(data, reqID) - - #---------------------------------------------------------------------- - def getAccountHistory(self): - """查询账户资金变动历史""" - return self.sendRequest(FUNCTIONCODE_GETACCOUNTHISTORY, {}, self.onGetAccountHistory) - - #---------------------------------------------------------------------- - def onGetAccountHistory(self, data, reqID): - """回调函数""" - print(data, reqID) - - #---------------------------------------------------------------------- - def getCalendar(self, params): - """查询日历""" - return self.sendRequest(FUNCTIONCODE_GETCALENDAR, params, self.onGetCalendar) - - #---------------------------------------------------------------------- - def onGetCalendar(self, data, reqID): - """回调函数""" - print(data, reqID) - - #---------------------------------------------------------------------- - def getPositionRatios(self, params): - """查询持仓比例""" - return self.sendRequest(FUNCTIONCODE_GETPOSITIONRATIOS, params, self.onGetPositionRatios) - - #---------------------------------------------------------------------- - def onGetPositionRatios(self, data, reqID): - """回调函数""" - print(data, reqID) - - #---------------------------------------------------------------------- - def getSpreads(self, params): - """查询所有仓位""" - return self.sendRequest(FUNCTIONCODE_GETSPREADS, params, self.onGetSpreads) - - #---------------------------------------------------------------------- - def onGetSpreads(self, data, reqID): - """回调函数""" - print(data, reqID) - - #---------------------------------------------------------------------- - def getCommitments(self, params): - """查询交易商持仓情况""" - return self.sendRequest(FUNCTIONCODE_GETCOMMIMENTS, params, self.onGetCommitments) - - #---------------------------------------------------------------------- - def onGetCommitments(self, data, reqID): - """回调函数""" - print(data, reqID) - - #---------------------------------------------------------------------- - def getOrderbook(self, params): - """查询订单簿""" - return self.sendRequest(FUNCTIONCODE_GETORDERBOOK, params, self.onGetOrderbook) - - #---------------------------------------------------------------------- - def onGetOrderbook(self, data, reqID): - """回调函数""" - print(data, reqID) - - #---------------------------------------------------------------------- - def getAutochartist(self, params): - """查询Autochartist识别的模式""" - return self.sendRequest(FUNCTIONCODE_GETAUTOCHARTIST, params, self.onGetAutochartist) - - #---------------------------------------------------------------------- - def onGetAutochartist(self, data, reqID): - """回调函数""" - print(data, reqID) - - #---------------------------------------------------------------------- - def onPrice(self, data): - """行情推送""" - print(data) - - #---------------------------------------------------------------------- - def onEvent(self, data): - """事件推送(成交等)""" - print(data) - - #---------------------------------------------------------------------- - def processStreamPrices(self): - """获取价格推送""" - # 首先获取所有合约的代码 - setting = self.functionSetting[FUNCTIONCODE_GETINSTRUMENTS] - req = {'url': self.restDomain + setting['path'], - 'method': setting['method'], - 'params': {'accountId': self.accountId}} - r, error = self.processRequest(req) - if r: - try: - data = r.json() - symbols = [d['instrument'] for d in data['instruments']] - except Exception as e: - self.onError(e, -1) - return - else: - self.onError(error, -1) - return - - # 然后订阅所有的合约行情 - setting = self.functionSetting[FUNCTIONCODE_STREAMPRICES] - params = {'accountId': self.accountId, - 'instruments': ','.join(symbols)} - req = {'url': self.streamDomain + setting['path'], - 'method': setting['method'], - 'params': params, - 'stream': True} - r, error = self.processRequest(req) - - if r: - for line in r.iter_lines(): - if line: - try: - msg = json.loads(line) - - if self.DEBUG: - print(self.onPrice.__name__) - - self.onPrice(msg) - except Exception as e: - self.onError(e, -1) - - if not self.active: - break - else: - self.onError(error, -1) - - #---------------------------------------------------------------------- - def processStreamEvents(self): - """获取事件推送""" - setting = self.functionSetting[FUNCTIONCODE_STREAMEVENTS] - req = {'url': self.streamDomain + setting['path'], - 'method': setting['method'], - 'params': {}, - 'stream': True} - r, error = self.processRequest(req) - if r: - for line in r.iter_lines(): - if line: - try: - msg = json.loads(line) - - if self.DEBUG: - print(self.onEvent.__name__) - - self.onEvent(msg) - except Exception as e: - self.onError(e, -1) - - if not self.active: - break - else: - self.onError(error, -1) diff --git a/beta/api/okex/README.md b/vnpy/api/okex/README.md similarity index 68% rename from beta/api/okex/README.md rename to vnpy/api/okex/README.md index f98f155c..5fa5cc86 100644 --- a/beta/api/okex/README.md +++ b/vnpy/api/okex/README.md @@ -10,5 +10,5 @@ OKEX的比特币交易接口,基于Websocket API开发,实现了以下功能 ### API信息 -链接:[https://www.okex.com/ws_getStarted.html](https://www.okex.com/ws_getStarted.html) +链接:[https://github.com/okcoin-okex/API-docs-OKEx.com](https://github.com/okcoin-okex/API-docs-OKEx.com) diff --git a/vnpy/api/okex/__init__.py b/vnpy/api/okex/__init__.py new file mode 100644 index 00000000..2b7ddb71 --- /dev/null +++ b/vnpy/api/okex/__init__.py @@ -0,0 +1,4 @@ +# encoding: UTF-8 + +from __future__ import absolute_import +from .vnokex import OkexSpotApi, SPOT_CURRENCY, SPOT_SYMBOL, OKEX_SPOT_HOST \ No newline at end of file diff --git a/beta/api/okex/vnokex.py b/vnpy/api/okex/vnokex.py similarity index 52% rename from beta/api/okex/vnokex.py rename to vnpy/api/okex/vnokex.py index 18267b97..555df99f 100644 --- a/beta/api/okex/vnokex.py +++ b/vnpy/api/okex/vnokex.py @@ -1,17 +1,19 @@ # encoding: UTF-8 from __future__ import print_function + +import ssl import hashlib -import zlib import json -from time import sleep +import traceback from threading import Thread +from time import sleep import websocket -# OKEX网站 -OKEX_USD_SPOT = 'wss://real.okex.com:10441/websocket' # OKEX 现货地址 -OKEX_USD_CONTRACT = 'wss://real.okex.com:10440/websocket/okexapi' # OKEX 期货地址 +# 常量定义 +OKEX_SPOT_HOST = 'wss://real.okex.com:10441/websocket' + SPOT_CURRENCY = ["usdt", "btc", @@ -56,16 +58,6 @@ KLINE_PERIOD = ["1min", "3day", "week"] -CONTRACT_SYMBOL = ["btc", - "ltc", - "eth", - "etc", - "bch"] - -CONTRACT_TYPE = ["this_week", - "next_week", - "quarter"] - ######################################################################## class OkexApi(object): @@ -78,41 +70,76 @@ class OkexApi(object): self.apiKey = '' # 用户名 self.secretKey = '' # 密码 - self.ws = None # websocket应用对象 现货对象 - self.thread = None # 初始化线程 + self.active = False # 工作状态 + self.ws = None # websocket应用对象 + self.wsThread = None # websocket工作线程 + + self.heartbeatCount = 0 # 心跳计数 + self.heartbeatThread = None # 心跳线程 + self.heartbeatReceived = True # 心跳是否收到 + + self.reconnecting = False # 重新连接中 + + #---------------------------------------------------------------------- + def heartbeat(self): + """""" + while self.active: + self.heartbeatCount += 1 + + if self.heartbeatCount < 10: + sleep(1) + else: + self.heartbeatCount = 0 + + if not self.heartbeatReceived: + self.reconnect() + else: + self.heartbeatReceived = False + d = {'event': 'ping'} + j = json.dumps(d) + + try: + self.ws.send(j) + except: + msg = traceback.format_exc() + self.onError(msg) + self.reconnect() #---------------------------------------------------------------------- def reconnect(self): """重新连接""" - # 首先关闭之前的连接 - self.close() + if not self.reconnecting: + self.reconnecting = True + + self.closeWebsocket() # 首先关闭之前的连接 + self.initWebsocket() + + self.reconnecting = False - # 再执行重连任务 - self.ws = websocket.WebSocketApp(self.host, - on_message=self.onMessage, - on_error=self.onError, - on_close=self.onClose, - on_open=self.onOpen) - - self.thread = Thread(target=self.ws.run_forever) - self.thread.start() - #---------------------------------------------------------------------- - def connect(self, apiKey, secretKey, trace=False): - self.host = OKEX_USD_SPOT + def connect(self, host, apiKey, secretKey, trace=False): + """连接""" + self.host = host self.apiKey = apiKey self.secretKey = secretKey - + websocket.enableTrace(trace) - + + self.initWebsocket() + self.active = True + + #---------------------------------------------------------------------- + def initWebsocket(self): + """""" self.ws = websocket.WebSocketApp(self.host, - on_message=self.onMessage, - on_error=self.onError, - on_close=self.onClose, - on_open=self.onOpen) - - self.thread = Thread(target=self.ws.run_forever) - self.thread.start() + on_message=self.onMessageCallback, + on_error=self.onErrorCallback, + on_close=self.onCloseCallback, + on_open=self.onOpenCallback) + + kwargs = {'sslopt': {'cert_reqs': ssl.CERT_NONE}} + self.wsThread = Thread(target=self.ws.run_forever, kwargs=kwargs) + self.wsThread.start() #---------------------------------------------------------------------- def readData(self, evt): @@ -121,32 +148,74 @@ class OkexApi(object): return data #---------------------------------------------------------------------- - def close(self): + def closeHeartbeat(self): """关闭接口""" - if self.thread and self.thread.isAlive(): - self.ws.close() - self.thread.join() + if self.heartbeatThread and self.heartbeatThread.isAlive(): + self.active = False + self.heartbeatThread.join() #---------------------------------------------------------------------- - def onMessage(self, ws, evt): - """信息推送""" - print(evt) + def closeWebsocket(self): + """关闭WS""" + if self.wsThread and self.wsThread.isAlive(): + self.ws.close() + self.wsThread.join() + + #---------------------------------------------------------------------- + def close(self): + """""" + self.closeHeartbeat() + self.closeWebsocket() #---------------------------------------------------------------------- - def onError(self, ws, evt): + def onMessage(self, data): + """信息推送""" + print('onMessage') + print(data) + + #---------------------------------------------------------------------- + def onError(self, data): """错误推送""" print('onError') - print(evt) + print(data) #---------------------------------------------------------------------- - def onClose(self, ws): + def onClose(self): """接口断开""" print('onClose') #---------------------------------------------------------------------- - def onOpen(self, ws): + def onOpen(self): """接口打开""" print('onOpen') + + #---------------------------------------------------------------------- + def onMessageCallback(self, ws, evt): + """""" + data = self.readData(evt) + if 'event' in data: + self.heartbeatReceived = True + else: + self.onMessage(data[0]) + + #---------------------------------------------------------------------- + def onErrorCallback(self, ws, evt): + """""" + self.onError(evt) + + #---------------------------------------------------------------------- + def onCloseCallback(self, ws): + """""" + self.onClose() + + #---------------------------------------------------------------------- + def onOpenCallback(self, ws): + """""" + if not self.heartbeatThread: + self.heartbeatThread = Thread(target=self.heartbeat) + self.heartbeatThread.start() + + self.onOpen() #---------------------------------------------------------------------- def generateSign(self, params): @@ -159,17 +228,18 @@ class OkexApi(object): return hashlib.md5(sign.encode('utf-8')).hexdigest().upper() #---------------------------------------------------------------------- - def sendTradingRequest(self, channel, params): - """发送交易请求""" - # 在参数字典中加上api_key和签名字段 - params['api_key'] = self.apiKey - params['sign'] = self.generateSign(params) - + def sendRequest(self, channel, params=None): + """发送请求""" # 生成请求 d = {} d['event'] = 'addChannel' d['channel'] = channel - d['parameters'] = params + + # 如果有参数,在参数字典中加上api_key和签名字段 + if params is not None: + params['api_key'] = self.apiKey + params['sign'] = self.generateSign(params) + d['parameters'] = params # 使用json打包并发送 j = json.dumps(d) @@ -177,22 +247,10 @@ class OkexApi(object): # 若触发异常则重连 try: self.ws.send(j) + return True except websocket.WebSocketConnectionClosedException: - pass - - #---------------------------------------------------------------------- - def sendDataRequest(self, channel): - """发送数据请求""" - d = {} - d['event'] = 'addChannel' - d['channel'] = channel - j = json.dumps(d) - - # 若触发异常则重连 - try: - self.ws.send(j) - except websocket.WebSocketConnectionClosedException: - pass + self.reconnect() + return False #---------------------------------------------------------------------- def login(self): @@ -204,8 +262,6 @@ class OkexApi(object): d = {} d['event'] = 'login' d['parameters'] = params - - # 使用json打包并发送 j = json.dumps(d) # 若触发异常则重连 @@ -213,6 +269,7 @@ class OkexApi(object): self.ws.send(j) return True except websocket.WebSocketConnectionClosedException: + self.reconnect() return False @@ -229,7 +286,7 @@ class OkexSpotApi(OkexApi): def subscribeSpotTicker(self, symbol): """订阅现货的Tick""" channel = 'ok_sub_spot_%s_ticker' %symbol - self.sendDataRequest(channel) + self.sendRequest(channel) #---------------------------------------------------------------------- def subscribeSpotDepth(self, symbol, depth=0): @@ -237,20 +294,20 @@ class OkexSpotApi(OkexApi): channel = 'ok_sub_spot_%s_depth' %symbol if depth: channel = channel + '_' + str(depth) - self.sendDataRequest(channel) + self.sendRequest(channel) #---------------------------------------------------------------------- def subscribeSpotDeals(self, symbol): channel = 'ok_sub_spot_%s_deals' %symbol - self.sendDataRequest(channel) + self.sendRequest(channel) #---------------------------------------------------------------------- def subscribeSpotKlines(self, symbol, period): channel = 'ok_sub_spot_%s_kline_%s' %(symbol, period) - self.sendDataRequest(channel) + self.sendRequest(channel) #---------------------------------------------------------------------- - def spotTrade(self, symbol, type_, price, amount): + def spotOrder(self, symbol, type_, price, amount): """现货委托""" params = {} params['symbol'] = str(symbol) @@ -260,7 +317,7 @@ class OkexSpotApi(OkexApi): channel = 'ok_spot_order' - self.sendTradingRequest(channel, params) + return self.sendRequest(channel, params) #---------------------------------------------------------------------- def spotCancelOrder(self, symbol, orderid): @@ -271,13 +328,13 @@ class OkexSpotApi(OkexApi): channel = 'ok_spot_cancel_order' - self.sendTradingRequest(channel, params) + self.sendRequest(channel, params) #---------------------------------------------------------------------- def spotUserInfo(self): """查询现货账户""" channel = 'ok_spot_userinfo' - self.sendTradingRequest(channel, {}) + self.sendRequest(channel, {}) #---------------------------------------------------------------------- def spotOrderInfo(self, symbol, orderid): @@ -288,126 +345,17 @@ class OkexSpotApi(OkexApi): channel = 'ok_spot_orderinfo' - self.sendTradingRequest(channel, params) - - - -######################################################################## -class OkexFuturesApi(OkexApi): - """期货交易接口 + self.sendRequest(channel, params) - 交割推送信息: - [{ - "channel": "btc_forecast_price", - "timestamp":"1490341322021", - "data": "998.8" - }] - data(string): 预估交割价格 - timestamp(string): 时间戳 + #---------------------------------------------------------------------- + def subSpotOrder(self, symbol): + """订阅委托推送""" + channel = 'ok_sub_spot_%s_order' %symbol + self.sendRequest(channel) - 无需订阅,交割前一小时自动返回 - """ - #---------------------------------------------------------------------- - def __init__(self): - """Constructor""" - super(OkexFuturesApi, self).__init__() - - #---------------------------------------------------------------------- - def subsribeFuturesTicker(self, symbol, contractType): - """订阅期货行情""" - channel ='ok_sub_futureusd_%s_ticker_%s' %(symbol, contractType) - self.sendDataRequest(channel) - - #---------------------------------------------------------------------- - def subscribeFuturesKline(self, symbol, contractType, period): - """订阅期货K线""" - channel = 'ok_sub_futureusd_%s_kline_%s_%s' %(symbol, contractType, period) - self.sendDataRequest(channel) - - #---------------------------------------------------------------------- - def subscribeFuturesDepth(self, symbol, contractType, depth=0): - """订阅期货深度""" - channel = 'ok_sub_futureusd_%s_depth_%s' %(symbol, contractType) - if depth: - channel = channel + '_' + str(depth) - self.sendDataRequest(channel) - - #---------------------------------------------------------------------- - def subscribeFuturesTrades(self, symbol, contractType): - """订阅期货成交""" - channel = 'ok_sub_futureusd_%s_trade_%s' %(symbol, contractType) - self.sendDataRequest(channel) - - #---------------------------------------------------------------------- - def subscribeFuturesIndex(self, symbol): - """订阅期货指数""" - channel = 'ok_sub_futureusd_%s_index' %symbol - self.sendDataRequest(channel) - - #---------------------------------------------------------------------- - def futuresTrade(self, symbol, contractType, type_, price, amount, matchPrice='0', leverRate='10'): - """期货委托""" - params = {} - params['symbol'] = str(symbol) - params['contract_type'] = str(contractType) - params['price'] = str(price) - params['amount'] = str(amount) - params['type'] = type_ # 1:开多 2:开空 3:平多 4:平空 - params['match_price'] = matchPrice # 是否为对手价: 0:不是 1:是 当取值为1时,price无效 - params['lever_rate'] = leverRate - - channel = 'ok_futureusd_trade' - - self.sendTradingRequest(channel, params) - - #---------------------------------------------------------------------- - def futuresCancelOrder(self, symbol, orderid, contractType): - """期货撤单""" - params = {} - params['symbol'] = str(symbol) - params['order_id'] = str(orderid) - params['contract_type'] = str(contractType) - - channel = 'ok_futureusd_cancel_order' - - self.sendTradingRequest(channel, params) - - #---------------------------------------------------------------------- - def futuresUserInfo(self): - """查询期货账户""" - channel = 'ok_futureusd_userinfo' - self.sendTradingRequest(channel, {}) - - #---------------------------------------------------------------------- - def futuresOrderInfo(self, symbol, orderid, contractType, status, current_page, page_length=10): - """查询期货委托""" - params = {} - params['symbol'] = str(symbol) - params['order_id'] = str(orderid) - params['contract_type'] = str(contractType) - params['status'] = str(status) - params['current_page'] = str(current_page) - params['page_length'] = str(page_length) - - channel = 'ok_futureusd_orderinfo' - - self.sendTradingRequest(channel, params) - - #---------------------------------------------------------------------- - def subscribeFuturesTrades( self): - channel = 'ok_sub_futureusd_trades' - self.sendTradingRequest(channel, {}) - - #---------------------------------------------------------------------- - def subscribeFuturesUserInfo(self): - """订阅期货账户信息""" - channel = 'ok_sub_futureusd_userinfo' - self.sendTradingRequest(channel, {}) - - #---------------------------------------------------------------------- - def subscribeFuturesPositions(self): - """订阅期货持仓信息""" - channel = 'ok_sub_futureusd_positions' - self.sendTradingRequest(channel, {}) - \ No newline at end of file + def subSpotBalance(self, symbol): + """订阅资金推送""" + channel = 'ok_sub_spot_%s_balance' %symbol + self.sendRequest(channel) + diff --git a/vnpy/api/sec/__init__.py b/vnpy/api/sec/__init__.py index 58843bbd..98f521e9 100644 --- a/vnpy/api/sec/__init__.py +++ b/vnpy/api/sec/__init__.py @@ -1,5 +1,6 @@ # encoding: UTF-8 -from vnsecmd import MdApi -from vnsectd import TdApi -import sec_data_type as DATA_TYPE \ No newline at end of file +from __future__ import absolute_import +from .vnsecmd import MdApi +from .vnsectd import TdApi +from . import sec_data_type as DATA_TYPE \ No newline at end of file diff --git a/vnpy/api/sec/pyscript/generate_data_type.py b/vnpy/api/sec/pyscript/generate_data_type.py index 9be99c04..1d4ffc4c 100644 --- a/vnpy/api/sec/pyscript/generate_data_type.py +++ b/vnpy/api/sec/pyscript/generate_data_type.py @@ -2,6 +2,7 @@ # C++和Python类型映射 +from __future__ import print_function type_map = { 'int': 'int', 'long': 'long', @@ -97,7 +98,7 @@ def main(cpp_filename, py_filename): cpp_f.close() py_f.close() - print u'data_type处理完成' + print(u'data_type处理完成') if __name__ == '__main__': diff --git a/vnpy/api/sec/pyscript/generate_md_functions.py b/vnpy/api/sec/pyscript/generate_md_functions.py index b11f90d7..87d778ad 100644 --- a/vnpy/api/sec/pyscript/generate_md_functions.py +++ b/vnpy/api/sec/pyscript/generate_md_functions.py @@ -1,5 +1,6 @@ # encoding: UTF-8 +from __future__ import print_function from generate_data_type import pre_process import sec_struct @@ -69,7 +70,7 @@ def process_function(cpp_line): args_type_list.append(l[0]) args_name_list.append(l[1]) - print args_type_list + print(args_type_list) if args_type_list and args_type_list[0] in STRUCT_DICT: create_function(fc_name, args_type_list, args_name_list) @@ -298,4 +299,4 @@ header_process_f.close() header_on_f.close() header_function_f.close() -print API_NAME + u'处理完成' \ No newline at end of file +print(API_NAME + u'处理完成') \ No newline at end of file diff --git a/vnpy/api/sec/pyscript/generate_struct.py b/vnpy/api/sec/pyscript/generate_struct.py index 9048ccb8..8bee3e6a 100644 --- a/vnpy/api/sec/pyscript/generate_struct.py +++ b/vnpy/api/sec/pyscript/generate_struct.py @@ -1,5 +1,6 @@ # encoding: UTF-8 +from __future__ import print_function from generate_data_type import pre_process import sec_data_type @@ -62,7 +63,7 @@ def main(cpp_filename, py_filename): cpp_f.close() py_f.close() - print u'struct处理完成' + print(u'struct处理完成') if __name__ == '__main__': diff --git a/vnpy/api/sec/pyscript/generate_td_functions.py b/vnpy/api/sec/pyscript/generate_td_functions.py index 1a574fdb..c3bc68dd 100644 --- a/vnpy/api/sec/pyscript/generate_td_functions.py +++ b/vnpy/api/sec/pyscript/generate_td_functions.py @@ -1,5 +1,6 @@ # encoding: UTF-8 +from __future__ import print_function from generate_data_type import pre_process import sec_struct @@ -69,7 +70,7 @@ def process_function(cpp_line): args_type_list.append(l[0]) args_name_list.append(l[1]) - print args_type_list + print(args_type_list) if args_type_list and args_type_list[0] in STRUCT_DICT: create_function(fc_name, args_type_list, args_name_list) @@ -298,4 +299,4 @@ header_process_f.close() header_on_f.close() header_function_f.close() -print API_NAME + u'处理完成' \ No newline at end of file +print(API_NAME + u'处理完成') \ No newline at end of file diff --git a/vnpy/api/sec/test/md_test.py b/vnpy/api/sec/test/md_test.py index cd2ab5a4..c304e392 100644 --- a/vnpy/api/sec/test/md_test.py +++ b/vnpy/api/sec/test/md_test.py @@ -1,7 +1,9 @@ # encoding: UTF-8 +from __future__ import print_function import sys +from six.moves import input from vnsecmd import MdApi @@ -9,7 +11,7 @@ from vnsecmd import MdApi def print_dict(d): """输出字典""" for k, v in d.items(): - print '%s:%s' %(k, v) + print('%s:%s' %(k, v)) ######################################################################## @@ -24,119 +26,119 @@ class TestMdApi(MdApi): #---------------------------------------------------------------------- def onFrontConnected(self): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onFrontDisconnected(self, reason): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRtnNotice(self, data): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspError(self, error): """""" - print sys._getframe().f_code.co_name - print locals() - print dict(error) + print(sys._getframe().f_code.co_name) + print(locals()) + print(dict(error)) #---------------------------------------------------------------------- def onRspStockUserLogin(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspStockUserLogout(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspSOPUserLogin(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) print_dict(data) print_dict(error) #---------------------------------------------------------------------- def onRspSOPUserLogout(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLUserLogin(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLUserLogout(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspStockSubMarketData(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspStockUnSubMarketData(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspSOPSubMarketData(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspSOPUnSubMarketData(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onStockMarketData(self, data): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onSOPMarketData(self, data): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspStockAvailableQuot(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspSopAvailableQuot(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspUserMDPasswordUpdate(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) if __name__ == '__main__': @@ -157,5 +159,5 @@ if __name__ == '__main__': } api.reqSOPUserLogin(req) - - raw_input() \ No newline at end of file + + input() diff --git a/vnpy/api/sec/test/td_test.py b/vnpy/api/sec/test/td_test.py index 742845ce..b12474b3 100644 --- a/vnpy/api/sec/test/td_test.py +++ b/vnpy/api/sec/test/td_test.py @@ -1,7 +1,9 @@ # encoding: UTF-8 +from __future__ import print_function import sys +from six.moves import input from vnsectd import TdApi @@ -9,7 +11,7 @@ from vnsectd import TdApi def print_dict(d): """输出字典""" for k, v in d.items(): - print '%s:%s' %(k, v) + print('%s:%s' %(k, v)) ######################################################################## @@ -24,556 +26,556 @@ class TestTdApi(TdApi): #---------------------------------------------------------------------- def onFrontConnected(self, ): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onFrontDisconnected(self, reason): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRtnNotice(self, data): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspError(self, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspStockUserLogin(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspStockUserLogout(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspStockUserPasswordUpdate(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspStockEntrustOrder(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspStockWithdrawOrder(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspStockQryEntrustOrder(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspStockQryRealTimeTrade(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspStockQrySerialTrade(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspStockQryPosition(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspStockQryCapitalAccountInfo(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspStockQryAccountInfo(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspStockQryShareholderInfo(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspStockTransferFunds(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspStockEntrustBatchOrder(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspStockWithdrawBatchOrder(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspStockCalcAbleEntrustQty(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspStockCalcAblePurchaseETFQty(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspStockQryFreezeFundsDetail(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspStockQryFreezeStockDetail(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspStockQryTransferStockDetail(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspStockQryTransferFundsDetail(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspStockQryStockInfo(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspStockQryStockStaticInfo(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspStockQryTradeTime(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onStockEntrustOrderRtn(self, data): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onStockTradeRtn(self, data): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onStockWithdrawOrderRtn(self, data): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspSOPUserLogin(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) print_dict(data) print_dict(error) #---------------------------------------------------------------------- def onRspSOPUserLogout(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspSOPUserPasswordUpdate(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspSOPEntrustOrder(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspSOPGroupSplit(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspSOPQryGroupPosition(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspSOPLockOUnLockStock(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspSOPWithdrawOrder(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspSOPQryEntrustOrder(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspSOPQrySerialTrade(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspSOPQryPosition(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspSOPQryCollateralPosition(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspSOPQryCapitalAccountInfo(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspSOPQryAccountInfo(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspSOPQryShareholderInfo(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspSOPCalcAbleEntrustQty(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspSOPQryAbleLockStock(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspSOPQryContactInfo(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspSOPExectueOrder(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspSOPQryExecAssiInfo(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspSOPQryTradeTime(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspSOPQryExchangeInfo(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspSOPQryCommission(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspSOPQryDeposit(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspSOPQryContractObjectInfo(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onSOPEntrustOrderRtn(self, data): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onSOPTradeRtn(self, data): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onSOPWithdrawOrderRtn(self, data): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLUserLogin(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLUserLogout(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLQryAbleFinInfo(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLQryAbleSloInfo(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLTransferCollateral(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLDirectRepayment(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLRepayStockTransfer(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLEntrustCrdtOrder(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLEntrustOrder(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLCalcAbleEntrustCrdtQty(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLQryCrdtFunds(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLQryCrdtContract(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLQryCrdtConChangeInfo(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLTransferFunds(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLQryAccountInfo(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLQryCapitalAccountInfo(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLQryShareholderInfo(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLQryPosition(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLQryEntrustOrder(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLQrySerialTrade(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLQryRealTimeTrade(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLQryFreezeFundsDetail(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLQryFreezeStockDetail(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLQryTransferFundsDetail(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLWithdrawOrder(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLQrySystemTime(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLQryTransferredContract(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLDesirableFundsOut(self, data, error): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLQryGuaranteedContract(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onRspFASLQryUnderlyingContract(self, data, error, flag): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onFASLEntrustOrderRtn(self, data): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onFASLTradeRtn(self, data): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) #---------------------------------------------------------------------- def onFASLWithdrawOrderRtn(self, data): """""" - print sys._getframe().f_code.co_name - print locals() + print(sys._getframe().f_code.co_name) + print(locals()) @@ -595,5 +597,5 @@ if __name__ == '__main__': } api.reqSOPUserLogin(req) - - raw_input() \ No newline at end of file + + input() diff --git a/vnpy/api/sgit/__init__.py b/vnpy/api/sgit/__init__.py index 7bdd52cd..906277fd 100644 --- a/vnpy/api/sgit/__init__.py +++ b/vnpy/api/sgit/__init__.py @@ -1,5 +1,6 @@ # encoding: UTF-8 -from vnsgitmd import MdApi -from vnsgittd import TdApi -from sgit_data_type import defineDict \ No newline at end of file +from __future__ import absolute_import +from .vnsgitmd import MdApi +from .vnsgittd import TdApi +from .sgit_data_type import defineDict \ No newline at end of file diff --git a/vnpy/api/shzd/__init__.py b/vnpy/api/shzd/__init__.py index 7a5e03d5..f76a6d69 100644 --- a/vnpy/api/shzd/__init__.py +++ b/vnpy/api/shzd/__init__.py @@ -1,3 +1,4 @@ # encoding: UTF-8 -from vnshzd import ShzdApi \ No newline at end of file +from __future__ import absolute_import +from .vnshzd import ShzdApi \ No newline at end of file diff --git a/vnpy/api/shzd/test/test.py b/vnpy/api/shzd/test/test.py index 13bb2c5f..a958d450 100644 --- a/vnpy/api/shzd/test/test.py +++ b/vnpy/api/shzd/test/test.py @@ -1,5 +1,6 @@ # encoding: UTF-8 +from __future__ import print_function from time import sleep from vnshzd import * @@ -10,7 +11,7 @@ def printDict(d): l = d.keys() l.sort() for key in l: - print '%s:%s' %(key, d[key]) + print('%s:%s' %(key, d[key])) ######################################################################## @@ -26,19 +27,19 @@ class TestApi(ShzdApi): #---------------------------------------------------------------------- def onReceiveErrorInfo(self, errcode, errmsg): """""" - print '-' * 50 - print 'errorcode %s, error msg %s' %(errcode, errmsg) + print('-' * 50) + print('errorcode %s, error msg %s' %(errcode, errmsg)) #---------------------------------------------------------------------- def onReceiveMarketInfo(self, data): """""" - print '-' * 50 + print('-' * 50) printDict(data) #---------------------------------------------------------------------- def onReceiveTradeInfo(self, data): """""" - print '-' * 50 + print('-' * 50) printDict(data) if __name__ == '__main__': @@ -49,8 +50,8 @@ if __name__ == '__main__': api.initShZdServer() # 注册前置机地址 - print api.registerFront('222.73.119.230', 7003) - print api.registerMarket('222.73.119.230', 9003) + print(api.registerFront('222.73.119.230', 7003)) + print(api.registerMarket('222.73.119.230', 9003)) # 登录 sleep(1) @@ -68,7 +69,7 @@ if __name__ == '__main__': data['201'] = '+' #data['307'] = "CME,6J1609" data['307'] = 'ICE,WBS1611' - print data + print(data) api.shzdSendInfoToMarket(data) # # 查询合约 diff --git a/vnpy/api/xspeed/__init__.py b/vnpy/api/xspeed/__init__.py index 697e5c75..0c4659fc 100644 --- a/vnpy/api/xspeed/__init__.py +++ b/vnpy/api/xspeed/__init__.py @@ -1,5 +1,6 @@ # encoding: UTF-8 -from vnxspeedmd import MdApi -from vnxspeedtd import TdApi -from xspeed_data_type import defineDict \ No newline at end of file +from __future__ import absolute_import +from .vnxspeedmd import MdApi +from .vnxspeedtd import TdApi +from .xspeed_data_type import defineDict \ No newline at end of file diff --git a/vnpy/api/xspeed/pyscript/generate_data_type.py b/vnpy/api/xspeed/pyscript/generate_data_type.py index df43f654..4595939d 100644 --- a/vnpy/api/xspeed/pyscript/generate_data_type.py +++ b/vnpy/api/xspeed/pyscript/generate_data_type.py @@ -1,5 +1,6 @@ # encoding: UTF-8 +from __future__ import print_function __author__ = 'CHENXY' # C++和python类型的映射字典 @@ -48,7 +49,7 @@ def process_typedef(line): else: keyword = content[-1] keyword = keyword.replace(';\n', '') - print content, keyword + print(content, keyword) if '[' in keyword: i = keyword.index('[') @@ -97,15 +98,15 @@ def main(): py_line = process_line(line) if py_line: fpy.write(py_line.decode('gbk').encode('utf-8')) - print n + print(n) fcpp.close() fpy.close() - print u'data_type.py生成过程完成' - except Exception, e: - print u'data_type.py生成过程出错' - print e + print(u'data_type.py生成过程完成') + except Exception as e: + print(u'data_type.py生成过程出错') + print(e) if __name__ == '__main__': diff --git a/vnpy/api/xspeed/pyscript/generate_md_functions.py b/vnpy/api/xspeed/pyscript/generate_md_functions.py index 9b380665..fb28de80 100644 --- a/vnpy/api/xspeed/pyscript/generate_md_functions.py +++ b/vnpy/api/xspeed/pyscript/generate_md_functions.py @@ -1,5 +1,6 @@ # encoding: UTF-8 +from __future__ import print_function __author__ = 'CHENXY' from string import join @@ -224,7 +225,7 @@ def processFunction(line): fcArgsTypeList.append(content[1]) # 参数类型列表 fcArgsValueList.append(content[3]) # 参数数据列表 - print fcArgsTypeList + print(fcArgsTypeList) if len(fcArgsTypeList)>0 and fcArgsTypeList[0] in structDict: createFunction(fcName, fcArgsTypeList, fcArgsValueList) @@ -286,10 +287,10 @@ define_count = 1 for line in fcpp: if " virtual void On" in line: - print 'callback' + print('callback') processCallBack(line) elif " virtual int" in line: - print 'function' + print('function') processFunction(line) fcpp.close() diff --git a/vnpy/api/xspeed/pyscript/generate_struct.py b/vnpy/api/xspeed/pyscript/generate_struct.py index ad0ff1c5..5f5a35fd 100644 --- a/vnpy/api/xspeed/pyscript/generate_struct.py +++ b/vnpy/api/xspeed/pyscript/generate_struct.py @@ -1,5 +1,6 @@ # encoding: UTF-8 +from __future__ import print_function __author__ = 'CHENXY' from xspeed_data_type import * @@ -47,10 +48,10 @@ def main(): n = line.index('//') line = line[:n] - print no, ':', line + print(no, ':', line) content = line.split('\t') - print content + print(content) typedef = content[1] type_ = typedefDict[typedef] diff --git a/vnpy/api/xspeed/pyscript/generate_td_functions.py b/vnpy/api/xspeed/pyscript/generate_td_functions.py index 52530211..a5dbe885 100644 --- a/vnpy/api/xspeed/pyscript/generate_td_functions.py +++ b/vnpy/api/xspeed/pyscript/generate_td_functions.py @@ -1,5 +1,6 @@ # encoding: UTF-8 +from __future__ import print_function __author__ = 'CHENXY' from string import join @@ -33,7 +34,7 @@ def processCallBack(line): cbArgsTypeList.append(content[0]) # 参数类型列表 cbArgsValueList.append(content[1]) # 参数数据列表 else: - print content + print(content) cbArgsTypeList.append(content[1]) # 参数类型列表 cbArgsValueList.append(content[2]+content[3]) # 参数数据列表 @@ -230,8 +231,8 @@ def processFunction(line): fcArgsTypeList.append(content[1]) # 参数类型列表 fcArgsValueList.append(content[3]) # 参数数据列表 - print line - print fcArgsTypeList + print(line) + print(fcArgsTypeList) if len(fcArgsTypeList)>0 and fcArgsTypeList[0] in structDict: createFunction(fcName, fcArgsTypeList, fcArgsValueList) @@ -263,7 +264,7 @@ def createFunction(fcName, fcArgsTypeList, fcArgsValueList): elif value == 'short': line = '\tgetShort(req, "' + key + '", &myreq.' + key + ');\n' elif value == 'float': - print line + print(line) line = '\tgetDouble(req, "' + key + '", &myreq.' + key + ');\n' ffunction.write(line) @@ -294,10 +295,10 @@ define_count = 1 for line in fcpp: if " virtual void On" in line: - print 'callback' + print('callback') processCallBack(line) elif " virtual int" in line: - print 'function' + print('function') processFunction(line) fcpp.close() diff --git a/vnpy/api/xspeed/pyscript/old/generate_data_type.py b/vnpy/api/xspeed/pyscript/old/generate_data_type.py index 477d5288..e833bad2 100644 --- a/vnpy/api/xspeed/pyscript/old/generate_data_type.py +++ b/vnpy/api/xspeed/pyscript/old/generate_data_type.py @@ -1,5 +1,6 @@ # encoding: UTF-8 +from __future__ import print_function __author__ = 'CHENXY' # C++和python类型的映射字典 @@ -48,7 +49,7 @@ def process_typedef(line): else: keyword = content[-1] keyword = keyword.replace(';\n', '') - print content, keyword + print(content, keyword) if '[' in keyword: i = keyword.index('[') @@ -91,15 +92,15 @@ def main(): py_line = process_line(line) if py_line: fpy.write(py_line.decode('gbk').encode('utf-8')) - print n + print(n) fcpp.close() fpy.close() - print u'data_type.py生成过程完成' - except Exception, e: - print u'data_type.py生成过程出错' - print e + print(u'data_type.py生成过程完成') + except Exception as e: + print(u'data_type.py生成过程出错') + print(e) if __name__ == '__main__': diff --git a/vnpy/api/xspeed/pyscript/old/generate_struct.py b/vnpy/api/xspeed/pyscript/old/generate_struct.py index df86652b..60f9662b 100644 --- a/vnpy/api/xspeed/pyscript/old/generate_struct.py +++ b/vnpy/api/xspeed/pyscript/old/generate_struct.py @@ -1,5 +1,6 @@ # encoding: UTF-8 +from __future__ import print_function __author__ = 'CHENXY' from ksgold_data_type import * @@ -47,7 +48,7 @@ def main(): n = line.index('//') line = line[:n] - print no, ':', line + print(no, ':', line) content = line.split('\t') diff --git a/vnpy/api/xspeed/test/xspeedmdtest.py b/vnpy/api/xspeed/test/xspeedmdtest.py index 44484002..08a6eb51 100644 --- a/vnpy/api/xspeed/test/xspeedmdtest.py +++ b/vnpy/api/xspeed/test/xspeedmdtest.py @@ -1,5 +1,6 @@ # encoding: UTF-8 +from __future__ import print_function import sys from time import sleep import datetime @@ -13,7 +14,7 @@ from vnxspeedmd import * def print_dict(d): """按照键值打印一个字典""" for key,value in d.items(): - print key + ':' + str(value) + print(key + ':' + str(value)) def parseDateTime(date,time,milli): @@ -32,8 +33,8 @@ def parseDateTime(date,time,milli): def simple_log(func): """简单装饰器用于输出函数名""" def wrapper(*args, **kw): - print "" - print str(func.__name__) + print("") + print(str(func.__name__)) return func(*args, **kw) return wrapper @@ -57,7 +58,7 @@ class TestMdApi(MdApi): @simple_log def onFrontDisconnected(self, n): """服务器断开""" - print n + print(n) #---------------------------------------------------------------------- @simple_log diff --git a/vnpy/api/xspeed/test/xspeedtdtest.py b/vnpy/api/xspeed/test/xspeedtdtest.py index 6dcedd32..9b69a2c7 100644 --- a/vnpy/api/xspeed/test/xspeedtdtest.py +++ b/vnpy/api/xspeed/test/xspeedtdtest.py @@ -1,5 +1,6 @@ # encoding: UTF-8 +from __future__ import print_function import sys from time import sleep @@ -12,15 +13,15 @@ from vnxspeedtd import * def print_dict(d): """按照键值打印一个字典""" for key,value in d.items(): - print key + ':' + str(value) + print(key + ':' + str(value)) #---------------------------------------------------------------------- def simple_log(func): """简单装饰器用于输出函数名""" def wrapper(*args, **kw): - print "" - print str(func.__name__) + print("") + print(str(func.__name__)) return func(*args, **kw) return wrapper @@ -43,7 +44,7 @@ class TestTdApi(TdApi): @simple_log def onFrontDisconnected(self, n): """服务器断开""" - print n + print(n) #---------------------------------------------------------------------- @simple_log @@ -82,7 +83,7 @@ class TestTdApi(TdApi): """查询持仓""" print_dict(data) print_dict(error) - print last + print(last) #---------------------------------------------------------------------- @simple_log @@ -90,7 +91,7 @@ class TestTdApi(TdApi): """查询持仓""" print_dict(data) print_dict(error) - print last + print(last) #---------------------------------------------------------------------- @simple_log @@ -105,7 +106,7 @@ class TestTdApi(TdApi): """查询持仓""" print_dict(data) print_dict(error) - print last + print(last) #---------------------------------------------------------------------- @simple_log @@ -113,7 +114,7 @@ class TestTdApi(TdApi): """查询持仓""" print_dict(data) print_dict(error) - print last + print(last) #---------------------------------------------------------------------- @simple_log @@ -145,7 +146,7 @@ class TestTdApi(TdApi): """查询持仓""" print_dict(data) print_dict(error) - print last + print(last) #---------------------------------------------------------------------- @simple_log @@ -153,7 +154,7 @@ class TestTdApi(TdApi): """查询持仓""" print_dict(data) print_dict(error) - print last + print(last) #---------------------------------------------------------------------- @simple_log @@ -161,7 +162,7 @@ class TestTdApi(TdApi): """查询持仓""" print_dict(data) print_dict(error) - print last + print(last) #---------------------------------------------------------------------- @simple_log @@ -188,7 +189,7 @@ class TestTdApi(TdApi): """查询持仓""" print_dict(data) print_dict(error) - print last + print(last) #---------------------------------------------------------------------- @simple_log @@ -203,7 +204,7 @@ class TestTdApi(TdApi): """查询持仓""" print_dict(data) print_dict(error) - print last + print(last) #---------------------------------------------------------------------- @simple_log @@ -217,7 +218,7 @@ class TestTdApi(TdApi): """查询持仓""" print_dict(data) print_dict(error) - print last + print(last) #---------------------------------------------------------------------- @simple_log @@ -225,7 +226,7 @@ class TestTdApi(TdApi): """查询持仓""" print_dict(data) print_dict(error) - print last + print(last) #---------------------------------------------------------------------- @simple_log @@ -233,7 +234,7 @@ class TestTdApi(TdApi): """查询持仓""" print_dict(data) print_dict(error) - print last + print(last) #---------------------------------------------------------------------- diff --git a/vnpy/api/xtp/__init__.py b/vnpy/api/xtp/__init__.py index 4978cf9f..792f0dcc 100644 --- a/vnpy/api/xtp/__init__.py +++ b/vnpy/api/xtp/__init__.py @@ -1,5 +1,6 @@ # encoding: UTF-8 -from vnxtpquote import QuoteApi -from vnxtptrader import TraderApi -from xtp_data_type import * \ No newline at end of file +from __future__ import absolute_import +from .vnxtpquote import QuoteApi +from .vnxtptrader import TraderApi +from .xtp_data_type import * \ No newline at end of file diff --git a/vnpy/api/xtp/pyscript/generate_data_type.py b/vnpy/api/xtp/pyscript/generate_data_type.py index e04ae76e..ba72d668 100644 --- a/vnpy/api/xtp/pyscript/generate_data_type.py +++ b/vnpy/api/xtp/pyscript/generate_data_type.py @@ -1,5 +1,6 @@ # encoding: UTF-8 +from __future__ import print_function __author__ = u'用Python的交易员' # C++和python类型的映射字典 @@ -128,7 +129,7 @@ def main(): fcpp.close() fpy.close() - print u'data_type.py生成过程完成' + print(u'data_type.py生成过程完成') if __name__ == '__main__': diff --git a/vnpy/api/xtp/pyscript/generate_md_functions.py b/vnpy/api/xtp/pyscript/generate_md_functions.py index 94aa92e2..54e089be 100644 --- a/vnpy/api/xtp/pyscript/generate_md_functions.py +++ b/vnpy/api/xtp/pyscript/generate_md_functions.py @@ -1,5 +1,6 @@ # encoding: UTF-8 +from __future__ import print_function __author__ = 'CHENXY' from string import join @@ -331,4 +332,4 @@ fheaderon.close() fheaderfunction.close() fwrap.close() -print 'md functions done' \ No newline at end of file +print('md functions done') \ No newline at end of file diff --git a/vnpy/api/xtp/pyscript/generate_struct_oms.py b/vnpy/api/xtp/pyscript/generate_struct_oms.py index b230050c..dbab14ef 100644 --- a/vnpy/api/xtp/pyscript/generate_struct_oms.py +++ b/vnpy/api/xtp/pyscript/generate_struct_oms.py @@ -1,5 +1,6 @@ # encoding: UTF-8 +from __future__ import print_function __author__ = 'CHENXY' from xtp_data_type import * @@ -27,7 +28,7 @@ def main(): fpy.write('\n') for row, line in enumerate(fcpp): - print row + print(row) # 结构体申明注释 if '///' in line and '\t' not in line: py_line = '#' + line[3:] @@ -38,7 +39,7 @@ def main(): # 结构体申明 elif 'struct ' in line: - print line + print(line) content = line.split(' ') name = content[1].replace('\n','') name = name.replace('\r', '') diff --git a/vnpy/api/xtp/pyscript/generate_struct_quote.py b/vnpy/api/xtp/pyscript/generate_struct_quote.py index 0f4e7833..b8ade4d7 100644 --- a/vnpy/api/xtp/pyscript/generate_struct_quote.py +++ b/vnpy/api/xtp/pyscript/generate_struct_quote.py @@ -1,5 +1,6 @@ # encoding: UTF-8 +from __future__ import print_function __author__ = 'CHENXY' from xtp_data_type import * @@ -38,7 +39,7 @@ def main(): lcpp = replaceTabs(fcpp) for n, line in enumerate(lcpp): - print n + print(n) # 结构体申明注释 if '///' in line and '\t' not in line: py_line = '#' + line[3:] diff --git a/vnpy/api/xtp/pyscript/generate_td_functions.py b/vnpy/api/xtp/pyscript/generate_td_functions.py index ede47e4a..7d4081f3 100644 --- a/vnpy/api/xtp/pyscript/generate_td_functions.py +++ b/vnpy/api/xtp/pyscript/generate_td_functions.py @@ -1,5 +1,6 @@ # encoding: UTF-8 +from __future__ import print_function __author__ = 'CHENXY' from string import join @@ -194,7 +195,7 @@ def createProcess(cbName, cbArgsTypeList, cbArgsValueList): fprocess.write(" PyLock lock;\n") onArgsList = [] - print cbName, cbArgsTypeList + print(cbName, cbArgsTypeList) for i, type_ in enumerate(cbArgsTypeList): if 'XTPRI' in type_: @@ -258,7 +259,7 @@ def processFunction(line): fcArgs = fcArgs.replace(')', '') fcArgsList = fcArgs.split(', ') # 将每个参数转化为列表 - print fcArgsList + print(fcArgsList) fcArgsTypeList = [] fcArgsValueList = [] @@ -352,4 +353,4 @@ fheaderon.close() fheaderfunction.close() fwrap.close() -print 'td functions done' \ No newline at end of file +print('td functions done') \ No newline at end of file diff --git a/vnpy/api/xtp/test/quotetest.py b/vnpy/api/xtp/test/quotetest.py index e353940b..deec29e8 100644 --- a/vnpy/api/xtp/test/quotetest.py +++ b/vnpy/api/xtp/test/quotetest.py @@ -1,5 +1,6 @@ # encoding: UTF-8 +from __future__ import print_function import os from time import sleep @@ -8,11 +9,11 @@ from vnxtpquote import * #---------------------------------------------------------------------- def printDict(d): """""" - print '-' * 50 + print('-' * 50) l = d.keys() l.sort() for k in l: - print k, d[k] + print(k, d[k]) @@ -28,32 +29,32 @@ class TestApi(QuoteApi): #---------------------------------------------------------------------- def onDisconnected(self, reason): """""" - print 'disconnect', reason + print('disconnect', reason) #---------------------------------------------------------------------- def onError(self, data): """""" - print 'error' + print('error') printDict(data) #---------------------------------------------------------------------- def onSubMarketData(self, data, error, last): """""" - print 'sub market data' + print('sub market data') printDict(data) printDict(error) #---------------------------------------------------------------------- def onUnSubMarketData(self, data, error, last): """""" - print 'unsub market data' + print('unsub market data') printDict(data) printDict(error) #---------------------------------------------------------------------- def onMarketData(self, data): """""" - print 'new market data' + print('new market data') printDict(data) #---------------------------------------------------------------------- @@ -75,7 +76,7 @@ if __name__ == '__main__': # 登录 n = api.login(ip, port, user, password, 1) - print 'login result', n + print('login result', n) # 订阅行情 api.subscribeMarketData('000001', 2) diff --git a/vnpy/api/xtp/test/tradertest.py b/vnpy/api/xtp/test/tradertest.py index 86f012d3..843e3b8f 100644 --- a/vnpy/api/xtp/test/tradertest.py +++ b/vnpy/api/xtp/test/tradertest.py @@ -1,20 +1,23 @@ # encoding: UTF-8 +from __future__ import print_function import os from time import sleep +from six.moves import input + from vnxtptrader import * + #---------------------------------------------------------------------- def printDict(d): """""" - print '-' * 50 + print('-' * 50) l = d.keys() l.sort() for k in l: - print k, d[k] - - + print(k, d[k]) + ######################################################################## class TestApi(TraderApi): @@ -28,79 +31,79 @@ class TestApi(TraderApi): #---------------------------------------------------------------------- def onDisconnected(self, reason): """""" - print '-' * 30 - print 'onDisconnected' - print reason + print('-' * 30) + print('onDisconnected') + print(reason) #---------------------------------------------------------------------- def onError(self, data): """""" - print '-' * 30 - print 'onError' + print('-' * 30) + print('onError') printDict(data) #---------------------------------------------------------------------- def onOrderEvent(self, data, error): """""" - print '-' * 30 - print 'onOrderEvent' + print('-' * 30) + print('onOrderEvent') printDict(data) printDict(error) #---------------------------------------------------------------------- def onTradeEvent(self, data): """""" - print '-' * 30 - print 'onTradeEvent' + print('-' * 30) + print('onTradeEvent') printDict(data) #---------------------------------------------------------------------- def onCancelOrderError(self, data, error): """""" - print '-' * 30 - print 'onCancelOrderError' + print('-' * 30) + print('onCancelOrderError') printDict(data) printDict(error) #---------------------------------------------------------------------- def onQueryOrder(self, data, error, reqid, last): """""" - print '-' * 30 - print 'onQueryOrder' + print('-' * 30) + print('onQueryOrder') printDict(data) printDict(error) - print reqid - print last + print(reqid) + print(last) #---------------------------------------------------------------------- def onQueryTrade(self, data, error, reqid, last): """""" - print '-' * 30 - print 'onQueryTrade' + print('-' * 30) + print('onQueryTrade') printDict(data) printDict(error) - print reqid - print last + print(reqid) + print(last) #---------------------------------------------------------------------- def onQueryPosition(self, data, error, reqid, last): """""" - print '-' * 30 - print 'onQueryPosition' + print('-' * 30) + print('onQueryPosition') printDict(data) printDict(error) - print reqid - print last + print(reqid) + print(last) #---------------------------------------------------------------------- def onQueryAsset(self, data, error, reqid, last): """""" - print '-' * 30 - print 'onQueryAsset' + print('-' * 30) + print('onQueryAsset') printDict(data) printDict(error) - print reqid - print last + print(reqid) + print(last) @@ -121,12 +124,12 @@ if __name__ == '__main__': # 登录 session = api.login(ip, port, user, password, 1) - print 'login result', session + print('login result', session) # 调用同步函数查询一些信息 - print 'trading day is:', api.getTradingDay() - print 'api version is:', api.getApiVersion() - print 'last error is:', api.getApiLastError() + print('trading day is:', api.getTradingDay()) + print('api version is:', api.getApiVersion()) + print('last error is:', api.getApiLastError()) # 查询资产 sleep(2) @@ -166,7 +169,7 @@ if __name__ == '__main__': # 登出 sleep(5) - print 'logout:', api.logout(session) + print('logout:', api.logout(session)) # 阻塞 - raw_input() + input() diff --git a/vnpy/event/eventEngine.py b/vnpy/event/eventEngine.py index 5d259ee0..dff82489 100644 --- a/vnpy/event/eventEngine.py +++ b/vnpy/event/eventEngine.py @@ -1,6 +1,7 @@ # encoding: UTF-8 # 系统模块 +from __future__ import print_function from queue import Queue, Empty from threading import Thread from time import sleep diff --git a/vnpy/event/eventType.py b/vnpy/event/eventType.py index 0515bcb1..acda0150 100644 --- a/vnpy/event/eventType.py +++ b/vnpy/event/eventType.py @@ -10,6 +10,7 @@ 建议将所有的常量定义放在该文件中,便于检查是否存在重复的现象。 ''' +from __future__ import print_function EVENT_TIMER = 'eTimer' # 计时器事件,每隔1秒发送一次 diff --git a/vnpy/rpc/testClient.py b/vnpy/rpc/testClient.py index d4d03fd3..f1bcf5b9 100644 --- a/vnpy/rpc/testClient.py +++ b/vnpy/rpc/testClient.py @@ -1,9 +1,10 @@ # encoding: UTF-8 from __future__ import print_function +from __future__ import absolute_import from time import sleep -from vnrpc import RpcClient +from .vnrpc import RpcClient ######################################################################## diff --git a/vnpy/rpc/testServer.py b/vnpy/rpc/testServer.py index cb5760b1..0a8a39c2 100644 --- a/vnpy/rpc/testServer.py +++ b/vnpy/rpc/testServer.py @@ -1,9 +1,10 @@ # encoding: UTF-8 from __future__ import print_function +from __future__ import absolute_import from time import sleep, time -from vnrpc import RpcServer +from .vnrpc import RpcServer ######################################################################## diff --git a/vnpy/trader/app/__init__.py b/vnpy/trader/app/__init__.py index dd55d697..e49be298 100644 --- a/vnpy/trader/app/__init__.py +++ b/vnpy/trader/app/__init__.py @@ -1 +1,22 @@ # encoding: UTF-8 + + + +######################################################################## +class AppEngine(object): + """""" + + #---------------------------------------------------------------------- + def __init__(self): + """Constructor""" + pass + + #---------------------------------------------------------------------- + def stop(self): + """""" + raise NotImplementedError + + + + + \ No newline at end of file diff --git a/vnpy/trader/app/algoTrading/__init__.py b/vnpy/trader/app/algoTrading/__init__.py new file mode 100644 index 00000000..0529d815 --- /dev/null +++ b/vnpy/trader/app/algoTrading/__init__.py @@ -0,0 +1,12 @@ +# encoding: UTF-8 + +import os + +from .algoEngine import AlgoEngine +from .uiAlgoManager import AlgoManager + +appName = 'AlgoTrading' +appDisplayName = u'算法交易' +appEngine = AlgoEngine +appWidget = AlgoManager +appIco = 'at.ico' diff --git a/vnpy/trader/app/algoTrading/algo/__init__.py b/vnpy/trader/app/algoTrading/algo/__init__.py new file mode 100644 index 00000000..d6fa977a --- /dev/null +++ b/vnpy/trader/app/algoTrading/algo/__init__.py @@ -0,0 +1,59 @@ +# encoding: UTF-8 + +''' +动态载入所有的策略类 +''' +from __future__ import print_function + +import os +import importlib +import traceback + + +# 用来保存算法类和控件类的字典 +ALGO_DICT = {} +WIDGET_DICT = {} + + +#---------------------------------------------------------------------- +def loadAlgoModule(path, prefix): + """使用importlib动态载入算法""" + for root, subdirs, files in os.walk(path): + for name in files: + # 只有文件名以Algo.py结尾的才是算法文件 + if len(name)>7 and name[-7:] == 'Algo.py': + try: + # 模块名称需要模块路径前缀 + moduleName = prefix + name.replace('.py', '') + module = importlib.import_module(moduleName) + + # 获取算法类和控件类 + algo = None + widget = None + + for k in dir(module): + # 以Algo结尾的类,是算法 + if k[-4:] == 'Algo': + algo = module.__getattribute__(k) + + # 以Widget结尾的类,是控件 + if k[-6:] == 'Widget': + widget = module.__getattribute__(k) + + # 保存到字典中 + if algo and widget: + ALGO_DICT[algo.templateName] = algo + WIDGET_DICT[algo.templateName] = widget + except: + print ('-' * 20) + print ('Failed to import strategy file %s:' %moduleName) + traceback.print_exc() + + +# 遍历algo目录下的文件 +path1 = os.path.abspath(os.path.dirname(__file__)) +loadAlgoModule(path1, 'vnpy.trader.app.algoTrading.algo.') + +# 遍历工作目录下的文件 +path2 = os.getcwd() +loadAlgoModule(path2, '') \ No newline at end of file diff --git a/vnpy/trader/app/algoTrading/algo/arbitrageAlgo.py b/vnpy/trader/app/algoTrading/algo/arbitrageAlgo.py new file mode 100644 index 00000000..220358d2 --- /dev/null +++ b/vnpy/trader/app/algoTrading/algo/arbitrageAlgo.py @@ -0,0 +1,225 @@ +# encoding: UTF-8 + +from __future__ import division +from collections import OrderedDict + +from vnpy.trader.vtConstant import (DIRECTION_LONG, DIRECTION_SHORT, + OFFSET_OPEN, OFFSET_CLOSE, + STATUS_ALLTRADED, STATUS_CANCELLED, + STATUS_REJECTED) +from vnpy.trader.uiQt import QtGui + +from vnpy.trader.app.algoTrading.algoTemplate import AlgoTemplate +from vnpy.trader.app.algoTrading.uiAlgoWidget import AlgoWidget, QtWidgets + + +STATUS_FINISHED = set([STATUS_ALLTRADED, STATUS_CANCELLED, STATUS_REJECTED]) + + +######################################################################## +class ArbitrageAlgo(AlgoTemplate): + """Arbitrage算法,用于套利""" + + templateName = u'Arbitrage 套利' + + #---------------------------------------------------------------------- + def __init__(self, engine, setting, algoName): + """Constructor""" + super(ArbitrageAlgo, self).__init__(engine, setting, algoName) + + # 配置参数 + self.activeVtSymbol = str(setting['activeVtSymbol']) # 主动腿 + self.passiveVtSymbol = str(setting['passiveVtSymbol']) # 被动腿 + + self.spread = float(setting['spread']) # 价差 + self.volume = float(setting['volume']) # 数量 + self.interval = int(setting['interval']) # 间隔 + + self.activeOrderID = '' # 主动委托号 + self.passiveOrderID = '' # 被动委托号 + + self.netPos = 0 # 净持仓 + self.count = 0 # 运行计数 + + # 初始化 + self.subscribe(self.activeVtSymbol) + self.subscribe(self.passiveVtSymbol) + + self.paramEvent() + self.varEvent() + + #---------------------------------------------------------------------- + def onTick(self, tick): + """""" + pass + + #---------------------------------------------------------------------- + def onTrade(self, trade): + """""" + # 更新净持仓数量 + if trade.direction == DIRECTION_LONG: + self.netPos += trade.volume + else: + self.netPos -= trade.volume + + # 如果是主动腿成交则需要执行对冲 + if trade.vtSymbol == self.activeVtSymbol: + self.hedge() + + self.varEvent() + + #---------------------------------------------------------------------- + def onOrder(self, order): + """""" + if order.vtSymbol == self.activeVtSymbol: + if order.status in STATUS_FINISHED: + self.activeOrderID = '' + elif order.vtSymbol == self.passiveVtSymbol: + if order.status in STATUS_FINISHED: + self.passiveOrderID = '' + + self.varEvent() + + #---------------------------------------------------------------------- + def onTimer(self): + """""" + self.count += 1 + if self.count < self.interval: + return + + self.count = 0 + + # 撤单 + if self.activeOrderID or self.passiveOrderID: + self.cancelAll() + return + + # 如果有净仓位则执行对冲 + if self.netPos: + self.hedge() + return + + # 计算价差的bid/ask + activeTick = self.getTick(self.activeVtSymbol) + passiveTick = self.getTick(self.passiveVtSymbol) + + spreadBidPrice = activeTick.bidPrice1 - passiveTick.askPrice1 + spreadAskPrice = activeTick.askPrice1 - passiveTick.bidPrice1 + + spreadBidVolume = min(activeTick.bidVolume1, passiveTick.askVolume1) + spreadAskVolume = min(activeTick.askVolume1, passiveTick.bidVolume1) + + if spreadBidPrice > self.spread: + self.activeOrderID = self.sell(self.activeVtSymbol, activeTick.bidPrice1, spreadBidVolume) + elif spreadAskPrice < - self.spread: + self.activeOrderID = self.buy(self.activeVtSymbol, activeTick.askPrice1, spreadAskVolume) + + # 更新界面 + self.varEvent() + + #---------------------------------------------------------------------- + def onStop(self): + """""" + self.writeLog(u'算法停止') + + self.varEvent() + + #---------------------------------------------------------------------- + def varEvent(self): + """更新变量""" + d = OrderedDict() + d[u'算法状态'] = self.active + d[u'运行计数'] = self.count + d[u'净持仓'] = self.netPos + d[u'主动腿委托号'] = self.activeOrderID + d[u'被动腿委托号'] = self.passiveOrderID + self.putVarEvent(d) + + #---------------------------------------------------------------------- + def paramEvent(self): + """更新参数""" + d = OrderedDict() + d[u'主动腿代码'] = self.activeVtSymbol + d[u'被动腿代码'] = self.passiveVtSymbol + d[u'价差'] = self.spread + d[u'数量'] = self.volume + d[u'间隔'] = self.interval + self.putParamEvent(d) + + #---------------------------------------------------------------------- + def hedge(self): + """""" + tick = self.getTick(self.passiveVtSymbol) + volume = abs(self.netPos) + + if self.netPos > 0: + self.passiveOrderID = self.sell(self.passiveVtSymbol, + tick.bidPrice5, + volume) + elif self.netPos < 0: + self.passiveOrderID = self.buy(self.activeVtSymbol, + tick.askPrice5, + volume) + + + +######################################################################## +class ArbitrageWidget(AlgoWidget): + """""" + + #---------------------------------------------------------------------- + def __init__(self, algoEngine, parent=None): + """Constructor""" + super(ArbitrageWidget, self).__init__(algoEngine, parent) + + self.templateName = ArbitrageAlgo.templateName + + #---------------------------------------------------------------------- + def initAlgoLayout(self): + """""" + self.lineActiveVtSymbol = QtWidgets.QLineEdit() + self.linePassiveVtSymbol = QtWidgets.QLineEdit() + + validator = QtGui.QDoubleValidator() + validator.setBottom(0) + + self.lineSpread = QtWidgets.QLineEdit() + self.lineSpread.setValidator(validator) + + self.lineVolume = QtWidgets.QLineEdit() + self.lineVolume.setValidator(validator) + + intValidator = QtGui.QIntValidator() + intValidator.setBottom(10) + self.lineInterval = QtWidgets.QLineEdit() + self.lineInterval.setValidator(intValidator) + + Label = QtWidgets.QLabel + + grid = QtWidgets.QGridLayout() + grid.addWidget(Label(u'主动腿代码'), 0, 0) + grid.addWidget(self.lineActiveVtSymbol, 0, 1) + grid.addWidget(Label(u'被动腿代码'), 1, 0) + grid.addWidget(self.linePassiveVtSymbol, 1, 1) + grid.addWidget(Label(u'套利价差'), 2, 0) + grid.addWidget(self.lineSpread, 2, 1) + grid.addWidget(Label(u'委托数量'), 3, 0) + grid.addWidget(self.lineVolume, 3, 1) + grid.addWidget(Label(u'运行间隔'), 4, 0) + grid.addWidget(self.lineInterval, 4, 1) + + return grid + + #---------------------------------------------------------------------- + def getAlgoSetting(self): + """""" + setting = OrderedDict() + setting['templateName'] = self.templateName + setting['activeVtSymbol'] = str(self.lineActiveVtSymbol.text()) + setting['passiveVtSymbol'] = str(self.linePassiveVtSymbol.text()) + setting['spread'] = float(self.lineSpread.text()) + setting['volume'] = float(self.lineVolume.text()) + setting['interval'] = int(self.lineInterval.text()) + + return setting + diff --git a/vnpy/trader/app/algoTrading/algo/blAlgo.py b/vnpy/trader/app/algoTrading/algo/blAlgo.py new file mode 100644 index 00000000..8a9bc154 --- /dev/null +++ b/vnpy/trader/app/algoTrading/algo/blAlgo.py @@ -0,0 +1,203 @@ +# encoding: UTF-8 + +from __future__ import division +from collections import OrderedDict + +from six import text_type + +from vnpy.trader.vtConstant import (DIRECTION_LONG, DIRECTION_SHORT, + OFFSET_OPEN, OFFSET_CLOSE, + STATUS_ALLTRADED, STATUS_CANCELLED, STATUS_REJECTED) +from vnpy.trader.uiQt import QtWidgets, QtGui +from vnpy.trader.app.algoTrading.algoTemplate import AlgoTemplate +from vnpy.trader.app.algoTrading.uiAlgoWidget import AlgoWidget + + + +STATUS_FINISHED = set([STATUS_ALLTRADED, STATUS_CANCELLED, STATUS_REJECTED]) + + +######################################################################## +class BlAlgo(AlgoTemplate): + """最优限价单算法""" + + templateName = u'BestLimit 最优限价' + + #---------------------------------------------------------------------- + def __init__(self, engine, setting, algoName): + """Constructor""" + super(BlAlgo, self).__init__(engine, setting, algoName) + + # 参数,强制类型转换,保证从CSV加载的配置正确 + self.vtSymbol = str(setting['vtSymbol']) # 合约代码 + self.direction = text_type(setting['direction']) # 买卖 + self.volume = float(setting['volume']) # 数量 + self.offset = text_type(setting['offset']) # 开平 + + self.lastTick = None # 最新Tick + self.orderPrice = 0 # 委托价格 + self.vtOrderID = '' # 委托号 + self.tradedVolume = 0 # 成交数量 + + self.subscribe(self.vtSymbol) + self.paramEvent() + self.varEvent() + + #---------------------------------------------------------------------- + def onTick(self, tick): + """""" + # 缓存最新行情 + self.lastTick = tick + + # 多头 + if self.direction == DIRECTION_LONG: + # 如果没有委托,则发单 + if not self.vtOrderID: + self.buyBestLimit() + # 如果最新行情买价和委托价格不等,则撤单 + elif self.orderPrice != self.lastTick.bidPrice1: + self.cancelAll() + # 空头 + if self.direction == DIRECTION_SHORT: + if not self.vtOrderID: + self.sellBestLimit() + elif self.orderPrice != self.lastTick.askPrice1: + self.cancelAll() + + # 更新变量 + self.varEvent() + + #---------------------------------------------------------------------- + def onTrade(self, trade): + """""" + self.tradedVolume += trade.volume + + if self.tradedVolume >= self.volume: + self.stop() + + self.varEvent() + + #---------------------------------------------------------------------- + def onOrder(self, order): + """""" + # 若委托已经结束,则清空委托号 + if order.status in STATUS_FINISHED: + self.vtOrderID = '' + self.orderPrice = 0 + + self.varEvent() + + #---------------------------------------------------------------------- + def onTimer(self): + """""" + pass + + #---------------------------------------------------------------------- + def onStop(self): + """""" + self.writeLog(u'停止算法') + self.varEvent() + + #---------------------------------------------------------------------- + def varEvent(self): + """更新变量""" + d = OrderedDict() + d[u'算法状态'] = self.active + d[u'委托价格'] = self.orderPrice + d[u'委托号'] = self.vtOrderID + d[u'成交数量'] = self.tradedVolume + self.putVarEvent(d) + + #---------------------------------------------------------------------- + def paramEvent(self): + """更新参数""" + d = OrderedDict() + d[u'代码'] = self.vtSymbol + d[u'方向'] = self.direction + d[u'数量'] = self.volume + d[u'开平'] = self.offset + self.putParamEvent(d) + + #---------------------------------------------------------------------- + def buyBestLimit(self): + """在买一挂买单""" + orderVolume = self.volume - self.tradedVolume + self.orderPrice = self.lastTick.bidPrice1 + self.vtOrderID = self.buy(self.vtSymbol, self.orderPrice, + orderVolume, offset=self.offset) + + #---------------------------------------------------------------------- + def sellBestLimit(self): + """在卖一挂卖单""" + orderVolume = self.volume - self.tradedVolume + self.orderPrice = self.lastTick.askPrice1 + self.vtOrderID = self.sell(self.vtSymbol, self.orderPrice, + orderVolume, offset=self.offset) + + +######################################################################## +class BlWidget(AlgoWidget): + """""" + + #---------------------------------------------------------------------- + def __init__(self, algoEngine, parent=None): + """Constructor""" + super(BlWidget, self).__init__(algoEngine, parent) + + self.templateName = BlAlgo.templateName + + #---------------------------------------------------------------------- + def initAlgoLayout(self): + """""" + self.lineVtSymbol = QtWidgets.QLineEdit() + + self.comboDirection = QtWidgets.QComboBox() + self.comboDirection.addItem(DIRECTION_LONG) + self.comboDirection.addItem(DIRECTION_SHORT) + self.comboDirection.setCurrentIndex(0) + + doubleValidator = QtGui.QDoubleValidator() + doubleValidator.setBottom(0) + + self.lineVolume = QtWidgets.QLineEdit() + self.lineVolume.setValidator(doubleValidator) + + self.comboOffset = QtWidgets.QComboBox() + self.comboOffset.addItems(['', OFFSET_OPEN, OFFSET_CLOSE]) + self.comboOffset.setCurrentIndex(0) + + buttonStart = QtWidgets.QPushButton(u'启动') + buttonStart.clicked.connect(self.addAlgo) + buttonStart.setMinimumHeight(100) + + Label = QtWidgets.QLabel + + grid = QtWidgets.QGridLayout() + grid.addWidget(Label(u'代码'), 0, 0) + grid.addWidget(self.lineVtSymbol, 0, 1) + grid.addWidget(Label(u'方向'), 1, 0) + grid.addWidget(self.comboDirection, 1, 1) + grid.addWidget(Label(u'数量'), 2, 0) + grid.addWidget(self.lineVolume, 2, 1) + grid.addWidget(Label(u'开平'), 3, 0) + grid.addWidget(self.comboOffset, 3, 1) + + return grid + + #---------------------------------------------------------------------- + def getAlgoSetting(self): + """""" + setting = OrderedDict() + setting['templateName'] = self.templateName + setting['vtSymbol'] = str(self.lineVtSymbol.text()) + setting['direction'] = text_type(self.comboDirection.currentText()) + setting['offset'] = text_type(self.comboOffset.currentText()) + + volumeText = self.lineVolume.text() + if not volumeText: + return + setting['volume'] = float(volumeText) + + return setting + + diff --git a/vnpy/trader/app/algoTrading/algo/dmaAlgo.py b/vnpy/trader/app/algoTrading/algo/dmaAlgo.py new file mode 100644 index 00000000..5b4b73ab --- /dev/null +++ b/vnpy/trader/app/algoTrading/algo/dmaAlgo.py @@ -0,0 +1,189 @@ +# encoding: UTF-8 + +from __future__ import division +from collections import OrderedDict + +from vnpy.trader.vtConstant import (DIRECTION_LONG, DIRECTION_SHORT, + OFFSET_OPEN, OFFSET_CLOSE, + PRICETYPE_LIMITPRICE, PRICETYPE_MARKETPRICE, + STATUS_REJECTED, STATUS_CANCELLED, STATUS_ALLTRADED) +from vnpy.trader.uiQt import QtWidgets +from vnpy.trader.app.algoTrading.algoTemplate import AlgoTemplate +from vnpy.trader.app.algoTrading.uiAlgoWidget import AlgoWidget, QtWidgets + +from six import text_type + + +STATUS_FINISHED = set([STATUS_ALLTRADED, STATUS_CANCELLED, STATUS_REJECTED]) + + +######################################################################## +class DmaAlgo(AlgoTemplate): + """DMA算法,直接发出限价或者市价委托""" + + templateName = u'DMA 直接委托' + + #---------------------------------------------------------------------- + def __init__(self, engine, setting, algoName): + """Constructor""" + super(DmaAlgo, self).__init__(engine, setting, algoName) + + # 参数,强制类型转换,保证从CSV加载的配置正确 + self.vtSymbol = str(setting['vtSymbol']) # 合约代码 + self.direction = text_type(setting['direction']) # 买卖 + self.offset = text_type(setting['offset']) # 开平 + self.priceType = text_type(setting['priceType']) # 价格类型 + self.price = float(setting['price']) # 价格 + self.totalVolume = float(setting['totalVolume']) # 数量 + + self.vtOrderID = '' # 委托号 + self.tradedVolume = 0 # 成交数量 + self.orderStatus = '' # 委托状态 + + self.subscribe(self.vtSymbol) + self.paramEvent() + self.varEvent() + + #---------------------------------------------------------------------- + def onTick(self, tick): + """""" + # 发出委托 + if not self.vtOrderID: + if self.direction == DIRECTION_LONG: + func = self.buy + else: + func = self.sell + + self.vtOrderID = func(self.vtSymbol, self.price, self.totalVolume, + self.priceType, self.offset) + + # 更新变量 + self.varEvent() + + #---------------------------------------------------------------------- + def onTrade(self, trade): + """""" + pass + + #---------------------------------------------------------------------- + def onOrder(self, order): + """""" + self.tradedVolume = order.tradedVolume + self.orderStatus = order.status + + if self.orderStatus in STATUS_FINISHED: + self.stop() + + self.varEvent() + + #---------------------------------------------------------------------- + def onTimer(self): + """""" + pass + + #---------------------------------------------------------------------- + def onStop(self): + """""" + self.writeLog(u'停止算法') + self.varEvent() + + #---------------------------------------------------------------------- + def varEvent(self): + """更新变量""" + d = OrderedDict() + d[u'算法状态'] = self.active + d[u'委托号'] = self.vtOrderID + d[u'成交数量'] = self.tradedVolume + d[u'委托状态'] = self.orderStatus + d['active'] = self.active + self.putVarEvent(d) + + #---------------------------------------------------------------------- + def paramEvent(self): + """更新参数""" + d = OrderedDict() + d[u'代码'] = self.vtSymbol + d[u'方向'] = self.direction + d[u'价格'] = self.price + d[u'数量'] = self.totalVolume + d[u'价格类型'] = self.priceType + d[u'开平'] = self.offset + self.putParamEvent(d) + + +######################################################################## +class DmaWidget(AlgoWidget): + """""" + + #---------------------------------------------------------------------- + def __init__(self, algoEngine, parent=None): + """Constructor""" + super(DmaWidget, self).__init__(algoEngine, parent) + + self.templateName = DmaAlgo.templateName + + #---------------------------------------------------------------------- + def initAlgoLayout(self): + """""" + self.lineSymbol = QtWidgets.QLineEdit() + + self.comboDirection = QtWidgets.QComboBox() + self.comboDirection.addItem(DIRECTION_LONG) + self.comboDirection.addItem(DIRECTION_SHORT) + self.comboDirection.setCurrentIndex(0) + + self.spinPrice = QtWidgets.QDoubleSpinBox() + self.spinPrice.setMinimum(0) + self.spinPrice.setMaximum(1000000000) + self.spinPrice.setDecimals(8) + + self.spinVolume = QtWidgets.QDoubleSpinBox() + self.spinVolume.setMinimum(0) + self.spinVolume.setMaximum(1000000000) + self.spinVolume.setDecimals(6) + + self.comboPriceType = QtWidgets.QComboBox() + self.comboPriceType.addItems([PRICETYPE_LIMITPRICE, PRICETYPE_MARKETPRICE]) + self.comboPriceType.setCurrentIndex(0) + + self.comboOffset = QtWidgets.QComboBox() + self.comboOffset.addItems(['', OFFSET_OPEN, OFFSET_CLOSE]) + self.comboOffset.setCurrentIndex(0) + + buttonStart = QtWidgets.QPushButton(u'启动') + buttonStart.clicked.connect(self.addAlgo) + buttonStart.setMinimumHeight(100) + + Label = QtWidgets.QLabel + + grid = QtWidgets.QGridLayout() + grid.addWidget(Label(u'代码'), 0, 0) + grid.addWidget(self.lineSymbol, 0, 1) + grid.addWidget(Label(u'方向'), 1, 0) + grid.addWidget(self.comboDirection, 1, 1) + grid.addWidget(Label(u'价格'), 2, 0) + grid.addWidget(self.spinPrice, 2, 1) + grid.addWidget(Label(u'数量'), 3, 0) + grid.addWidget(self.spinVolume, 3, 1) + grid.addWidget(Label(u'类型'), 4, 0) + grid.addWidget(self.comboPriceType, 4, 1) + grid.addWidget(Label(u'开平'), 5, 0) + grid.addWidget(self.comboOffset, 5, 1) + + return grid + + #---------------------------------------------------------------------- + def getAlgoSetting(self): + """""" + setting = OrderedDict() + setting['templateName'] = DmaAlgo.templateName + setting['vtSymbol'] = str(self.lineSymbol.text()) + setting['direction'] = text_type(self.comboDirection.currentText()) + setting['price'] = float(self.spinPrice.value()) + setting['totalVolume'] = float(self.spinVolume.value()) + setting['priceType'] = text_type(self.comboPriceType.currentText()) + setting['offset'] = text_type(self.comboOffset.currentText()) + + return setting + + diff --git a/vnpy/trader/app/algoTrading/algo/icebergAlgo.py b/vnpy/trader/app/algoTrading/algo/icebergAlgo.py new file mode 100644 index 00000000..027659a9 --- /dev/null +++ b/vnpy/trader/app/algoTrading/algo/icebergAlgo.py @@ -0,0 +1,229 @@ +# encoding: UTF-8 + +from __future__ import division +from collections import OrderedDict + +from six import text_type + +from vnpy.trader.vtConstant import (DIRECTION_LONG, DIRECTION_SHORT, + OFFSET_OPEN, OFFSET_CLOSE, + STATUS_ALLTRADED, STATUS_CANCELLED, STATUS_REJECTED) +from vnpy.trader.uiQt import QtWidgets, QtGui +from vnpy.trader.app.algoTrading.algoTemplate import AlgoTemplate +from vnpy.trader.app.algoTrading.uiAlgoWidget import AlgoWidget + + + +STATUS_FINISHED = set([STATUS_ALLTRADED, STATUS_CANCELLED, STATUS_REJECTED]) + + +######################################################################## +class IcebergAlgo(AlgoTemplate): + """冰山算法,可用于护盘""" + + templateName = u'Iceberg 冰山' + + #---------------------------------------------------------------------- + def __init__(self, engine, setting, algoName): + """Constructor""" + super(IcebergAlgo, self).__init__(engine, setting, algoName) + + # 参数,强制类型转换,保证从CSV加载的配置正确 + self.vtSymbol = str(setting['vtSymbol']) # 合约代码 + self.direction = text_type(setting['direction']) # 买卖 + self.price = float(setting['price']) # 价格 + self.volume = float(setting['volume']) # 数量 + self.display = float(setting['display']) # 挂出数量 + self.interval = int(setting['interval']) # 间隔 + self.offset = text_type(setting['offset']) # 开平 + + self.count = 0 # 执行计数 + self.vtOrderID = '' # 委托号 + self.tradedVolume = 0 # 成交数量 + + self.subscribe(self.vtSymbol) + self.paramEvent() + self.varEvent() + + #---------------------------------------------------------------------- + def onTick(self, tick): + """""" + pass + + #---------------------------------------------------------------------- + def onTrade(self, trade): + """""" + self.tradedVolume += trade.volume + + if self.tradedVolume >= self.volume: + self.stop() + + self.varEvent() + + #---------------------------------------------------------------------- + def onOrder(self, order): + """""" + # 若委托已经结束,则清空委托号 + if order.status in STATUS_FINISHED: + self.vtOrderID = '' + + self.varEvent() + + #---------------------------------------------------------------------- + def onTimer(self): + """""" + self.count += 1 + + if self.count < self.interval: + self.varEvent() + return + + self.count = 0 + + contract = self.getContract(self.vtSymbol) + if not contract: + self.writeLog(u'找不到合约%s' %self.vtSymbol) + return + + if not self.vtOrderID: + orderVolume = self.volume - self.tradedVolume + orderVolume = min(orderVolume, self.display) + + if self.direction == DIRECTION_LONG: + self.vtOrderID = self.buy(self.vtSymbol, self.price, + orderVolume, offset=self.offset) + else: + self.vtOrderID = self.sell(self.vtSymbol, self.price, + orderVolume, offset=self.offset) + + self.varEvent() + + #---------------------------------------------------------------------- + def onStop(self): + """""" + self.writeLog(u'停止算法') + self.varEvent() + + #---------------------------------------------------------------------- + def varEvent(self): + """更新变量""" + d = OrderedDict() + d[u'算法状态'] = self.active + d[u'运行读秒'] = self.count + d[u'委托号'] = self.vtOrderID + d[u'成交数量'] = self.tradedVolume + self.putVarEvent(d) + + #---------------------------------------------------------------------- + def paramEvent(self): + """更新参数""" + d = OrderedDict() + d[u'代码'] = self.vtSymbol + d[u'方向'] = self.direction + d[u'价格'] = self.price + d[u'数量'] = self.volume + d[u'挂出数量'] = self.display + d[u'运行间隔'] = self.interval + d[u'开平'] = self.offset + self.putParamEvent(d) + + +######################################################################## +class IcebergWidget(AlgoWidget): + """""" + + #---------------------------------------------------------------------- + def __init__(self, algoEngine, parent=None): + """Constructor""" + super(IcebergWidget, self).__init__(algoEngine, parent) + + self.templateName = IcebergAlgo.templateName + + #---------------------------------------------------------------------- + def initAlgoLayout(self): + """""" + self.lineVtSymbol = QtWidgets.QLineEdit() + + self.comboDirection = QtWidgets.QComboBox() + self.comboDirection.addItem(DIRECTION_LONG) + self.comboDirection.addItem(DIRECTION_SHORT) + self.comboDirection.setCurrentIndex(0) + + doubleValidator = QtGui.QDoubleValidator() + doubleValidator.setBottom(0) + + intValidator = QtGui.QIntValidator() + intValidator.setBottom(1) + + self.linePrice = QtWidgets.QLineEdit() + self.linePrice.setValidator(doubleValidator) + + self.lineVolume = QtWidgets.QLineEdit() + self.lineVolume.setValidator(doubleValidator) + + self.lineDisplay = QtWidgets.QLineEdit() + self.lineDisplay.setValidator(doubleValidator) + + self.lineInterval = QtWidgets.QLineEdit() + self.lineInterval.setValidator(intValidator) + + self.comboOffset = QtWidgets.QComboBox() + self.comboOffset.addItems(['', OFFSET_OPEN, OFFSET_CLOSE]) + self.comboOffset.setCurrentIndex(0) + + buttonStart = QtWidgets.QPushButton(u'启动') + buttonStart.clicked.connect(self.addAlgo) + buttonStart.setMinimumHeight(100) + + Label = QtWidgets.QLabel + + grid = QtWidgets.QGridLayout() + grid.addWidget(Label(u'代码'), 0, 0) + grid.addWidget(self.lineVtSymbol, 0, 1) + grid.addWidget(Label(u'方向'), 1, 0) + grid.addWidget(self.comboDirection, 1, 1) + grid.addWidget(Label(u'价格'), 2, 0) + grid.addWidget(self.linePrice, 2, 1) + grid.addWidget(Label(u'数量'), 3, 0) + grid.addWidget(self.lineVolume, 3, 1) + grid.addWidget(Label(u'挂出数量'), 4, 0) + grid.addWidget(self.lineDisplay, 4, 1) + grid.addWidget(Label(u'运行间隔'), 5, 0) + grid.addWidget(self.lineInterval, 5, 1) + grid.addWidget(Label(u'开平'), 6, 0) + grid.addWidget(self.comboOffset, 6, 1) + + return grid + + #---------------------------------------------------------------------- + def getAlgoSetting(self): + """""" + setting = OrderedDict() + setting['templateName'] = self.templateName + setting['vtSymbol'] = str(self.lineVtSymbol.text()) + setting['direction'] = text_type(self.comboDirection.currentText()) + setting['offset'] = text_type(self.comboOffset.currentText()) + + priceText = self.linePrice.text() + if not priceText: + return + setting['price'] = float(priceText) + + volumeText = self.lineVolume.text() + if not volumeText: + return + setting['volume'] = float(volumeText) + + displayText = self.lineDisplay.text() + if not displayText: + return + setting['display'] = float(displayText) + + intervalText = self.lineInterval.text() + if not intervalText: + return + setting['interval'] = int(intervalText) + + return setting + + diff --git a/vnpy/trader/app/algoTrading/algo/sniperAlgo.py b/vnpy/trader/app/algoTrading/algo/sniperAlgo.py new file mode 100644 index 00000000..2153f775 --- /dev/null +++ b/vnpy/trader/app/algoTrading/algo/sniperAlgo.py @@ -0,0 +1,198 @@ +# encoding: UTF-8 + +from __future__ import division +from collections import OrderedDict + +from six import text_type + +from vnpy.trader.vtConstant import (DIRECTION_LONG, DIRECTION_SHORT, + OFFSET_OPEN, OFFSET_CLOSE, + STATUS_ALLTRADED, STATUS_CANCELLED, STATUS_REJECTED) +from vnpy.trader.uiQt import QtWidgets, QtGui +from vnpy.trader.app.algoTrading.algoTemplate import AlgoTemplate +from vnpy.trader.app.algoTrading.uiAlgoWidget import AlgoWidget + + + +STATUS_FINISHED = set([STATUS_ALLTRADED, STATUS_CANCELLED, STATUS_REJECTED]) + + +######################################################################## +class SniperAlgo(AlgoTemplate): + """狙击手算法""" + + templateName = u'Sniper 狙击手' + + #---------------------------------------------------------------------- + def __init__(self, engine, setting, algoName): + """Constructor""" + super(SniperAlgo, self).__init__(engine, setting, algoName) + + # 参数,强制类型转换,保证从CSV加载的配置正确 + self.vtSymbol = str(setting['vtSymbol']) # 合约代码 + self.direction = text_type(setting['direction']) # 买卖 + self.price = float(setting['price']) # 价格 + self.volume = float(setting['volume']) # 数量 + self.offset = text_type(setting['offset']) # 开平 + + self.vtOrderID = '' # 委托号 + self.tradedVolume = 0 # 成交数量 + + self.subscribe(self.vtSymbol) + self.paramEvent() + self.varEvent() + + #---------------------------------------------------------------------- + def onTick(self, tick): + """""" + # 执行撤单 + if self.vtOrderID: + self.cancelAll() + return + + # 做多,且卖1价格小于等于执行目标价 + if (self.direction == DIRECTION_LONG and + tick.askPrice1 <= self.price): + orderVolume = self.volume - self.tradedVolume + orderVolume = min(orderVolume, tick.askVolume1) + self.vtOrderID = self.buy(self.vtSymbol, self.price, + orderVolume, offset=self.offset) + + # 做空 + elif (self.direction == DIRECTION_SHORT and + tick.bidPrice1 >= self.price): + orderVolume = self.volume - self.tradedVolume + orderVolume = min(orderVolume, tick.bidVolume1) + self.vtOrderID = self.sell(self.vtSymbol, self.price, + orderVolume, offset=self.offset) + + # 更新变量 + self.varEvent() + + #---------------------------------------------------------------------- + def onTrade(self, trade): + """""" + self.tradedVolume += trade.volume + + if self.tradedVolume >= self.volume: + self.stop() + + self.varEvent() + + #---------------------------------------------------------------------- + def onOrder(self, order): + """""" + # 若委托已经结束,则清空委托号 + if order.status in STATUS_FINISHED: + self.vtOrderID = '' + + self.varEvent() + + #---------------------------------------------------------------------- + def onTimer(self): + """""" + pass + + #---------------------------------------------------------------------- + def onStop(self): + """""" + self.writeLog(u'停止算法') + self.varEvent() + + #---------------------------------------------------------------------- + def varEvent(self): + """更新变量""" + d = OrderedDict() + d[u'算法状态'] = self.active + d[u'委托号'] = self.vtOrderID + d[u'成交数量'] = self.tradedVolume + self.putVarEvent(d) + + #---------------------------------------------------------------------- + def paramEvent(self): + """更新参数""" + d = OrderedDict() + d[u'代码'] = self.vtSymbol + d[u'方向'] = self.direction + d[u'价格'] = self.price + d[u'数量'] = self.volume + d[u'开平'] = self.offset + self.putParamEvent(d) + + +######################################################################## +class SniperWidget(AlgoWidget): + """""" + + #---------------------------------------------------------------------- + def __init__(self, algoEngine, parent=None): + """Constructor""" + super(SniperWidget, self).__init__(algoEngine, parent) + + self.templateName = SniperAlgo.templateName + + #---------------------------------------------------------------------- + def initAlgoLayout(self): + """""" + self.lineVtSymbol = QtWidgets.QLineEdit() + + self.comboDirection = QtWidgets.QComboBox() + self.comboDirection.addItem(DIRECTION_LONG) + self.comboDirection.addItem(DIRECTION_SHORT) + self.comboDirection.setCurrentIndex(0) + + doubleValidator = QtGui.QDoubleValidator() + doubleValidator.setBottom(0) + + self.linePrice = QtWidgets.QLineEdit() + self.linePrice.setValidator(doubleValidator) + + self.lineVolume = QtWidgets.QLineEdit() + self.lineVolume.setValidator(doubleValidator) + + self.comboOffset = QtWidgets.QComboBox() + self.comboOffset.addItems(['', OFFSET_OPEN, OFFSET_CLOSE]) + self.comboOffset.setCurrentIndex(0) + + buttonStart = QtWidgets.QPushButton(u'启动') + buttonStart.clicked.connect(self.addAlgo) + buttonStart.setMinimumHeight(100) + + Label = QtWidgets.QLabel + + grid = QtWidgets.QGridLayout() + grid.addWidget(Label(u'代码'), 0, 0) + grid.addWidget(self.lineVtSymbol, 0, 1) + grid.addWidget(Label(u'方向'), 1, 0) + grid.addWidget(self.comboDirection, 1, 1) + grid.addWidget(Label(u'价格'), 2, 0) + grid.addWidget(self.linePrice, 2, 1) + grid.addWidget(Label(u'数量'), 3, 0) + grid.addWidget(self.lineVolume, 3, 1) + grid.addWidget(Label(u'开平'), 4, 0) + grid.addWidget(self.comboOffset, 4, 1) + + return grid + + #---------------------------------------------------------------------- + def getAlgoSetting(self): + """""" + setting = OrderedDict() + setting['templateName'] = self.templateName + setting['vtSymbol'] = str(self.lineVtSymbol.text()) + setting['direction'] = text_type(self.comboDirection.currentText()) + setting['offset'] = text_type(self.comboOffset.currentText()) + + priceText = self.linePrice.text() + if not priceText: + return + setting['price'] = float(priceText) + + volumeText = self.lineVolume.text() + if not volumeText: + return + setting['volume'] = float(volumeText) + + return setting + + diff --git a/vnpy/trader/app/algoTrading/algo/stAlgo.py b/vnpy/trader/app/algoTrading/algo/stAlgo.py new file mode 100644 index 00000000..805f4689 --- /dev/null +++ b/vnpy/trader/app/algoTrading/algo/stAlgo.py @@ -0,0 +1,169 @@ +# encoding: UTF-8 + +from __future__ import division +from collections import OrderedDict + +from vnpy.trader.vtConstant import (DIRECTION_LONG, DIRECTION_SHORT, + OFFSET_OPEN, OFFSET_CLOSE) +from vnpy.trader.uiQt import QtWidgets +from vnpy.trader.app.algoTrading.algoTemplate import AlgoTemplate +from vnpy.trader.app.algoTrading.uiAlgoWidget import AlgoWidget, QtWidgets + + + +######################################################################## +class StAlgo(AlgoTemplate): + """自成交算法(self trade),用于刷成交量""" + + templateName = u'SelfTrade 刷单' + + #---------------------------------------------------------------------- + def __init__(self, engine, setting, algoName): + """Constructor""" + super(StAlgo, self).__init__(engine, setting, algoName) + + # 参数,强制类型转换,保证从CSV加载的配置正确 + self.vtSymbol = str(setting['vtSymbol']) # 合约代码 + self.orderVolume = float(setting['orderVolume']) # 委托数量 + self.interval = int(setting['interval']) # 运行间隔 + self.minTickSpread = int(setting['minTickSpread']) # 最小价差 + + self.count = 0 # 定时计数 + self.tradedVolume = 0 # 总成交数量 + + self.subscribe(self.vtSymbol) + self.paramEvent() + self.varEvent() + + #---------------------------------------------------------------------- + def onTick(self, tick): + """""" + pass + + #---------------------------------------------------------------------- + def onTrade(self, trade): + """""" + self.tradedVolume += trade.volume + self.varEvent() + + #---------------------------------------------------------------------- + def onOrder(self, order): + """""" + pass + + #---------------------------------------------------------------------- + def onTimer(self): + """""" + self.count += 1 + if self.count == self.interval: + self.count = 0 + + # 全撤委托 + self.cancelAll() + + # 获取行情 + tick = self.getTick(self.vtSymbol) + if not tick: + return + + contract = self.getContract(self.vtSymbol) + if not contract: + return + + tickSpread = (tick.askPrice1 - tick.bidPrice1) / contract.priceTick + if tickSpread < self.minTickSpread: + self.writeLog(u'当前价差为%s个Tick,小于算法设置%s,不执行刷单' %(tickSpread, self.minTickSpread)) + return + + midPrice = tick.bidPrice1 + contract.priceTick * int(tickSpread/2) + + self.buy(self.vtSymbol, midPrice, self.orderVolume) + self.sell(self.vtSymbol, midPrice, self.orderVolume) + + self.writeLog(u'发出刷单买卖委托,价格:%s,数量:%s' %(midPrice, self.orderVolume)) + + self.varEvent() + + #---------------------------------------------------------------------- + def onStop(self): + """""" + self.writeLog(u'停止算法') + self.varEvent() + + #---------------------------------------------------------------------- + def varEvent(self): + """更新变量""" + d = OrderedDict() + d[u'算法状态'] = self.active + d[u'成交数量'] = self.tradedVolume + d[u'定时计数'] = self.count + d['active'] = self.active + self.putVarEvent(d) + + #---------------------------------------------------------------------- + def paramEvent(self): + """更新参数""" + d = OrderedDict() + d[u'代码'] = self.vtSymbol + d[u'单次委托数量'] = self.orderVolume + d[u'执行间隔'] = self.interval + d[u'最小价差Tick'] = self.minTickSpread + self.putParamEvent(d) + + +######################################################################## +class StWidget(AlgoWidget): + """""" + + #---------------------------------------------------------------------- + def __init__(self, algoEngine, parent=None): + """Constructor""" + super(StWidget, self).__init__(algoEngine, parent) + + self.templateName = StAlgo.templateName + + #---------------------------------------------------------------------- + def initAlgoLayout(self): + """""" + self.lineSymbol = QtWidgets.QLineEdit() + + self.spinVolume = QtWidgets.QDoubleSpinBox() + self.spinVolume.setMinimum(0) + self.spinVolume.setMaximum(1000000000) + self.spinVolume.setDecimals(6) + + self.spinInterval = QtWidgets.QSpinBox() + self.spinInterval.setMinimum(20) + self.spinInterval.setMaximum(3600) + + self.spinMinTickSpread = QtWidgets.QSpinBox() + self.spinMinTickSpread.setMinimum(0) + self.spinMinTickSpread.setMaximum(1000) + + Label = QtWidgets.QLabel + + grid = QtWidgets.QGridLayout() + grid.addWidget(Label(u'代码'), 0, 0) + grid.addWidget(self.lineSymbol, 0, 1) + grid.addWidget(Label(u'单次刷单量'), 1, 0) + grid.addWidget(self.spinVolume, 1, 1) + grid.addWidget(Label(u'执行间隔'), 2, 0) + grid.addWidget(self.spinInterval, 2, 1) + grid.addWidget(Label(u'最小价差Tick'), 3, 0) + grid.addWidget(self.spinMinTickSpread, 3, 1) + + return grid + + #---------------------------------------------------------------------- + def getAlgoSetting(self): + """""" + setting = OrderedDict() + setting['templateName'] = StAlgo.templateName + setting['vtSymbol'] = str(self.lineSymbol.text()) + setting['orderVolume'] = float(self.spinVolume.value()) + setting['interval'] = int(self.spinInterval.value()) + setting['minTickSpread'] = int(self.spinMinTickSpread.value()) + + return setting + + \ No newline at end of file diff --git a/vnpy/trader/app/algoTrading/algo/stopAlgo.py b/vnpy/trader/app/algoTrading/algo/stopAlgo.py new file mode 100644 index 00000000..0985d96b --- /dev/null +++ b/vnpy/trader/app/algoTrading/algo/stopAlgo.py @@ -0,0 +1,211 @@ +# encoding: UTF-8 + +from __future__ import division +from collections import OrderedDict + +from six import text_type + +from vnpy.trader.vtConstant import (DIRECTION_LONG, DIRECTION_SHORT, + OFFSET_OPEN, OFFSET_CLOSE, + STATUS_ALLTRADED, STATUS_CANCELLED, STATUS_REJECTED) +from vnpy.trader.uiQt import QtWidgets +from vnpy.trader.app.algoTrading.algoTemplate import AlgoTemplate +from vnpy.trader.app.algoTrading.uiAlgoWidget import AlgoWidget, QtWidgets + + + +STATUS_FINISHED = set([STATUS_ALLTRADED, STATUS_CANCELLED, STATUS_REJECTED]) + + +######################################################################## +class StopAlgo(AlgoTemplate): + """停止单算法,也可以用于止损单""" + + templateName = u'STOP 条件委托' + + #---------------------------------------------------------------------- + def __init__(self, engine, setting, algoName): + """Constructor""" + super(StopAlgo, self).__init__(engine, setting, algoName) + + # 参数,强制类型转换,保证从CSV加载的配置正确 + self.vtSymbol = str(setting['vtSymbol']) # 合约代码 + self.direction = text_type(setting['direction']) # 买卖 + self.stopPrice = float(setting['stopPrice']) # 触发价格 + self.totalVolume = float(setting['totalVolume']) # 数量 + self.offset = text_type(setting['offset']) # 开平 + self.priceAdd = float(setting['priceAdd']) # 下单时的超价 + + self.vtOrderID = '' # 委托号 + self.tradedVolume = 0 # 成交数量 + self.orderStatus = '' # 委托状态 + + self.subscribe(self.vtSymbol) + self.paramEvent() + self.varEvent() + + #---------------------------------------------------------------------- + def onTick(self, tick): + """""" + # 如果已经发出委托,则忽略行情事件 + if self.vtOrderID: + return + + # 如果到达止损位,才触发委托 + if (self.direction == DIRECTION_LONG and + tick.lastPrice >= self.stopPrice): + # 计算超价委托价格 + price = self.stopPrice + self.priceAdd + + # 避免价格超过涨停价 + if tick.upperLimit: + price = min(price, tick.upperLimit) + + func = self.buy + else: + price = self.stopPrice - self.priceAdd + + if tick.lowerLimit: + price = max(price, tick.lowerLimit) + + func = self.sell + + self.vtOrderID = func(self.vtSymbol, price, self.volume, offset=self.offset) + + msg = u'停止单已触发,代码:%s,方向:%s, 价格:%s,数量:%s,开平:%s' %(self.vtSymbol, + self.direction, + self.stopPrice, + self.totalVolume, + self.offset) + self.writeLog(msg) + + # 更新变量 + self.varEvent() + + #---------------------------------------------------------------------- + def onTrade(self, trade): + """""" + pass + + #---------------------------------------------------------------------- + def onOrder(self, order): + """""" + self.tradedVolume = order.tradedVolume + self.orderStatus = order.status + + if self.orderStatus in STATUS_FINISHED: + self.stop() + + self.varEvent() + + #---------------------------------------------------------------------- + def onTimer(self): + """""" + pass + + #---------------------------------------------------------------------- + def onStop(self): + """""" + self.writeLog(u'停止算法') + self.varEvent() + + #---------------------------------------------------------------------- + def varEvent(self): + """更新变量""" + d = OrderedDict() + d[u'算法状态'] = self.active + d[u'委托号'] = self.vtOrderID + d[u'成交数量'] = self.tradedVolume + d[u'委托状态'] = self.orderStatus + d['active'] = self.active + self.putVarEvent(d) + + #---------------------------------------------------------------------- + def paramEvent(self): + """更新参数""" + d = OrderedDict() + d[u'代码'] = self.vtSymbol + d[u'方向'] = self.direction + d[u'触发价格'] = self.stopPrice + d[u'数量'] = self.totalVolume + d[u'开平'] = self.offset + self.putParamEvent(d) + + +######################################################################## +class StopWidget(AlgoWidget): + """""" + + #---------------------------------------------------------------------- + def __init__(self, algoEngine, parent=None): + """Constructor""" + super(StopWidget, self).__init__(algoEngine, parent) + + self.templateName = StopAlgo.templateName + + #---------------------------------------------------------------------- + def initAlgoLayout(self): + """""" + self.lineSymbol = QtWidgets.QLineEdit() + + self.comboDirection = QtWidgets.QComboBox() + self.comboDirection.addItem(DIRECTION_LONG) + self.comboDirection.addItem(DIRECTION_SHORT) + self.comboDirection.setCurrentIndex(0) + + self.spinPrice = QtWidgets.QDoubleSpinBox() + self.spinPrice.setMinimum(0) + self.spinPrice.setMaximum(1000000000) + self.spinPrice.setDecimals(8) + + self.spinVolume = QtWidgets.QDoubleSpinBox() + self.spinVolume.setMinimum(0) + self.spinVolume.setMaximum(1000000000) + self.spinVolume.setDecimals(6) + + self.comboOffset = QtWidgets.QComboBox() + self.comboOffset.addItems(['', OFFSET_OPEN, OFFSET_CLOSE]) + self.comboOffset.setCurrentIndex(0) + + self.spinPriceAdd = QtWidgets.QDoubleSpinBox() + self.spinPriceAdd.setMinimum(0) + self.spinPriceAdd.setMaximum(1000000000) + self.spinPriceAdd.setDecimals(8) + + buttonStart = QtWidgets.QPushButton(u'启动') + buttonStart.clicked.connect(self.addAlgo) + buttonStart.setMinimumHeight(100) + + Label = QtWidgets.QLabel + + grid = QtWidgets.QGridLayout() + grid.addWidget(Label(u'代码'), 0, 0) + grid.addWidget(self.lineSymbol, 0, 1) + grid.addWidget(Label(u'方向'), 1, 0) + grid.addWidget(self.comboDirection, 1, 1) + grid.addWidget(Label(u'价格'), 2, 0) + grid.addWidget(self.spinPrice, 2, 1) + grid.addWidget(Label(u'数量'), 3, 0) + grid.addWidget(self.spinVolume, 3, 1) + grid.addWidget(Label(u'开平'), 4, 0) + grid.addWidget(self.comboOffset, 4, 1) + grid.addWidget(Label(u'超价'), 5, 0) + grid.addWidget(self.spinPriceAdd, 5, 1) + + return grid + + #---------------------------------------------------------------------- + def getAlgoSetting(self): + """""" + setting = OrderedDict() + setting['templateName'] = StopAlgo.templateName + setting['vtSymbol'] = str(self.lineSymbol.text()) + setting['direction'] = text_type(self.comboDirection.currentText()) + setting['stopPrice'] = float(self.spinPrice.value()) + setting['totalVolume'] = float(self.spinVolume.value()) + setting['offset'] = text_type(self.comboOffset.currentText()) + setting['priceAdd'] = float(self.spinPriceAdd.value()) + + return setting + + diff --git a/vnpy/trader/app/algoTrading/algo/twapAlgo.py b/vnpy/trader/app/algoTrading/algo/twapAlgo.py new file mode 100644 index 00000000..e01cf567 --- /dev/null +++ b/vnpy/trader/app/algoTrading/algo/twapAlgo.py @@ -0,0 +1,265 @@ +# encoding: UTF-8 + +from __future__ import division +from collections import OrderedDict + +from six import text_type + +from vnpy.trader.vtConstant import (DIRECTION_LONG, DIRECTION_SHORT) +from vnpy.trader.uiQt import QtWidgets +from vnpy.trader.app.algoTrading.algoTemplate import AlgoTemplate +from vnpy.trader.app.algoTrading.uiAlgoWidget import AlgoWidget, QtWidgets + + +######################################################################## +class TwapAlgo(AlgoTemplate): + """TWAP算法""" + + templateName = u'TWAP 时间加权平均' + + #---------------------------------------------------------------------- + def __init__(self, engine, setting, algoName): + """Constructor""" + super(TwapAlgo, self).__init__(engine, setting, algoName) + + # 参数,强制类型转换,保证从CSV加载的配置正确 + self.vtSymbol = str(setting['vtSymbol']) # 合约代码 + self.direction = text_type(setting['direction']) # 买卖 + self.targetPrice = float(setting['targetPrice']) # 目标价格 + self.totalVolume = float(setting['totalVolume']) # 总数量 + self.time = int(setting['time']) # 执行时间 + self.interval = int(setting['interval']) # 执行间隔 + self.minVolume = float(setting['minVolume']) # 最小委托数量 + self.priceLevel = int(setting['priceLevel']) # 使用第几档价格委托 + + # 变量 + self.orderSize = self.totalVolume / (self.time / self.interval) + self.orderSize = self.roundValue(self.orderSize, self.minVolume) + if self.minVolume >= 1: + self.orderSize = int(self.orderSize) + + self.timerCount = 0 + self.timerTotal = 0 + self.tradedVolume = 0 + + self.subscribe(self.vtSymbol) + self.paramEvent() + self.varEvent() + + #---------------------------------------------------------------------- + def onTick(self, tick): + """""" + pass + + #---------------------------------------------------------------------- + def onTrade(self, trade): + """""" + self.tradedVolume += trade.volume + + if self.tradedVolume >= self.totalVolume: + self.stop() + else: + self.varEvent() + + #---------------------------------------------------------------------- + def onOrder(self, order): + """""" + pass + + #---------------------------------------------------------------------- + def onTimer(self): + """""" + self.timerCount += 1 + self.timerTotal += 1 + + # 总时间结束,停止算法 + if self.timerTotal >= self.time: + self.stop() + return + + # 每到间隔发一次委托 + if self.timerCount >= self.interval: + self.timerCount = 0 + + tick = self.getTick(self.vtSymbol) + if not tick: + return + + size = min(self.orderSize, self.totalVolume-self.tradedVolume) + + # 买入 + if self.direction == DIRECTION_LONG: + # 市场买1价小于目标买价 + if tick.bidPrice1 < self.targetPrice: + # 计算委托价格 + priceMap = { + 1: tick.askPrice1, + 2: tick.askPrice2, + 3: tick.askPrice3, + 4: tick.askPrice4, + 5: tick.askPrice5, + } + price = priceMap[self.priceLevel] + if price: + price = min(price, self.targetPrice) # 如果深度价格为0,则使用目标价 + else: + price = self.targetPrice + + # 发出委托 + self.buy(self.vtSymbol, price, size) + self.writeLog(u'委托买入%s,数量%s,价格%s' %(self.vtSymbol, self.orderSize, price)) + # 卖出 + if self.direction == DIRECTION_SHORT: + # 市场卖1价大于目标价 + if tick.askPrice1 > self.targetPrice: + # 计算委托价格 + priceMap = { + 1: tick.bidPrice1, + 2: tick.bidPrice2, + 3: tick.bidPrice3, + 4: tick.bidPrice4, + 5: tick.bidPrice5, + } + price = priceMap[self.priceLevel] + if price: + price = max(price, self.targetPrice) + else: + price = self.targetPrice + + # 发出委托 + self.sell(self.vtSymbol, price, size) + self.writeLog(u'委托卖出%s,数量%s,价格%s' %(self.vtSymbol, self.orderSize, price)) + + # 委托后等待到间隔一半的时间撤单 + elif self.timerCount == round(self.interval/2, 0): + result = self.cancelAll() + if result: + self.writeLog(u'撤销之前的委托') + + self.varEvent() + + #---------------------------------------------------------------------- + def onStop(self): + """""" + self.writeLog(u'运行时间已到,停止算法') + self.varEvent() + + #---------------------------------------------------------------------- + def varEvent(self): + """更新变量""" + d = OrderedDict() + d[u'算法状态'] = self.active + d[u'成交数量'] = self.tradedVolume + d[u'单笔委托'] = self.orderSize + d[u'本轮读秒'] = self.timerCount + d[u'累计读秒'] = self.timerTotal + d['active'] = self.active + self.putVarEvent(d) + + #---------------------------------------------------------------------- + def paramEvent(self): + """更新参数""" + d = OrderedDict() + d[u'代码'] = self.vtSymbol + d[u'方向'] = self.direction + d[u'目标价格'] = self.targetPrice + d[u'总数量'] = self.totalVolume + d[u'总时间(秒)'] = self.time + d[u'间隔(秒)'] = self.interval + d[u'委托档位'] = self.priceLevel + self.putParamEvent(d) + + +######################################################################## +class TwapWidget(AlgoWidget): + """""" + + #---------------------------------------------------------------------- + def __init__(self, algoEngine, parent=None): + """Constructor""" + super(TwapWidget, self).__init__(algoEngine, parent) + + self.templateName = TwapAlgo.templateName + + #---------------------------------------------------------------------- + def initAlgoLayout(self): + """""" + self.lineSymbol = QtWidgets.QLineEdit() + + self.comboDirection = QtWidgets.QComboBox() + self.comboDirection.addItem(DIRECTION_LONG) + self.comboDirection.addItem(DIRECTION_SHORT) + self.comboDirection.setCurrentIndex(0) + + self.spinPrice = QtWidgets.QDoubleSpinBox() + self.spinPrice.setMinimum(0) + self.spinPrice.setMaximum(1000000000) + self.spinPrice.setDecimals(8) + + self.spinVolume = QtWidgets.QDoubleSpinBox() + self.spinVolume.setMinimum(0) + self.spinVolume.setMaximum(1000000000) + self.spinVolume.setDecimals(6) + + self.spinTime = QtWidgets.QSpinBox() + self.spinTime.setMinimum(30) + self.spinTime.setMaximum(86400) + + self.spinInterval = QtWidgets.QSpinBox() + self.spinInterval.setMinimum(10) + self.spinInterval.setMaximum(3600) + + self.spinMinVolume = QtWidgets.QDoubleSpinBox() + self.spinMinVolume.setMinimum(0) + self.spinMinVolume.setMaximum(10000) + self.spinMinVolume.setDecimals(6) + self.spinMinVolume.setValue(1) + + self.spinPriceLevel = QtWidgets.QSpinBox() + self.spinPriceLevel.setMinimum(1) + self.spinPriceLevel.setMaximum(5) + self.spinPriceLevel.setValue(1) + + buttonStart = QtWidgets.QPushButton(u'启动') + buttonStart.clicked.connect(self.addAlgo) + buttonStart.setMinimumHeight(100) + + Label = QtWidgets.QLabel + + grid = QtWidgets.QGridLayout() + grid.addWidget(Label(u'交易代码'), 0, 0) + grid.addWidget(self.lineSymbol, 0, 1) + grid.addWidget(Label(u'方向'), 1, 0) + grid.addWidget(self.comboDirection, 1, 1) + grid.addWidget(Label(u'目标价格'), 2, 0) + grid.addWidget(self.spinPrice, 2, 1) + grid.addWidget(Label(u'总数量'), 3, 0) + grid.addWidget(self.spinVolume, 3, 1) + grid.addWidget(Label(u'总时间(秒)'), 4, 0) + grid.addWidget(self.spinTime, 4, 1) + grid.addWidget(Label(u'间隔(秒)'), 5, 0) + grid.addWidget(self.spinInterval, 5, 1) + grid.addWidget(Label(u'委托档位'), 6, 0) + grid.addWidget(self.spinPriceLevel, 6, 1) + grid.addWidget(Label(u'数量取整'), 7, 0) + grid.addWidget(self.spinMinVolume, 7, 1) + + return grid + + #---------------------------------------------------------------------- + def getAlgoSetting(self): + """""" + setting = OrderedDict() + setting['templateName'] = TwapAlgo.templateName + setting['vtSymbol'] = str(self.lineSymbol.text()) + setting['direction'] = str(self.comboDirection.currentText()) + setting['targetPrice'] = float(self.spinPrice.value()) + setting['totalVolume'] = float(self.spinVolume.value()) + setting['time'] = int(self.spinTime.value()) + setting['interval'] = int(self.spinInterval.value()) + setting['priceLevel'] = int(self.spinPriceLevel.value()) + setting['minVolume'] = float(self.spinMinVolume.value()) + + return setting + + diff --git a/vnpy/trader/app/algoTrading/algoEngine.py b/vnpy/trader/app/algoTrading/algoEngine.py new file mode 100644 index 00000000..b3d855dc --- /dev/null +++ b/vnpy/trader/app/algoTrading/algoEngine.py @@ -0,0 +1,366 @@ +# encoding: UTF-8 + +''' +算法交易引擎 +''' + +from __future__ import division +import os +import importlib + +from vnpy.event import Event +from vnpy.rpc import RpcServer +from vnpy.trader.vtEvent import EVENT_TIMER, EVENT_TICK, EVENT_ORDER, EVENT_TRADE +from vnpy.trader.vtConstant import (DIRECTION_LONG, DIRECTION_SHORT, + PRICETYPE_LIMITPRICE, PRICETYPE_MARKETPRICE, + OFFSET_OPEN, OFFSET_CLOSE, + OFFSET_CLOSETODAY, OFFSET_CLOSEYESTERDAY) +from vnpy.trader.vtObject import VtSubscribeReq, VtOrderReq, VtCancelOrderReq, VtLogData + +from .algo import ALGO_DICT + + +EVENT_ALGO_LOG = 'eAlgoLog' # 算法日志事件 +EVENT_ALGO_PARAM = 'eAlgoParam' # 算法参数事件 +EVENT_ALGO_VAR = 'eAlgoVar' # 算法变量事件 +EVENT_ALGO_SETTING = 'eAlgoSetting' # 算法配置事件 + +ALGOTRADING_DB_NAME = 'VnTrader_AlgoTrading_Db' # AlgoTrading数据库名 + +SETTING_COLLECTION_NAME = 'AlgoSetting' # 算法配置集合名 +HISTORY_COLLECTION_NAME = 'AlgoHistory' # 算法历史集合名 + + +######################################################################## +class AlgoEngine(object): + """算法交易引擎""" + + #---------------------------------------------------------------------- + def __init__(self, mainEngine, eventEngine): + """""" + self.mainEngine = mainEngine + self.eventEngine = eventEngine + self.rpcServer = None + + self.algoDict = {} # algoName:algo + self.orderAlgoDict = {} # vtOrderID:algo + self.symbolAlgoDict = {} # vtSymbol:algo set + self.settingDict = {} # settingName:setting + self.historyDict = {} # algoName:dict + + self.registerEvent() + + #---------------------------------------------------------------------- + def registerEvent(self): + """注册事件监听""" + self.eventEngine.register(EVENT_TICK, self.processTickEvent) + self.eventEngine.register(EVENT_TIMER, self.processTimerEvent) + self.eventEngine.register(EVENT_ORDER, self.processOrderEvent) + self.eventEngine.register(EVENT_TRADE, self.processTradeEvent) + + #---------------------------------------------------------------------- + def stop(self): + """停止""" + if self.rpcServer: + self.rpcServer.stop() + + #---------------------------------------------------------------------- + def processTickEvent(self, event): + """行情事件""" + tick = event.dict_['data'] + + l = self.symbolAlgoDict.get(tick.vtSymbol, None) + if l: + for algo in l: + algo.updateTick(tick) + + #---------------------------------------------------------------------- + def processOrderEvent(self, event): + """委托事件""" + order = event.dict_['data'] + + algo = self.orderAlgoDict.get(order.vtOrderID, None) + if algo: + algo.updateOrder(order) + + #---------------------------------------------------------------------- + def processTradeEvent(self, event): + """成交事件""" + trade = event.dict_['data'] + + algo = self.orderAlgoDict.get(trade.vtOrderID, None) + if algo: + algo.updateTrade(trade) + + #---------------------------------------------------------------------- + def processTimerEvent(self, event): + """定时事件""" + for algo in self.algoDict.values(): + algo.updateTimer() + + #---------------------------------------------------------------------- + def addAlgo(self, algoSetting): + """新增算法""" + templateName = algoSetting['templateName'] + algoClass = ALGO_DICT[templateName] + algo = algoClass.new(self, algoSetting) + + self.algoDict[algo.algoName] = algo + + return algo.algoName + + #---------------------------------------------------------------------- + def stopAlgo(self, algoName): + """停止算法""" + if algoName in self.algoDict: + self.algoDict[algoName].stop() + del self.algoDict[algoName] + + #---------------------------------------------------------------------- + def stopAll(self): + """全部停止""" + l = self.algoDict.keys() + for algoName in l: + self.stopAlgo(algoName) + + #---------------------------------------------------------------------- + def subscribe(self, algo, vtSymbol): + """""" + contract = self.mainEngine.getContract(vtSymbol) + if not contract: + self.writeLog(u'%s订阅行情失败,找不到合约%s' %(algo.algoName, vtSymbol)) + return + + # 如果vtSymbol已存在于字典,说明已经订阅过 + if vtSymbol in self.symbolAlgoDict: + s = self.symbolAlgoDict[vtSymbol] + s.add(algo) + return + # 否则需要添加到字典中并执行订阅 + else: + s = set() + self.symbolAlgoDict[vtSymbol] = s + s.add(algo) + + req = VtSubscribeReq() + req.symbol = contract.symbol + req.exchange = contract.exchange + self.mainEngine.subscribe(req, contract.gatewayName) + + #---------------------------------------------------------------------- + def sendOrder(self, algo, vtSymbol, direction, price, volume, + priceType=None, offset=None): + """发单""" + contract = self.mainEngine.getContract(vtSymbol) + if not contract: + self.writeLog(u'%s委托下单失败,找不到合约:%s' %(algo.algoName, vtSymbol)) + + req = VtOrderReq() + req.vtSymbol = vtSymbol + req.symbol = contract.symbol + req.exchange = contract.exchange + req.direction = direction + req.offset = OFFSET_CLOSETODAY + req.price = price + req.volume = volume + + if priceType: + req.priceType = priceType + else: + req.priceType = PRICETYPE_LIMITPRICE + + if offset: + req.offset = offset + else: + req.offset = OFFSET_OPEN + + vtOrderID = self.mainEngine.sendOrder(req, contract.gatewayName) + self.orderAlgoDict[vtOrderID] = algo + + return vtOrderID + + #---------------------------------------------------------------------- + def buy(self, algo, vtSymbol, price, volume, priceType=None, offset=None): + """买入""" + return self.sendOrder(algo, vtSymbol, DIRECTION_LONG, price, volume, priceType, offset) + + #---------------------------------------------------------------------- + def sell(self, algo, vtSymbol, price, volume, priceType=None, offset=None): + """卖出""" + return self.sendOrder(algo, vtSymbol, DIRECTION_SHORT, price, volume, priceType, offset) + + #---------------------------------------------------------------------- + def cancelOrder(self, algo, vtOrderID): + """撤单""" + order = self.mainEngine.getOrder(vtOrderID) + if not order: + self.writeLog(u'%s委托撤单失败,找不到委托:%s' %(algo.algoName, vtOrderID)) + return + + req = VtCancelOrderReq() + req.symbol = order.symbol + req.exchange = order.exchange + req.orderID = order.orderID + req.frontID = order.frontID + req.sessionID = order.sessionID + self.mainEngine.cancelOrder(req, order.gatewayName) + + #---------------------------------------------------------------------- + def writeLog(self, content, algo=None): + """输出日志""" + log = VtLogData() + log.logContent = content + + if algo: + log.gatewayName = algo.algoName + + event = Event(EVENT_ALGO_LOG) + event.dict_['data'] = log + self.eventEngine.put(event) + + #---------------------------------------------------------------------- + def putVarEvent(self, algo, d): + """更新变量""" + algoName = algo.algoName + + d['algoName'] = algoName + event = Event(EVENT_ALGO_VAR) + event.dict_['data'] = d + self.eventEngine.put(event) + + # RPC推送 + if self.rpcServer: + self.rpcServer.publish('AlgoTrading', event) + + # 保存数据到数据库 + history = self.historyDict.setdefault(algoName, {}) + history['algoName'] = algoName + history['var'] = d + + self.mainEngine.dbUpdate(ALGOTRADING_DB_NAME, + HISTORY_COLLECTION_NAME, + history, + {'algoName': algoName}, + True) + + + #---------------------------------------------------------------------- + def putParamEvent(self, algo, d): + """更新参数""" + algoName = algo.algoName + + d['algoName'] = algoName + event = Event(EVENT_ALGO_PARAM) + event.dict_['data'] = d + self.eventEngine.put(event) + + # RPC推送 + if self.rpcServer: + self.rpcServer.publish('AlgoTrading', event) + + # 保存数据到数据库 + history = self.historyDict.setdefault(algoName, {}) + history['algoName'] = algoName + history['param'] = d + + self.mainEngine.dbUpdate(ALGOTRADING_DB_NAME, + HISTORY_COLLECTION_NAME, + history, + {'algoName': algoName}, + True) + + #---------------------------------------------------------------------- + def getTick(self, algo, vtSymbol): + """查询行情""" + tick = self.mainEngine.getTick(vtSymbol) + if not tick: + self.writeLog(u'%s查询行情失败,找不到报价:%s' %(algo.algoName, vtSymbol)) + return + + return tick + + #---------------------------------------------------------------------- + def getContract(self, algo, vtSymbol): + """查询合约""" + contract = self.mainEngine.getContract(vtSymbol) + if not contract: + self.writeLog(u'%s查询合约失败,找不到报价:%s' %(algo.algoName, vtSymbol)) + return + + return contract + + #---------------------------------------------------------------------- + def saveAlgoSetting(self, algoSetting): + """保存算法配置""" + settingName = algoSetting['settingName'] + self.settingDict[settingName] = algoSetting + + self.mainEngine.dbUpdate(ALGOTRADING_DB_NAME, + SETTING_COLLECTION_NAME, + algoSetting, + {'settingName': settingName}, + True) + + self.putSettingEvent(settingName, algoSetting) + + #---------------------------------------------------------------------- + def loadAlgoSetting(self): + """加载算法配置""" + l = self.mainEngine.dbQuery(ALGOTRADING_DB_NAME, + SETTING_COLLECTION_NAME, + {}, + 'templateName') + for algoSetting in l: + settingName = algoSetting['settingName'] + self.settingDict[settingName] = algoSetting + self.putSettingEvent(settingName, algoSetting) + + #---------------------------------------------------------------------- + def deleteAlgoSetting(self, algoSetting): + """删除算法配置""" + settingName = algoSetting['settingName'] + + del self.settingDict[settingName] + self.mainEngine.dbDelete(ALGOTRADING_DB_NAME, + SETTING_COLLECTION_NAME, + {'settingName': settingName}) + + self.putSettingEvent(settingName, {}) + + #---------------------------------------------------------------------- + def putSettingEvent(self, settingName, algoSetting): + """发出算法配置更新事件""" + algoSetting['settingName'] = settingName + + event = Event(EVENT_ALGO_SETTING) + event.dict_['data'] = algoSetting + self.eventEngine.put(event) + + #---------------------------------------------------------------------- + def startRpc(self, repPort, pubPort): + """启动RPC服务""" + if self.rpcServer: + return + + self.rpcServer = AlgoRpcServer(self, repPort, pubPort) + self.rpcServer.start() + self.writeLog(u'算法交易RPC服务启动成功,REP端口:%s,PUB端口:%s' %(repPort, pubPort)) + + +######################################################################## +class AlgoRpcServer(RpcServer): + """算法交易RPC服务器""" + + #---------------------------------------------------------------------- + def __init__(self, engine, repPort, pubPort): + """Constructor""" + self.engine = engine + repAddress = 'tcp://*:%s' %repPort + pubAddress = 'tcp://*:%s' %pubPort + + super(AlgoRpcServer, self).__init__(repAddress, pubAddress) + + self.register(self.engine.addAlgo) + self.register(self.engine.stopAlgo) + self.register(self.engine.stopAll) + \ No newline at end of file diff --git a/vnpy/trader/app/algoTrading/algoTemplate.py b/vnpy/trader/app/algoTrading/algoTemplate.py new file mode 100644 index 00000000..442dedf1 --- /dev/null +++ b/vnpy/trader/app/algoTrading/algoTemplate.py @@ -0,0 +1,182 @@ +# encoding: UTF-8 + +from __future__ import division +from datetime import datetime + +from vnpy.trader.vtConstant import STATUS_NOTTRADED, STATUS_PARTTRADED, STATUS_UNKNOWN + + +# 活动委托状态 +STATUS_ACTIVE = [STATUS_NOTTRADED, STATUS_PARTTRADED, STATUS_UNKNOWN] + + +######################################################################## +class AlgoTemplate(object): + """算法模板""" + templateName = 'AlgoTemplate' + + timestamp = '' + count = 0 + + @classmethod + #---------------------------------------------------------------------- + def new(cls, engine, setting): + """创建新对象""" + timestamp = datetime.now().strftime('%Y%m%d%H%M%S') + if timestamp != cls.timestamp: + cls.timestamp = timestamp + cls.count = 0 + else: + cls.count += 1 + + algoName = '_'.join([cls.templateName, cls.timestamp, str(cls.count)]) + algo = cls(engine, setting, algoName) + return algo + + #---------------------------------------------------------------------- + def __init__(self, engine, setting, algoName): + """Constructor""" + self.engine = engine + self.active = True + self.algoName = algoName + self.activeOrderDict = {} # vtOrderID:order + + #---------------------------------------------------------------------- + def updateTick(self, tick): + """""" + if not self.active: + return + + self.onTick(tick) + + #---------------------------------------------------------------------- + def updateTrade(self, trade): + """""" + if not self.active: + return + + self.onTrade(trade) + + #---------------------------------------------------------------------- + def updateOrder(self, order): + """""" + if not self.active: + return + + # 活动委托需要缓存 + if order.status in STATUS_ACTIVE: + self.activeOrderDict[order.vtOrderID] = order + # 结束委托需要移除 + elif order.vtOrderID in self.activeOrderDict: + del self.activeOrderDict[order.vtOrderID] + + self.onOrder(order) + + #---------------------------------------------------------------------- + def updateTimer(self): + """""" + if not self.active: + return + + self.onTimer() + + #---------------------------------------------------------------------- + def stop(self): + """""" + self.active = False + self.cancelAll() + + self.onStop() + + #---------------------------------------------------------------------- + def onTick(self, tick): + """""" + pass + + #---------------------------------------------------------------------- + def onTrade(self, trade): + """""" + pass + + #---------------------------------------------------------------------- + def onOrder(self, order): + """""" + pass + + #---------------------------------------------------------------------- + def onTimer(self): + """""" + pass + + #---------------------------------------------------------------------- + def onStop(self): + """""" + pass + + #---------------------------------------------------------------------- + def subscribe(self, vtSymbol): + """""" + self.engine.subscribe(self, vtSymbol) + + #---------------------------------------------------------------------- + def buy(self, vtSymbol, price, volume, priceType=None, offset=None): + """""" + return self.engine.buy(self, vtSymbol, price, volume, priceType, offset) + + #---------------------------------------------------------------------- + def sell(self, vtSymbol, price, volume, priceType=None, offset=None): + """""" + return self.engine.sell(self, vtSymbol, price, volume, priceType, offset) + + #---------------------------------------------------------------------- + def cancelOrder(self, vtOrderID): + """""" + self.engine.cancelOrder(self, vtOrderID) + + #---------------------------------------------------------------------- + def cancelAll(self): + """""" + if not self.activeOrderDict: + return False + + for order in self.activeOrderDict.values(): + self.cancelOrder(order.vtOrderID) + return True + + #---------------------------------------------------------------------- + def getTick(self, vtSymbol): + """""" + return self.engine.getTick(self, vtSymbol) + + #---------------------------------------------------------------------- + def getContract(self, vtSymbol): + """""" + return self.engine.getContract(self, vtSymbol) + + #---------------------------------------------------------------------- + def roundValue(self, value, change): + """标准化价格或者数量""" + if not change: + return value + + n = value / change + v = round(n, 0) * change + return v + + #---------------------------------------------------------------------- + def putVarEvent(self, d): + """更新变量""" + d['active'] = self.active + self.engine.putVarEvent(self, d) + + #---------------------------------------------------------------------- + def putParamEvent(self, d): + """更新参数""" + self.engine.putParamEvent(self, d) + + #---------------------------------------------------------------------- + def writeLog(self, content): + """输出日志""" + self.engine.writeLog(content, self) + + \ No newline at end of file diff --git a/vnpy/trader/app/algoTrading/at.ico b/vnpy/trader/app/algoTrading/at.ico new file mode 100644 index 00000000..83114df8 Binary files /dev/null and b/vnpy/trader/app/algoTrading/at.ico differ diff --git a/vnpy/trader/app/algoTrading/testRpc.py b/vnpy/trader/app/algoTrading/testRpc.py new file mode 100644 index 00000000..feb7cf6e --- /dev/null +++ b/vnpy/trader/app/algoTrading/testRpc.py @@ -0,0 +1,48 @@ +# encoding: UTF-8 + +from __future__ import print_function +from time import sleep + +from six.moves import input +from vnpy.rpc import RpcClient +from vnpy.trader.vtConstant import OFFSET_OPEN, DIRECTION_LONG + + +######################################################################## +class TestClient(RpcClient): + """""" + + #---------------------------------------------------------------------- + def __init__(self, reqAddress, subAddress): + """Constructor""" + super(TestClient, self).__init__(reqAddress, subAddress) + + #---------------------------------------------------------------------- + def callback(self, topic, data): + """回调函数实现""" + print(('client received topic:', topic, ', data:', data)) + + +if __name__ == '__main__': + reqAddress = 'tcp://localhost:8899' + subAddress = 'tcp://localhost:9988' + + tc = TestClient(reqAddress, subAddress) + tc.subscribeTopic('') + tc.start() + + setting = { + 'templateName': u'BestLimit 最优限价', + 'vtSymbol': 'rb1810.SHFE', + 'volume': 10, + 'direction': DIRECTION_LONG, + 'offset': OFFSET_OPEN + } + algoName = tc.addAlgo(setting) + print(u'启动算法,实例名', algoName) + + sleep(5) + tc.stopAlgo(algoName) + print(u'停止算法') + + input() diff --git a/vnpy/trader/app/algoTrading/uiAlgoManager.py b/vnpy/trader/app/algoTrading/uiAlgoManager.py new file mode 100644 index 00000000..5e18e1be --- /dev/null +++ b/vnpy/trader/app/algoTrading/uiAlgoManager.py @@ -0,0 +1,561 @@ +# encoding: UTF-8 + +import csv +import traceback +from collections import OrderedDict + +from six import text_type + +from vnpy.event import Event +from vnpy.trader.uiQt import QtCore, QtWidgets + +from .algoEngine import (EVENT_ALGO_LOG, EVENT_ALGO_PARAM, + EVENT_ALGO_VAR, EVENT_ALGO_SETTING) +from .algo import WIDGET_DICT + + + +######################################################################## +class StopButton(QtWidgets.QPushButton): + """停止算法用按钮""" + + #---------------------------------------------------------------------- + def __init__(self, algoEngine, algoName=''): + """Constructor""" + super(StopButton, self).__init__() + + self.algoEngine = algoEngine + self.algoName = algoName + + self.setStyleSheet("color:black;background-color:yellow") + + if algoName: + self.setText(u'停止') + self.clicked.connect(self.stopAlgo) + else: + self.setText(u'停止全部算法') + self.clicked.connect(self.stopAll) + + #---------------------------------------------------------------------- + def stopAlgo(self): + """停止某一算法""" + self.algoEngine.stopAlgo(self.algoName) + self.disable() + + #---------------------------------------------------------------------- + def stopAll(self): + """停止全部算法""" + self.algoEngine.stopAll() + + #---------------------------------------------------------------------- + def disable(self): + """禁用按钮""" + self.setEnabled(False) + self.setStyleSheet("color:black;background-color:grey") + + +AlgoCell = QtWidgets.QTableWidgetItem + + +######################################################################## +class AlgoStatusMonitor(QtWidgets.QTableWidget): + """算法状态监控""" + signalParam = QtCore.Signal(type(Event())) + signalVar = QtCore.Signal(type(Event())) + + MODE_WORKING = 'working' + MODE_HISTORY = 'history' + + #---------------------------------------------------------------------- + def __init__(self, algoEngine, mode): + """Constructor""" + super(AlgoStatusMonitor, self).__init__() + + self.algoEngine = algoEngine + self.eventEngine = algoEngine.eventEngine + self.mode = mode + + self.cellDict = {} + + self.initUi() + self.registerEvent() + + #---------------------------------------------------------------------- + def initUi(self): + """初始化界面""" + labels = [u'', + u'名称', + u'参数', + u'变量'] + + self.setColumnCount(len(labels)) + self.setHorizontalHeaderLabels(labels) + self.setRowCount(0) + self.verticalHeader().setVisible(False) + self.setEditTriggers(self.NoEditTriggers) + self.setAlternatingRowColors(True) + + if self.mode == self.MODE_HISTORY: + self.hideColumn(0) + + #---------------------------------------------------------------------- + def registerEvent(self): + """注册事件监听""" + self.signalParam.connect(self.processParamEvent) + self.signalVar.connect(self.processVarEvent) + + self.eventEngine.register(EVENT_ALGO_PARAM, self.signalParam.emit) + self.eventEngine.register(EVENT_ALGO_VAR, self.signalVar.emit) + + #---------------------------------------------------------------------- + def addAlgo(self, algoName): + """新增算法""" + self.insertRow(0) + + buttonStop = StopButton(self.algoEngine, algoName) + cellName = AlgoCell(algoName) + cellParam = AlgoCell() + cellVar = AlgoCell() + + self.setCellWidget(0, 0, buttonStop) + self.setItem(0, 1, cellName) + self.setItem(0, 2, cellParam) + self.setItem(0, 3, cellVar) + + self.cellDict[algoName] = { + 'param': cellParam, + 'var': cellVar, + 'button': buttonStop + } + + if self.mode == self.MODE_HISTORY: + self.hideRow(0) + + #---------------------------------------------------------------------- + def processParamEvent(self, event): + """处理参数事件""" + d = event.dict_['data'] + + algoName = d['algoName'] + if algoName not in self.cellDict: + self.addAlgo(algoName) + + text = self.generateText(d) + cell = self.cellDict[algoName]['param'] + cell.setText(text) + + self.resizeColumnsToContents() + + #---------------------------------------------------------------------- + def processVarEvent(self, event): + """处理变量事件""" + d = event.dict_['data'] + + algoName = d['algoName'] + if algoName not in self.cellDict: + self.addAlgo(algoName) + + if 'active' in d: + active = d['active'] + + # 若算法已经结束 + if not active: + # 禁用按钮 + cells = self.cellDict[algoName] + button = cells['button'] + button.disable() + + # 根据模式决定显示或者隐藏该行 + cell = cells['var'] + row = self.row(cell) + if self.mode == self.MODE_WORKING: + self.hideRow(row) + else: + self.showRow(row) + + text = self.generateText(d) + cell = self.cellDict[algoName]['var'] + cell.setText(text) + + self.resizeColumnsToContents() + + #---------------------------------------------------------------------- + def generateText(self, d): + """从字典生成字符串""" + l = [] + for k, v in d.items(): + if k not in ['algoName']: + msg = u'%s:%s' %(k, v) + l.append(msg) + text = ','.join(l) + return text + + +######################################################################## +class AlgoLogMonitor(QtWidgets.QTextEdit): + """""" + signal = QtCore.Signal(type(Event())) + + #---------------------------------------------------------------------- + def __init__(self, algoEngine): + """Constructor""" + super(AlgoLogMonitor, self).__init__() + + self.eventEngine = algoEngine.eventEngine + + self.registerEvent() + + #---------------------------------------------------------------------- + def registerEvent(self): + """""" + self.signal.connect(self.processEvent) + + self.eventEngine.register(EVENT_ALGO_LOG, self.signal.emit) + + #---------------------------------------------------------------------- + def processEvent(self, event): + """""" + log = event.dict_['data'] + if not log.gatewayName: + log.gatewayName = u'算法引擎' + msg = u'%s\t%s:%s' %(log.logTime, log.gatewayName, log.logContent) + self.append(msg) + + +######################################################################## +class StartButton(QtWidgets.QPushButton): + """基于配置启动算法用按钮""" + + #---------------------------------------------------------------------- + def __init__(self, algoEngine, setting): + """Constructor""" + super(StartButton, self).__init__() + + self.algoEngine = algoEngine + self.setting = setting + + self.setStyleSheet("color:black;background-color:green") + self.setText(u'启动') + + self.clicked.connect(self.startAlgo) + + #---------------------------------------------------------------------- + def startAlgo(self): + """启动算法""" + self.algoEngine.addAlgo(self.setting) + + #---------------------------------------------------------------------- + def updateSetting(self, setting): + """更新配置""" + self.setting = setting + + +######################################################################## +class DeleteButton(QtWidgets.QPushButton): + """删除算法用按钮""" + + #---------------------------------------------------------------------- + def __init__(self, algoEngine, setting): + """Constructor""" + super(DeleteButton, self).__init__() + + self.algoEngine = algoEngine + self.setting = setting + + self.setStyleSheet("color:black;background-color:red") + self.setText(u'删除') + + self.clicked.connect(self.deleteAlgoSetting) + + #---------------------------------------------------------------------- + def deleteAlgoSetting(self): + """删除算法配置""" + self.algoEngine.deleteAlgoSetting(self.setting) + + #---------------------------------------------------------------------- + def updateSetting(self, setting): + """更新配置""" + self.setting = setting + + +######################################################################## +class AlgoSettingMonitor(QtWidgets.QTableWidget): + """""" + signal = QtCore.Signal(type(Event())) + + #---------------------------------------------------------------------- + def __init__(self, algoEngine): + """Constructor""" + super(AlgoSettingMonitor, self).__init__() + + self.algoEngine = algoEngine + self.eventEngine = algoEngine.eventEngine + + self.cellDict = {} + + self.initUi() + self.registerEvent() + + #---------------------------------------------------------------------- + def initUi(self): + """初始化界面""" + labels = ['', + '', + u'名称', + u'算法', + u'参数'] + + self.setColumnCount(len(labels)) + self.setHorizontalHeaderLabels(labels) + self.setRowCount(0) + self.verticalHeader().setVisible(False) + self.setEditTriggers(self.NoEditTriggers) + + #---------------------------------------------------------------------- + def registerEvent(self): + """注册事件监听""" + self.signal.connect(self.processEvent) + + self.eventEngine.register(EVENT_ALGO_SETTING, self.signal.emit) + + #---------------------------------------------------------------------- + def processEvent(self, event): + """处理事件""" + setting = event.dict_['data'] + settingName = setting['settingName'] + + # 删除配置行 + if len(setting) == 1: + d = self.cellDict.pop(settingName) + cell = d['text'] + row = self.row(cell) + self.removeRow(row) + # 新增配置行 + elif settingName not in self.cellDict: + self.insertRow(0) + + buttonStart = StartButton(self.algoEngine, setting) + buttonDelete = DeleteButton(self.algoEngine, setting) + cellSettingName = AlgoCell(settingName) + cellTemplateName = AlgoCell(setting['templateName']) + cellSettingText = AlgoCell(self.generateText(setting)) + + self.setCellWidget(0, 0, buttonStart) + self.setCellWidget(0, 1, buttonDelete) + self.setItem(0, 2, cellSettingName) + self.setItem(0, 3, cellTemplateName) + self.setItem(0, 4, cellSettingText) + + self.cellDict[settingName] = { + 'start': buttonStart, + 'template': cellTemplateName, + 'text': cellSettingText, + 'delete': buttonDelete + } + # 更新已有配置行 + else: + d = self.cellDict[settingName] + d['start'].updateSetting(setting) + d['template'].setText(setting['templateName']) + d['text'].setText(self.generateText(setting)) + d['delete'].updateSetting(setting) + + self.resizeColumnsToContents() + + #---------------------------------------------------------------------- + def generateText(self, d): + """从字典生成字符串""" + l = [] + for k, v in d.items(): + if k not in ['settingName', 'templateName', '_id']: + msg = u'%s:%s' %(k, v) + l.append(msg) + text = ','.join(l) + return text + + +######################################################################## +class AlgoManager(QtWidgets.QWidget): + """算法交易管理组件""" + + #---------------------------------------------------------------------- + def __init__(self, algoEngine, eventEngine, parent=None): + """Constructor""" + super(AlgoManager, self).__init__(parent) + + self.algoEngine = algoEngine + self.eventEngine = eventEngine + + self.widgetDict = {} + + self.initUi() + self.changeWidget() + self.algoEngine.loadAlgoSetting() # 界面初始化后,再加载算法配置 + + #---------------------------------------------------------------------- + def initUi(self): + """""" + self.setWindowTitle(u'算法交易') + + #buttonWidth = 400 + #buttonHeight = 60 + + self.comboTemplate = QtWidgets.QComboBox() + #self.comboTemplate.setMaximumWidth(buttonWidth) + self.comboTemplate.currentIndexChanged.connect(self.changeWidget) + + vbox = QtWidgets.QVBoxLayout() + for templateName, widgetClass in WIDGET_DICT.items(): + widget = widgetClass(self.algoEngine) + #widget.setMaximumWidth(buttonWidth) + widget.hide() + vbox.addWidget(widget) + + self.widgetDict[templateName] = widget + self.comboTemplate.addItem(templateName) + + self.buttonStop = StopButton(self.algoEngine) + + self.buttonAddAlgo = QtWidgets.QPushButton(u'启动篮子算法') + self.buttonAddAlgo.setStyleSheet("color:white;background-color:green") + self.buttonAddAlgo.clicked.connect(self.addAlgoFromCsv) + + self.buttonSaveSetting = QtWidgets.QPushButton(u'加载算法配置') + self.buttonSaveSetting.setStyleSheet("color:white;background-color:blue") + self.buttonSaveSetting.clicked.connect(self.saveSettingFromCsv) + + self.lineRepPort = QtWidgets.QLineEdit('8899') + self.linePubPort = QtWidgets.QLineEdit('9988') + + self.buttonStartRpc = QtWidgets.QPushButton(u'启动RPC服务') + self.buttonStartRpc.setStyleSheet("color:black;background-color:orange") + self.buttonStartRpc.clicked.connect(self.startRpc) + + label = QtWidgets.QLabel(u'算法类型') + label.setFixedWidth(100) + + hbox = QtWidgets.QHBoxLayout() + hbox.addWidget(label) + hbox.addWidget(self.comboTemplate) + + grid = QtWidgets.QGridLayout() + grid.addWidget(QtWidgets.QLabel(u'REP端口'), 0, 0) + grid.addWidget(self.lineRepPort, 0, 1) + grid.addWidget(QtWidgets.QLabel(u'PUB端口'), 1, 0) + grid.addWidget(self.linePubPort, 1, 1) + + vbox1 = QtWidgets.QVBoxLayout() + vbox1.addLayout(hbox) + vbox1.addLayout(vbox) + vbox1.addStretch() + vbox1.addWidget(self.buttonStop) + vbox1.addWidget(self.buttonAddAlgo) + vbox1.addWidget(self.buttonSaveSetting) + vbox1.addStretch() + vbox1.addLayout(grid) + vbox1.addWidget(self.buttonStartRpc) + + workingMonitor = AlgoStatusMonitor(self.algoEngine, AlgoStatusMonitor.MODE_WORKING) + workingMonitor.setFixedWidth(1500) + + historyMonitor = AlgoStatusMonitor(self.algoEngine, AlgoStatusMonitor.MODE_HISTORY) + logMonitor = AlgoLogMonitor(self.algoEngine) + settingMonitor = AlgoSettingMonitor(self.algoEngine) + + tab1 = QtWidgets.QTabWidget() + tab1.addTab(workingMonitor, u'运行中') + tab1.addTab(historyMonitor, u'已结束') + + tab2 = QtWidgets.QTabWidget() + tab2.addTab(logMonitor, u'日志信息') + + tab3 = QtWidgets.QTabWidget() + tab3.addTab(settingMonitor, u'算法配置') + + hbox = QtWidgets.QHBoxLayout() + hbox.addWidget(tab2) + hbox.addWidget(tab3) + + vbox2 = QtWidgets.QVBoxLayout() + vbox2.addWidget(tab1) + vbox2.addLayout(hbox) + + hbox2 = QtWidgets.QHBoxLayout() + hbox2.addLayout(vbox1) + hbox2.addLayout(vbox2) + + self.setLayout(hbox2) + + #---------------------------------------------------------------------- + def changeWidget(self): + """""" + for widget in self.widgetDict.values(): + widget.hide() + + templateName = text_type(self.comboTemplate.currentText()) + widget = self.widgetDict[templateName] + widget.show() + + #---------------------------------------------------------------------- + def addAlgoWidget(self, widgetClass): + """添加算法控制组件 """ + w = widgetClass(self.algoEngine) + self.tab.addTab(w, w.templateName) + + #---------------------------------------------------------------------- + def loadCsv(self, path): + """读取CSV配置文件""" + try: + with open(text_type(path)) as f: + buf = [line.encode('UTF-8') for line in f] + + reader = csv.DictReader(buf) + l = [] + + for d in reader: + setting = OrderedDict() + for name in reader.fieldnames: + setting[str(name)] = d[name] + l.append(setting) + + return l + + except: + msg = traceback.format_exc() + self.algoEngine.writeLog(u'读取CSV文件失败:\n' + msg) + return [] + + #---------------------------------------------------------------------- + def saveSettingFromCsv(self): + """从CSV加载配置到数据库""" + path, fileType = QtWidgets.QFileDialog.getOpenFileName(self, u'加载算法配置', '', 'CSV(*.csv)') + l = self.loadCsv(path) + for setting in l: + self.algoEngine.saveAlgoSetting(setting) + + #---------------------------------------------------------------------- + def addAlgoFromCsv(self): + """从CSV启动一篮子算法""" + path, fileType = QtWidgets.QFileDialog.getOpenFileName(self, u'启动篮子算法', '', 'CSV(*.csv) ') + l = self.loadCsv(path) + for setting in l: + self.algoEngine.addAlgo(setting) + + #---------------------------------------------------------------------- + def startRpc(self): + """启动算法服务""" + try: + repPort = int(self.lineRepPort.text()) + pubPort = int(self.linePubPort.text()) + except: + self.algoEngine.writeLog(u'请检查RPC端口,只能使用整数') + return + + self.algoEngine.startRpc(repPort, pubPort) + + #---------------------------------------------------------------------- + def show(self): + """""" + self.showMaximized() + + diff --git a/vnpy/trader/app/algoTrading/uiAlgoWidget.py b/vnpy/trader/app/algoTrading/uiAlgoWidget.py new file mode 100644 index 00000000..32732f86 --- /dev/null +++ b/vnpy/trader/app/algoTrading/uiAlgoWidget.py @@ -0,0 +1,73 @@ +# encoding: UTF-8 + +from six import text_type + +from vnpy.trader.uiQt import QtWidgets + + +######################################################################## +class AlgoWidget(QtWidgets.QFrame): + """算法启动组件""" + + #---------------------------------------------------------------------- + def __init__(self, algoEngine, parent=None): + """Constructor""" + super(AlgoWidget, self).__init__(parent) + + self.templateName = '' + self.algoEngine = algoEngine + + self.initUi() + + #---------------------------------------------------------------------- + def initUi(self): + """""" + self.setFrameShape(self.Box) + algoLayout = self.initAlgoLayout() + + buttonStart = QtWidgets.QPushButton(u'启动算法') + buttonStart.clicked.connect(self.addAlgo) + buttonStart.setMinimumHeight(100) + + buttonSave = QtWidgets.QPushButton(u'保存配置') + buttonSave.clicked.connect(self.saveAlgoSetting) + buttonSave.setMinimumHeight(100) + + self.lineSettingName = QtWidgets.QLineEdit() + self.lineSettingName.setPlaceholderText(u'算法配置名称') + + vbox = QtWidgets.QVBoxLayout() + vbox.addLayout(algoLayout) + vbox.addWidget(buttonStart) + vbox.addWidget(QtWidgets.QLabel('')) + vbox.addWidget(QtWidgets.QLabel('')) + vbox.addWidget(self.lineSettingName) + vbox.addWidget(buttonSave) + + self.setLayout(vbox) + + #---------------------------------------------------------------------- + def addAlgo(self): + """启动算法""" + setting = self.getAlgoSetting() + self.algoEngine.addAlgo(setting) + + #---------------------------------------------------------------------- + def saveAlgoSetting(self): + """保存算法配置""" + setting = self.getAlgoSetting() + setting['templateName'] = self.templateName + setting['settingName'] = text_type(self.lineSettingName.text()) + self.algoEngine.saveAlgoSetting(setting) + + self.lineSettingName.setText('') + + #---------------------------------------------------------------------- + def initAlgoLayout(self): + """初始化算法相关的组件部分""" + pass + + #---------------------------------------------------------------------- + def getAlgoSetting(self): + """获取算法配置""" + pass diff --git a/vnpy/trader/app/ctaStrategy/ctaBacktesting.py b/vnpy/trader/app/ctaStrategy/ctaBacktesting.py index c0bb5c6d..2fde1617 100644 --- a/vnpy/trader/app/ctaStrategy/ctaBacktesting.py +++ b/vnpy/trader/app/ctaStrategy/ctaBacktesting.py @@ -5,6 +5,7 @@ 可以使用和实盘相同的代码进行回测。 ''' from __future__ import division +from __future__ import print_function from datetime import datetime, timedelta from collections import OrderedDict @@ -17,6 +18,9 @@ import pandas as pd import numpy as np import matplotlib.pyplot as plt +from vnpy.rpc import RpcClient, RpcServer, RemoteException + + # 如果安装了seaborn则设置为白色风格 try: import seaborn as sns @@ -70,6 +74,7 @@ class BacktestingEngine(object): self.dbClient = None # 数据库客户端 self.dbCursor = None # 数据库指针 + self.hdsClient = None # 历史数据服务器客户端 self.initData = [] # 初始化用的数据 self.dbName = '' # 回测数据库名 @@ -112,7 +117,7 @@ class BacktestingEngine(object): #---------------------------------------------------------------------- def output(self, content): """输出内容""" - print str(datetime.now()) + "\t" + content + print(str(datetime.now()) + "\t" + content) #------------------------------------------------ # 参数设置相关 @@ -180,6 +185,15 @@ class BacktestingEngine(object): # 数据回放相关 #------------------------------------------------ + #---------------------------------------------------------------------- + def initHdsClient(self): + """初始化历史数据服务器客户端""" + reqAddress = 'tcp://localhost:5555' + subAddress = 'tcp://localhost:7777' + + self.hdsClient = RpcClient(reqAddress, subAddress) + self.hdsClient.start() + #---------------------------------------------------------------------- def loadHistoryData(self): """载入历史数据""" @@ -187,7 +201,7 @@ class BacktestingEngine(object): collection = self.dbClient[self.dbName][self.symbol] self.output(u'开始载入数据') - + # 首先根据回测模式,确认要使用的数据类 if self.mode == self.BAR_MODE: dataClass = VtBarData @@ -196,10 +210,16 @@ class BacktestingEngine(object): dataClass = VtTickData func = self.newTick - # 载入初始化需要用的数据 - flt = {'datetime':{'$gte':self.dataStartDate, - '$lt':self.strategyStartDate}} - initCursor = collection.find(flt).sort('datetime') + # 载入初始化需要用的数据 + if self.hdsClient: + initCursor = self.hdsClient.loadHistoryData(self.dbName, + self.symbol, + self.dataStartDate, + self.strategyStartDate) + else: + flt = {'datetime':{'$gte':self.dataStartDate, + '$lt':self.strategyStartDate}} + initCursor = collection.find(flt).sort('datetime') # 将数据从查询指针中读取出,并生成列表 self.initData = [] # 清空initData列表 @@ -209,14 +229,24 @@ class BacktestingEngine(object): self.initData.append(data) # 载入回测数据 - if not self.dataEndDate: - flt = {'datetime':{'$gte':self.strategyStartDate}} # 数据过滤条件 + if self.hdsClient: + self.dbCursor = self.hdsClient.loadHistoryData(self.dbName, + self.symbol, + self.strategyStartDate, + self.dataEndDate) else: - flt = {'datetime':{'$gte':self.strategyStartDate, - '$lte':self.dataEndDate}} - self.dbCursor = collection.find(flt).sort('datetime') + if not self.dataEndDate: + flt = {'datetime':{'$gte':self.strategyStartDate}} # 数据过滤条件 + else: + flt = {'datetime':{'$gte':self.strategyStartDate, + '$lte':self.dataEndDate}} + self.dbCursor = collection.find(flt).sort('datetime') - self.output(u'载入完成,数据量:%s' %(initCursor.count() + self.dbCursor.count())) + if isinstance(self.dbCursor, list): + count = len(initCursor) + len(self.dbCursor) + else: + count = initCursor.count() + self.dbCursor.count() + self.output(u'载入完成,数据量:%s' %count) #---------------------------------------------------------------------- def runBacktesting(self): @@ -234,8 +264,8 @@ class BacktestingEngine(object): self.output(u'开始回测') - self.strategy.inited = True self.strategy.onInit() + self.strategy.inited = True self.output(u'策略初始化完成') self.strategy.trading = True @@ -585,6 +615,11 @@ class BacktestingEngine(object): """ self.output(u'计算回测结果') + # 检查成交记录 + if not self.tradeDict: + self.output(u'成交记录为空,无法计算回测结果') + return {} + # 首先基于回测后的成交记录,计算每笔交易的盈亏 resultList = [] # 交易结果列表 @@ -941,6 +976,11 @@ class BacktestingEngine(object): """计算按日统计的交易结果""" self.output(u'计算按日统计结果') + # 检查成交记录 + if not self.tradeDict: + self.output(u'成交记录为空,无法计算回测结果') + return {} + # 将成交添加到每日交易结果中 for trade in self.tradeDict.values(): date = trade.dt.date() @@ -1213,11 +1253,11 @@ class OptimizationSetting(object): return if end < start: - print u'参数起始点必须不大于终止点' + print(u'参数起始点必须不大于终止点') return if step <= 0: - print u'参数布进必须大于0' + print(u'参数布进必须大于0') return l = [] @@ -1253,6 +1293,59 @@ class OptimizationSetting(object): self.optimizeTarget = target +######################################################################## +class HistoryDataServer(RpcServer): + """历史数据缓存服务器""" + + #---------------------------------------------------------------------- + def __init__(self, repAddress, pubAddress): + """Constructor""" + super(HistoryDataServer, self).__init__(repAddress, pubAddress) + + self.dbClient = pymongo.MongoClient(globalSetting['mongoHost'], + globalSetting['mongoPort']) + + self.historyDict = {} + + self.register(self.loadHistoryData) + + #---------------------------------------------------------------------- + def loadHistoryData(self, dbName, symbol, start, end): + """""" + # 首先检查是否有缓存,如果有则直接返回 + history = self.historyDict.get((dbName, symbol, start, end), None) + if history: + print(u'找到内存缓存:%s %s %s %s' %(dbName, symbol, start, end)) + return history + + # 否则从数据库加载 + collection = self.dbClient[dbName][symbol] + + if end: + flt = {'datetime':{'$gte':start, '$lt':end}} + else: + flt = {'datetime':{'$gte':start}} + + cx = collection.find(flt).sort('datetime') + history = [d for d in cx] + + self.historyDict[(dbName, symbol, start, end)] = history + print(u'从数据库加载:%s %s %s %s' %(dbName, symbol, start, end)) + return history + +#---------------------------------------------------------------------- +def runHistoryDataServer(): + """""" + repAddress = 'tcp://*:5555' + pubAddress = 'tcp://*:7777' + + hds = HistoryDataServer(repAddress, pubAddress) + hds.start() + + print(u'按任意键退出') + hds.stop() + raw_input() + #---------------------------------------------------------------------- def formatNumber(n): """格式化数字到字符串""" diff --git a/vnpy/trader/app/ctaStrategy/ctaEngine.py b/vnpy/trader/app/ctaStrategy/ctaEngine.py index ba25d273..f4388264 100644 --- a/vnpy/trader/app/ctaStrategy/ctaEngine.py +++ b/vnpy/trader/app/ctaStrategy/ctaEngine.py @@ -19,13 +19,14 @@ from vnpy.trader.vtConstant import * from vnpy.trader.vtObject import VtTickData, VtBarData from vnpy.trader.vtGateway import VtSubscribeReq, VtOrderReq, VtCancelOrderReq, VtLogData from vnpy.trader.vtFunction import todayDate, getJsonPath +from vnpy.trader.app import AppEngine from .ctaBase import * from .strategy import STRATEGY_CLASS ######################################################################## -class CtaEngine(object): +class CtaEngine(AppEngine): """CTA策略引擎""" settingFileName = 'CTA_setting.json' settingfilePath = getJsonPath(settingFileName, __file__) @@ -234,19 +235,22 @@ class CtaEngine(object): price = tick.lowerLimit # 发出市价委托 - self.sendOrder(so.vtSymbol, so.orderType, price, so.volume, so.strategy) + vtOrderID = self.sendOrder(so.vtSymbol, so.orderType, + price, so.volume, so.strategy) - # 从活动停止单字典中移除该停止单 - del self.workingStopOrderDict[so.stopOrderID] - - # 从策略委托号集合中移除 - s = self.strategyOrderDict[so.strategy.name] - if so.stopOrderID in s: - s.remove(so.stopOrderID) - - # 更新停止单状态,并通知策略 - so.status = STOPORDER_TRIGGERED - so.strategy.onStopOrder(so) + # 检查因为风控流控等原因导致的委托失败(无委托号) + if vtOrderID: + # 从活动停止单字典中移除该停止单 + del self.workingStopOrderDict[so.stopOrderID] + + # 从策略委托号集合中移除 + s = self.strategyOrderDict[so.strategy.name] + if so.stopOrderID in s: + s.remove(so.stopOrderID) + + # 更新停止单状态,并通知策略 + so.status = STOPORDER_TRIGGERED + so.strategy.onStopOrder(so) #---------------------------------------------------------------------- def processTickEvent(self, event): @@ -430,8 +434,9 @@ class CtaEngine(object): strategy = self.strategyDict[name] if not strategy.inited: - strategy.inited = True self.callStrategyFunc(strategy, strategy.onInit) + strategy.inited = True + self.loadSyncData(strategy) # 初始化完成后加载同步数据 self.subscribeMarketData(strategy) # 加载同步数据后再订阅行情 else: diff --git a/vnpy/trader/app/ctaStrategy/ctaHistoryData.py b/vnpy/trader/app/ctaStrategy/ctaHistoryData.py index 070a2cd6..a979e806 100644 --- a/vnpy/trader/app/ctaStrategy/ctaHistoryData.py +++ b/vnpy/trader/app/ctaStrategy/ctaHistoryData.py @@ -7,10 +7,12 @@ 3. 将交易开拓者导出的历史数据载入到MongoDB中的函数 4. 将OKEX下载的历史数据载入到MongoDB中的函数 """ +from __future__ import print_function import csv from datetime import datetime, timedelta from time import time +from struct import unpack import pymongo @@ -25,7 +27,7 @@ def downloadEquityDailyBarts(self, symbol): """ 下载股票的日行情,symbol是股票代码 """ - print u'开始下载%s日行情' %symbol + print(u'开始下载%s日行情' %symbol) # 查询数据库中已有数据的最后日期 cl = self.dbClient[DAILY_DB_NAME][symbol] @@ -61,22 +63,20 @@ def downloadEquityDailyBarts(self, symbol): bar.datetime = datetime.strptime(bar.date, '%Y%m%d') bar.volume = d.get('volume') except KeyError: - print d + print(d) flt = {'datetime': bar.datetime} self.dbClient[DAILY_DB_NAME][symbol].update_one(flt, {'$set':bar.__dict__}, upsert=True) - print u'%s下载完成' %symbol + print(u'%s下载完成' %symbol) else: - print u'找不到合约%s' %symbol + print(u'找不到合约%s' %symbol) #---------------------------------------------------------------------- def loadMcCsv(fileName, dbName, symbol): """将Multicharts导出的csv格式的历史数据插入到Mongo数据库中""" - import csv - start = time() - print u'开始读取CSV文件%s中的数据插入到%s的%s中' %(fileName, dbName, symbol) + print(u'开始读取CSV文件%s中的数据插入到%s的%s中' %(fileName, dbName, symbol)) # 锁定集合,并创建索引 client = pymongo.MongoClient(globalSetting['mongoHost'], globalSetting['mongoPort']) @@ -84,33 +84,32 @@ def loadMcCsv(fileName, dbName, symbol): collection.ensure_index([('datetime', pymongo.ASCENDING)], unique=True) # 读取数据和插入到数据库 - reader = csv.DictReader(file(fileName, 'r')) - for d in reader: - bar = VtBarData() - bar.vtSymbol = symbol - bar.symbol = symbol - bar.open = float(d['Open']) - bar.high = float(d['High']) - bar.low = float(d['Low']) - bar.close = float(d['Close']) - bar.date = datetime.strptime(d['Date'], '%Y-%m-%d').strftime('%Y%m%d') - bar.time = d['Time'] - bar.datetime = datetime.strptime(bar.date + ' ' + bar.time, '%Y%m%d %H:%M:%S') - bar.volume = d['TotalVolume'] - - flt = {'datetime': bar.datetime} - collection.update_one(flt, {'$set':bar.__dict__}, upsert=True) - print bar.date, bar.time + with open(fileName, 'r') as f: + reader = csv.DictReader(f) + for d in reader: + bar = VtBarData() + bar.vtSymbol = symbol + bar.symbol = symbol + bar.open = float(d['Open']) + bar.high = float(d['High']) + bar.low = float(d['Low']) + bar.close = float(d['Close']) + bar.date = datetime.strptime(d['Date'], '%Y-%m-%d').strftime('%Y%m%d') + bar.time = d['Time'] + bar.datetime = datetime.strptime(bar.date + ' ' + bar.time, '%Y%m%d %H:%M:%S') + bar.volume = d['TotalVolume'] - print u'插入完毕,耗时:%s' % (time()-start) + flt = {'datetime': bar.datetime} + collection.update_one(flt, {'$set':bar.__dict__}, upsert=True) + print(bar.date, bar.time) + + print(u'插入完毕,耗时:%s' % (time()-start)) #---------------------------------------------------------------------- def loadTbCsv(fileName, dbName, symbol): """将TradeBlazer导出的csv格式的历史分钟数据插入到Mongo数据库中""" - import csv - start = time() - print u'开始读取CSV文件%s中的数据插入到%s的%s中' %(fileName, dbName, symbol) + print(u'开始读取CSV文件%s中的数据插入到%s的%s中' %(fileName, dbName, symbol)) # 锁定集合,并创建索引 client = pymongo.MongoClient(globalSetting['mongoHost'], globalSetting['mongoPort']) @@ -135,17 +134,15 @@ def loadTbCsv(fileName, dbName, symbol): flt = {'datetime': bar.datetime} collection.update_one(flt, {'$set':bar.__dict__}, upsert=True) - print bar.date, bar.time + print(bar.date, bar.time) - print u'插入完毕,耗时:%s' % (time()-start) + print(u'插入完毕,耗时:%s' % (time()-start)) #---------------------------------------------------------------------- def loadTbPlusCsv(fileName, dbName, symbol): """将TB极速版导出的csv格式的历史分钟数据插入到Mongo数据库中""" - import csv - start = time() - print u'开始读取CSV文件%s中的数据插入到%s的%s中' %(fileName, dbName, symbol) + print(u'开始读取CSV文件%s中的数据插入到%s的%s中' %(fileName, dbName, symbol)) # 锁定集合,并创建索引 client = pymongo.MongoClient(globalSetting['mongoHost'], globalSetting['mongoPort']) @@ -172,9 +169,9 @@ def loadTbPlusCsv(fileName, dbName, symbol): bar.openInterest = d[7] flt = {'datetime': bar.datetime} collection.update_one(flt, {'$set':bar.__dict__}, upsert=True) - print bar.date, bar.time + print(bar.date, bar.time) - print u'插入完毕,耗时:%s' % (time()-start) + print(u'插入完毕,耗时:%s' % (time()-start)) #---------------------------------------------------------------------- """ @@ -189,11 +186,9 @@ def loadTbPlusCsv(fileName, dbName, symbol): """ def loadTdxCsv(fileName, dbName, symbol): """将通达信导出的csv格式的历史分钟数据插入到Mongo数据库中""" - import csv - start = time() date_correct = "" - print u'开始读取CSV文件%s中的数据插入到%s的%s中' %(fileName, dbName, symbol) + print(u'开始读取CSV文件%s中的数据插入到%s的%s中' %(fileName, dbName, symbol)) # 锁定集合,并创建索引 client = pymongo.MongoClient(globalSetting['mongoHost'], globalSetting['mongoPort']) @@ -225,7 +220,7 @@ def loadTdxCsv(fileName, dbName, symbol): flt = {'datetime': bar.datetime} collection.update_one(flt, {'$set':bar.__dict__}, upsert=True) - print u'插入完毕,耗时:%s' % (time()-start) + print(u'插入完毕,耗时:%s' % (time()-start)) #---------------------------------------------------------------------- """ @@ -236,11 +231,9 @@ def loadTdxCsv(fileName, dbName, symbol): """ def loadTdxLc1(fileName, dbName, symbol): """将通达信导出的lc1格式的历史分钟数据插入到Mongo数据库中""" - from struct import * - start = time() - print u'开始读取通达信Lc1文件%s中的数据插入到%s的%s中' %(fileName, dbName, symbol) + print(u'开始读取通达信Lc1文件%s中的数据插入到%s的%s中' %(fileName, dbName, symbol)) # 锁定集合,并创建索引 client = pymongo.MongoClient(globalSetting['mongoHost'], globalSetting['mongoPort']) @@ -276,13 +269,13 @@ def loadTdxLc1(fileName, dbName, symbol): flt = {'datetime': bar.datetime} collection.update_one(flt, {'$set':bar.__dict__}, upsert=True) - print u'插入完毕,耗时:%s' % (time()-start) + print(u'插入完毕,耗时:%s' % (time()-start)) #---------------------------------------------------------------------- def loadOKEXCsv(fileName, dbName, symbol): """将OKEX导出的csv格式的历史分钟数据插入到Mongo数据库中""" start = time() - print u'开始读取CSV文件%s中的数据插入到%s的%s中' %(fileName, dbName, symbol) + print(u'开始读取CSV文件%s中的数据插入到%s的%s中' %(fileName, dbName, symbol)) # 锁定集合,并创建索引 client = pymongo.MongoClient(globalSetting['mongoHost'], globalSetting['mongoPort']) @@ -313,5 +306,5 @@ def loadOKEXCsv(fileName, dbName, symbol): collection.update_one(flt, {'$set':bar.__dict__}, upsert=True) print('%s \t %s' % (bar.date, bar.time)) - print u'插入完毕,耗时:%s' % (time()-start) + print(u'插入完毕,耗时:%s' % (time()-start)) diff --git a/vnpy/trader/app/ctaStrategy/ctaTemplate.py b/vnpy/trader/app/ctaStrategy/ctaTemplate.py index 5f829d0f..f6b6a465 100644 --- a/vnpy/trader/app/ctaStrategy/ctaTemplate.py +++ b/vnpy/trader/app/ctaStrategy/ctaTemplate.py @@ -449,6 +449,13 @@ class BarGenerator(object): # 清空老K线缓存对象 self.xminBar = None + #---------------------------------------------------------------------- + def generate(self): + """手动强制立即完成K线合成""" + self.onBar(self.bar) + self.bar = None + + ######################################################################## class ArrayManager(object): diff --git a/vnpy/trader/app/ctaStrategy/language/__init__.py b/vnpy/trader/app/ctaStrategy/language/__init__.py index a058945f..e402c4f5 100644 --- a/vnpy/trader/app/ctaStrategy/language/__init__.py +++ b/vnpy/trader/app/ctaStrategy/language/__init__.py @@ -1,13 +1,14 @@ # encoding: UTF-8 +from __future__ import absolute_import import json import os import traceback # 默认设置 -from chinese import text +from .chinese import text # 是否要使用英文 from vnpy.trader.vtGlobal import globalSetting if globalSetting['language'] == 'english': - from english import text + from .english import text diff --git a/vnpy/trader/app/ctaStrategy/strategy/__init__.py b/vnpy/trader/app/ctaStrategy/strategy/__init__.py index 7a14afa9..5b10bc02 100644 --- a/vnpy/trader/app/ctaStrategy/strategy/__init__.py +++ b/vnpy/trader/app/ctaStrategy/strategy/__init__.py @@ -3,6 +3,7 @@ ''' 动态载入所有的策略类 ''' +from __future__ import print_function import os import importlib diff --git a/vnpy/trader/app/dataRecorder/__init__.py b/vnpy/trader/app/dataRecorder/__init__.py index 84020dfe..8d3ddf78 100644 --- a/vnpy/trader/app/dataRecorder/__init__.py +++ b/vnpy/trader/app/dataRecorder/__init__.py @@ -1,7 +1,8 @@ # encoding: UTF-8 -from drEngine import DrEngine -from uiDrWidget import DrEngineManager +from __future__ import absolute_import +from .drEngine import DrEngine +from .uiDrWidget import DrEngineManager appName = 'DataRecorder' appDisplayName = u'行情记录' diff --git a/vnpy/trader/app/dataRecorder/drEngine.py b/vnpy/trader/app/dataRecorder/drEngine.py index 5b29f3dd..dee29801 100644 --- a/vnpy/trader/app/dataRecorder/drEngine.py +++ b/vnpy/trader/app/dataRecorder/drEngine.py @@ -12,7 +12,7 @@ import os import copy import traceback from collections import OrderedDict -from datetime import datetime, timedelta +from datetime import datetime, timedelta, time from Queue import Queue, Empty from threading import Thread from pymongo.errors import DuplicateKeyError @@ -60,6 +60,11 @@ class DrEngine(object): self.queue = Queue() # 队列 self.thread = Thread(target=self.run) # 线程 + # 收盘相关 + self.marketCloseTime = None # 收盘时间 + self.timerCount = 0 # 定时器计数 + self.lastTimerTime = None # 上一次记录时间 + # 载入设置,订阅行情 self.loadSetting() @@ -79,6 +84,11 @@ class DrEngine(object): working = drSetting['working'] if not working: return + + # 加载收盘时间 + if 'marketCloseTime' in drSetting: + timestamp = drSetting['marketCloseTime'] + self.marketCloseTime = datetime.strptime(timestamp, '%H:%M:%S').time() # Tick记录配置 if 'tick' in drSetting: @@ -175,14 +185,47 @@ class DrEngine(object): # 生成datetime对象 if not tick.datetime: - tick.datetime = datetime.strptime(' '.join([tick.date, tick.time]), '%Y%m%d %H:%M:%S.%f') + if '.' in tick.time: + tick.datetime = datetime.strptime(' '.join([tick.date, tick.time]), '%Y%m%d %H:%M:%S.%f') + else: + tick.datetime = datetime.strptime(' '.join([tick.date, tick.time]), '%Y%m%d %H:%M:%S') self.onTick(tick) bm = self.bgDict.get(vtSymbol, None) if bm: bm.updateTick(tick) + + #---------------------------------------------------------------------- + def processTimerEvent(self, event): + """处理定时事件""" + # 如果没有设置收盘时间,则无需处理 + if not self.marketCloseTime: + return + # 10秒检查一次 + self.timerCount += 1 + if self.timerCount < 10: + return + self.timerCount = 0 + + # 获取当前时间 + currentTime = datetime.now().time() + + if not self.lastTimerTime: + self.lastTimerTime = currentTime + return + + # 上一个时间戳尚未到收盘时间,且当前时间戳已经到收盘时间 + if (self.lastTimerTime < self.marketCloseTime and + currentTime >= self.marketCloseTime): + # 强制所有的K线生成器立即完成K线 + for bg in self.bgDict.values(): + bg.generate() + + # 记录新的时间 + self.lastTimerTime = currentTime + #---------------------------------------------------------------------- def onTick(self, tick): """Tick更新""" @@ -224,6 +267,7 @@ class DrEngine(object): def registerEvent(self): """注册事件监听""" self.eventEngine.register(EVENT_TICK, self.procecssTickEvent) + self.eventEngine.register(EVENT_TIMER, self.processTimerEvent) #---------------------------------------------------------------------- def insertData(self, dbName, collectionName, data): diff --git a/vnpy/trader/app/dataRecorder/language/__init__.py b/vnpy/trader/app/dataRecorder/language/__init__.py index ecf72358..09d8fba7 100644 --- a/vnpy/trader/app/dataRecorder/language/__init__.py +++ b/vnpy/trader/app/dataRecorder/language/__init__.py @@ -1,13 +1,14 @@ # encoding: UTF-8 +from __future__ import absolute_import import json import os import traceback # 默认设置 -from chinese import text +from .chinese import text # 是否要使用英文 from vnpy.trader.vtGlobal import globalSetting if globalSetting['language'] == 'english': - from english import text \ No newline at end of file + from .english import text \ No newline at end of file diff --git a/vnpy/trader/app/optionMaster/omDate.py b/vnpy/trader/app/optionMaster/omDate.py index 1211c166..795f3068 100644 --- a/vnpy/trader/app/optionMaster/omDate.py +++ b/vnpy/trader/app/optionMaster/omDate.py @@ -159,9 +159,11 @@ class CalendarManager(QtWidgets.QWidget): #---------------------------------------------------------------------- def runCalendarEditor(): """运行日历编辑器""" - reload(sys) - sys.setdefaultencoding('utf8') - + try: # Python 2 + reload(sys) + sys.setdefaultencoding('utf8') + except NameError: # Python 3 + pass app = QtWidgets.QApplication(sys.argv) app.setFont(QtGui.QFont(u'微软雅黑', 12)) @@ -249,4 +251,4 @@ def getTimeToMaturity(expiryDate): if __name__ == '__main__': - runCalendarEditor() \ No newline at end of file + runCalendarEditor() diff --git a/vnpy/trader/app/optionMaster/omEngine.py b/vnpy/trader/app/optionMaster/omEngine.py index d7b1e92c..169f90e1 100644 --- a/vnpy/trader/app/optionMaster/omEngine.py +++ b/vnpy/trader/app/optionMaster/omEngine.py @@ -152,10 +152,15 @@ class OmEngine(object): detail = self.mainEngine.getPositionDetail(contract.vtSymbol) option = OmOption(contract, detail, underlying, model, r) + key = str(option.k) if contract.optionType is OPTION_CALL: - callDict[option.k] = option + if key in callDict: + key += 'a' + callDict[key] = option else: - putDict[option.k] = option + if key in putDict: + key += 'a' + putDict[key] = option # 期权排序 strikeList = callDict.keys() diff --git a/vnpy/trader/app/optionMaster/strategy/__init__.py b/vnpy/trader/app/optionMaster/strategy/__init__.py index 5e349490..0035381d 100644 --- a/vnpy/trader/app/optionMaster/strategy/__init__.py +++ b/vnpy/trader/app/optionMaster/strategy/__init__.py @@ -3,6 +3,7 @@ ''' 动态载入所有的策略类 ''' +from __future__ import print_function import os import importlib diff --git a/vnpy/trader/app/optionMaster/uiOmManualTrader.py b/vnpy/trader/app/optionMaster/uiOmManualTrader.py index 1652be06..d29743f9 100644 --- a/vnpy/trader/app/optionMaster/uiOmManualTrader.py +++ b/vnpy/trader/app/optionMaster/uiOmManualTrader.py @@ -1,5 +1,6 @@ # encoding: UTF-8 +from __future__ import absolute_import from vnpy.event import Event from vnpy.trader.vtConstant import DIRECTION_LONG, DIRECTION_SHORT, OFFSET_OPEN, OFFSET_CLOSE, PRICETYPE_LIMITPRICE @@ -7,7 +8,7 @@ from vnpy.trader.vtObject import VtOrderReq from vnpy.trader.vtEvent import EVENT_TICK, EVENT_TRADE from vnpy.trader.uiBasicWidget import WorkingOrderMonitor, PositionMonitor -from uiOmBase import * +from .uiOmBase import * diff --git a/vnpy/trader/app/optionMaster/uiOmWidget.py b/vnpy/trader/app/optionMaster/uiOmWidget.py index ea770808..956a6372 100644 --- a/vnpy/trader/app/optionMaster/uiOmWidget.py +++ b/vnpy/trader/app/optionMaster/uiOmWidget.py @@ -5,6 +5,8 @@ from __future__ import division import os from datetime import datetime +from six import text_type + from vnpy.event import Event from .omBase import EVENT_OM_LOG @@ -113,7 +115,7 @@ class OmManager(QtWidgets.QWidget): def initOmEngine(self): """初始化引擎""" path = os.getcwd() - fileName = unicode(self.comboSettingFile.currentText()) + fileName = text_type(self.comboSettingFile.currentText()) fileName = os.path.join(path, fileName) result = self.omEngine.initEngine(fileName) diff --git a/vnpy/trader/app/riskManager/RM_setting.json b/vnpy/trader/app/riskManager/RM_setting.json index 20625b38..d34f89d0 100644 --- a/vnpy/trader/app/riskManager/RM_setting.json +++ b/vnpy/trader/app/riskManager/RM_setting.json @@ -1,10 +1,10 @@ { "orderFlowClear": 1, "orderCancelLimit": 10, + "marginRatioLimit": 0.95, "workingOrderLimit": 20, "tradeLimit": 1000, "orderSizeLimit": 100, - "active": true, - "orderFlowLimit": 50, - "marginRatioLimit": 0.95 + "active": false, + "orderFlowLimit": 50 } \ No newline at end of file diff --git a/vnpy/trader/app/riskManager/__init__.py b/vnpy/trader/app/riskManager/__init__.py index c58c8c61..daa917c0 100644 --- a/vnpy/trader/app/riskManager/__init__.py +++ b/vnpy/trader/app/riskManager/__init__.py @@ -1,10 +1,11 @@ # encoding: UTF-8 -from rmEngine import RmEngine -from uiRmWidget import RmEngineManager +from __future__ import absolute_import +from .rmEngine import RmEngine +from .uiRmWidget import RmEngineManager appName = 'RiskManager' appDisplayName = u'风险管理' appEngine = RmEngine appWidget = RmEngineManager -appIco = 'rm.ico' \ No newline at end of file +appIco = 'rm.ico' diff --git a/vnpy/trader/app/riskManager/language/__init__.py b/vnpy/trader/app/riskManager/language/__init__.py index a058945f..e402c4f5 100644 --- a/vnpy/trader/app/riskManager/language/__init__.py +++ b/vnpy/trader/app/riskManager/language/__init__.py @@ -1,13 +1,14 @@ # encoding: UTF-8 +from __future__ import absolute_import import json import os import traceback # 默认设置 -from chinese import text +from .chinese import text # 是否要使用英文 from vnpy.trader.vtGlobal import globalSetting if globalSetting['language'] == 'english': - from english import text + from .english import text diff --git a/vnpy/trader/app/spreadTrading/stEngine.py b/vnpy/trader/app/spreadTrading/stEngine.py index 1473b06e..43339eab 100644 --- a/vnpy/trader/app/spreadTrading/stEngine.py +++ b/vnpy/trader/app/spreadTrading/stEngine.py @@ -3,6 +3,7 @@ import json import traceback import shelve +from collections import OrderedDict from vnpy.event import Event from vnpy.trader.vtFunction import getJsonPath, getTempPath @@ -195,13 +196,7 @@ class StDataEngine(object): spread.calculatePos() # 推送价差持仓更新 - event1 = Event(EVENT_SPREADTRADING_POS+spread.name) - event1.dict_['data'] = spread - self.eventEngine.put(event1) - - event2 = Event(EVENT_SPREADTRADING_POS) - event2.dict_['data'] = spread - self.eventEngine.put(event2) + self.putSpreadPosEvent(spread) #---------------------------------------------------------------------- def processPosEvent(self, event): @@ -289,8 +284,8 @@ class StAlgoEngine(object): self.mainEngine = mainEngine self.eventEngine = eventEngine - self.algoDict = {} # spreadName:algo - self.vtSymbolAlgoDict = {} # vtSymbol:algo + self.algoDict = OrderedDict() # spreadName:algo + self.vtSymbolAlgoDict = {} # vtSymbol:algo self.registerEvent() diff --git a/vnpy/trader/app/spreadTrading/uiStWidget.py b/vnpy/trader/app/spreadTrading/uiStWidget.py index 7f505c8b..1b77c27d 100644 --- a/vnpy/trader/app/spreadTrading/uiStWidget.py +++ b/vnpy/trader/app/spreadTrading/uiStWidget.py @@ -2,6 +2,8 @@ from collections import OrderedDict +from six import text_type + from vnpy.event import Event from vnpy.trader.uiQt import QtWidgets, QtCore from vnpy.trader.uiBasicWidget import (BasicMonitor, BasicCell, PnlCell, @@ -338,7 +340,7 @@ class StModeComboBox(QtWidgets.QComboBox): #---------------------------------------------------------------------- def setMode(self): """设置模式""" - mode = unicode(self.currentText()) + mode = text_type(self.currentText()) self.algoEngine.setAlgoMode(self.spreadName, mode) #---------------------------------------------------------------------- @@ -413,6 +415,7 @@ class StActiveButton(QtWidgets.QPushButton): ######################################################################## class StAlgoManager(QtWidgets.QTableWidget): """价差算法管理组件""" + signalPos = QtCore.pyqtSignal(type(Event())) #---------------------------------------------------------------------- def __init__(self, stEngine, parent=None): @@ -420,16 +423,20 @@ class StAlgoManager(QtWidgets.QTableWidget): super(StAlgoManager, self).__init__(parent) self.algoEngine = stEngine.algoEngine + self.eventEngine = stEngine.eventEngine self.buttonActiveDict = {} # spreadName: buttonActive + self.posCellDict = {} # spreadName: cell self.initUi() + self.registerEvent() #---------------------------------------------------------------------- def initUi(self): """初始化表格""" headers = [u'价差', u'算法', + u'净持仓' 'BuyPrice', 'SellPrice', 'CoverPrice', @@ -460,6 +467,7 @@ class StAlgoManager(QtWidgets.QTableWidget): for row, d in enumerate(l): cellSpreadName = QtWidgets.QTableWidgetItem(d['spreadName']) cellAlgoName = QtWidgets.QTableWidgetItem(d['algoName']) + cellNetPos = QtWidgets.QTableWidgetItem('0') spinBuyPrice = StBuyPriceSpinBox(algoEngine, d['spreadName'], d['buyPrice']) spinSellPrice = StSellPriceSpinBox(algoEngine, d['spreadName'], d['sellPrice']) spinShortPrice = StShortPriceSpinBox(algoEngine, d['spreadName'], d['shortPrice']) @@ -471,14 +479,15 @@ class StAlgoManager(QtWidgets.QTableWidget): self.setItem(row, 0, cellSpreadName) self.setItem(row, 1, cellAlgoName) - self.setCellWidget(row, 2, spinBuyPrice) - self.setCellWidget(row, 3, spinSellPrice) - self.setCellWidget(row, 4, spinCoverPrice) - self.setCellWidget(row, 5, spinShortPrice) - self.setCellWidget(row, 6, spinMaxOrderSize) - self.setCellWidget(row, 7, spinMaxPosSize) - self.setCellWidget(row, 8, comboMode) - self.setCellWidget(row, 9, buttonActive) + self.setItem(row, 2, cellNetPos) + self.setCellWidget(row, 3, spinBuyPrice) + self.setCellWidget(row, 4, spinSellPrice) + self.setCellWidget(row, 5, spinCoverPrice) + self.setCellWidget(row, 6, spinShortPrice) + self.setCellWidget(row, 7, spinMaxOrderSize) + self.setCellWidget(row, 8, spinMaxPosSize) + self.setCellWidget(row, 9, comboMode) + self.setCellWidget(row, 10, buttonActive) buttonActive.signalActive.connect(spinBuyPrice.algoActiveChanged) buttonActive.signalActive.connect(spinSellPrice.algoActiveChanged) @@ -489,12 +498,28 @@ class StAlgoManager(QtWidgets.QTableWidget): buttonActive.signalActive.connect(comboMode.algoActiveChanged) self.buttonActiveDict[d['spreadName']] = buttonActive + self.posCellDict[d['spreadName']] = cellNetPos #---------------------------------------------------------------------- def stopAll(self): """停止所有算法""" for button in self.buttonActiveDict.values(): button.stop() + + #---------------------------------------------------------------------- + def processStPosEvent(self, event): + """""" + pos = event.dict_['data'] + cell = self.posCellDict[pos.name] + cell.setText(str(pos.netPos)) + + #---------------------------------------------------------------------- + def registerEvent(self): + """""" + self.signalPos.connect(self.processStPosEvent) + + self.eventEngine.register(EVENT_SPREADTRADING_POS, self.signalPos.emit) + ######################################################################## @@ -582,4 +607,4 @@ class StManager(QtWidgets.QWidget): - \ No newline at end of file + diff --git a/vnpy/trader/gateway/bigoneGateway/__init__.py b/vnpy/trader/gateway/bigoneGateway/__init__.py new file mode 100644 index 00000000..4796cae6 --- /dev/null +++ b/vnpy/trader/gateway/bigoneGateway/__init__.py @@ -0,0 +1,10 @@ +# encoding: UTF-8 + +from vnpy.trader import vtConstant +from .bigoneGateway import BigoneGateway + +gatewayClass = BigoneGateway +gatewayName = 'BIGONE' +gatewayDisplayName = 'BIGONE' +gatewayType = vtConstant.GATEWAYTYPE_BTC +gatewayQryEnabled = True \ No newline at end of file diff --git a/vnpy/trader/gateway/bigoneGateway/bigoneGateway.py b/vnpy/trader/gateway/bigoneGateway/bigoneGateway.py new file mode 100644 index 00000000..1c60ecaa --- /dev/null +++ b/vnpy/trader/gateway/bigoneGateway/bigoneGateway.py @@ -0,0 +1,512 @@ +# encoding: UTF-8 + +''' +vnpy.api.bigone的gateway接入 +''' +from __future__ import print_function + +import os +import json +import time +import traceback +from datetime import datetime, timedelta +from copy import copy +from math import pow + +from vnpy.api.bigone import BigoneRestApi +from vnpy.trader.vtGateway import * +from vnpy.trader.vtFunction import getJsonPath, getTempPath + + + +# 委托状态类型映射 +statusMapReverse = {} +statusMapReverse['PENDING'] = STATUS_NOTTRADED +statusMapReverse['FILLED'] = STATUS_ALLTRADED +statusMapReverse['CANCELED'] = STATUS_CANCELLED + +# 方向映射 +directionMap = {} +directionMap[DIRECTION_LONG] = 'BID' +directionMap[DIRECTION_SHORT] = 'ASK' +directionMapReverse = {v:k for k,v in directionMap.items()} + + +######################################################################## +class BigoneGateway(VtGateway): + """Bigone接口""" + + #---------------------------------------------------------------------- + def __init__(self, eventEngine, gatewayName=''): + """Constructor""" + super(BigoneGateway, self).__init__(eventEngine, gatewayName) + + self.restApi = RestApi(self) + + self.qryEnabled = False # 是否要启动循环查询 + + self.fileName = self.gatewayName + '_connect.json' + self.filePath = getJsonPath(self.fileName, __file__) + + #---------------------------------------------------------------------- + def connect(self): + """连接""" + try: + f = file(self.filePath) + except IOError: + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = u'读取连接配置出错,请检查' + self.onLog(log) + return + + # 解析json文件 + setting = json.load(f) + try: + apiKey = str(setting['apiKey']) + apiSecret = str(setting['apiSecret']) + symbols = setting['symbols'] + except KeyError: + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = u'连接配置缺少字段,请检查' + self.onLog(log) + return + + # 创建行情和交易接口对象 + self.restApi.connect(apiKey, apiSecret, symbols) + + # 初始化并启动查询 + self.initQuery() + + #---------------------------------------------------------------------- + def subscribe(self, subscribeReq): + """订阅行情""" + pass + + #---------------------------------------------------------------------- + def sendOrder(self, orderReq): + """发单""" + return self.restApi.sendOrder(orderReq) + + #---------------------------------------------------------------------- + def cancelOrder(self, cancelOrderReq): + """撤单""" + self.restApi.cancelOrder(cancelOrderReq) + + #---------------------------------------------------------------------- + def close(self): + """关闭""" + self.restApi.close() + + #---------------------------------------------------------------------- + def initQuery(self): + """初始化连续查询""" + if self.qryEnabled: + # 需要循环的查询函数列表 + self.qryFunctionList = [self.restApi.qryTickers, + self.restApi.qryDepth, + self.restApi.qryAccount, + self.restApi.qryOrder] + + self.qryCount = 0 # 查询触发倒计时 + self.qryTrigger = 1 # 查询触发点 + self.qryNextFunction = 0 # 上次运行的查询函数索引 + + self.startQuery() + + #---------------------------------------------------------------------- + def query(self, event): + """注册到事件处理引擎上的查询函数""" + self.qryCount += 1 + + if self.qryCount > self.qryTrigger: + # 清空倒计时 + self.qryCount = 0 + + # 执行查询函数 + function = self.qryFunctionList[self.qryNextFunction] + function() + + # 计算下次查询函数的索引,如果超过了列表长度,则重新设为0 + self.qryNextFunction += 1 + if self.qryNextFunction == len(self.qryFunctionList): + self.qryNextFunction = 0 + + #---------------------------------------------------------------------- + def startQuery(self): + """启动连续查询""" + self.eventEngine.register(EVENT_TIMER, self.query) + + #---------------------------------------------------------------------- + def setQryEnabled(self, qryEnabled): + """设置是否要启动循环查询""" + self.qryEnabled = qryEnabled + + +######################################################################## +class RestApi(BigoneRestApi): + """REST API实现""" + + #---------------------------------------------------------------------- + def __init__(self, gateway): + """Constructor""" + super(RestApi, self).__init__() + + self.gateway = gateway # gateway对象 + self.gatewayName = gateway.gatewayName # gateway对象名称 + + self.localID = 0 + self.tradeID = 0 + + self.orderDict = {} # sysID:order + self.localSysDict = {} # localID:sysID + self.reqOrderDict = {} # reqID:order + self.cancelDict = {} # localID:req + + self.tickDict = {} + + #---------------------------------------------------------------------- + def connect(self, apiKey, apiSecret, symbols): + """连接服务器""" + self.init(apiKey, apiSecret) + self.start() + + self.symbols = symbols + self.writeLog(u'REST API启动成功') + + self.qryContract() + + #---------------------------------------------------------------------- + def writeLog(self, content): + """发出日志""" + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = content + self.gateway.onLog(log) + + #---------------------------------------------------------------------- + def sendOrder(self, orderReq): + """""" + #orderReq.price = 300.0 + #orderReq.volume = 0.01 + + self.localID += 1 + orderID = str(self.localID) + vtOrderID = '.'.join([self.gatewayName, orderID]) + + req = { + 'market_id': orderReq.symbol, + 'side': directionMap[orderReq.direction], + 'price': str(orderReq.price), + 'amount': str(orderReq.volume) + } + + reqid = self.addReq('POST', '/viewer/orders', self.onSendOrder, postdict=req) + + # 缓存委托数据对象 + order = VtOrderData() + order.gatewayName = self.gatewayName + order.symbol = orderReq.symbol + order.exchange = EXCHANGE_BIGONE + order.vtSymbol = '.'.join([order.symbol, order.exchange]) + order.orderID = orderID + order.vtOrderID = vtOrderID + order.price = orderReq.price + order.totalVolume = orderReq.volume + order.direction = orderReq.direction + order.status = STATUS_UNKNOWN + + self.reqOrderDict[reqid] = order + + return vtOrderID + + #---------------------------------------------------------------------- + def cancelOrder(self, cancelOrderReq): + """""" + localID = cancelOrderReq.orderID + + if localID in self.localSysDict: + sysID = self.localSysDict[localID] + path = '/viewer/orders/%s/cancel' %sysID + self.addReq('POST', path, self.onCancelOrder) + else: + self.cancelDict[localID] = cancelOrderReq + + #---------------------------------------------------------------------- + def qryContract(self): + """""" + self.addReq('GET', '/markets', self.onQryContract) + + #---------------------------------------------------------------------- + def qryTickers(self): + """""" + self.addReq('GET', '/tickers', self.onQryTickers) + + #---------------------------------------------------------------------- + def qryDepth(self): + """""" + for symbol in self.symbols: + path = '/markets/%s/depth' %symbol + self.addReq('GET', path, self.onQryDepth) + + #---------------------------------------------------------------------- + def qryOrder(self): + """""" + #for symbol in self.symbols: + #req = { + #'market_id': symbol, + #'last': 100 + #} + #self.addReq('GET', '/viewer/orders', self.onQryOrder, params=req) + + req = { + #'market_id': symbol, + 'last': 100 + } + self.addReq('GET', '/viewer/orders', self.onQryOrder, params=req) + + #---------------------------------------------------------------------- + def qryAccount(self): + """""" + self.addReq('GET', '/viewer/accounts', self.onQryAccount) + + #---------------------------------------------------------------------- + def onSendOrder(self, data, reqid): + """""" + if self.checkError(u'委托', data): + return + + d = data['data'] + + order = self.reqOrderDict[reqid] + localID = order.orderID + sysID = d['id'] + + self.localSysDict[localID] = sysID + self.orderDict[sysID] = order + + self.gateway.onOrder(order) + + # 发出等待的撤单委托 + if localID in self.cancelDict: + req = self.cancelDict[localID] + self.cancelOrder(req) + del self.cancelDict[localID] + + #---------------------------------------------------------------------- + def onCancelOrder(self, data, reqid): + """""" + if self.checkError(u'撤单', data): + return + + #---------------------------------------------------------------------- + def onError(self, code, error): + """""" + msg = u'发生异常,错误代码:%s,错误信息:%s' %(code, error) + self.writeLog(msg) + + #---------------------------------------------------------------------- + def onQryOrder(self, data, reqid): + """""" + if self.checkError(u'查询委托', data): + return + + for node in data['data']['edges']: + orderUpdated = False + tradeUpdated = False + d = node['node'] + + # 获取委托对象 + sysID = d['id'] + if sysID in self.orderDict: + order = self.orderDict[sysID] + else: + order = VtOrderData() + order.gatewayName = self.gatewayName + + order.symbol = d['market_id'] + order.exchange = EXCHANGE_BIGONE + order.vtSymbol = '.'.join([order.symbol, order.exchange]) + + self.localID += 1 + localID = str(self.localID) + self.localSysDict[localID] = sysID + + order.orderID = localID + order.vtOrderID = '.'.join([order.gatewayName, order.orderID]) + + order.direction = directionMapReverse[d['side']] + order.price = float(d['price']) + order.totalVolume = float(d['amount']) + + self.orderDict[sysID] = order + orderUpdated = True + + # 检查是否委托有变化 + newTradedVolume = float(d['filled_amount']) + newStatus = statusMapReverse[d['state']] + + if newTradedVolume != float(order.tradedVolume) or newStatus != order.status: + orderUpdated = True + + if newTradedVolume != float(order.tradedVolume): + tradeUpdated = True + newVolume = newTradedVolume - order.tradedVolume + + order.tradedVolume = newTradedVolume + order.status = newStatus + + # 若有更新才推送 + if orderUpdated: + self.gateway.onOrder(order) + + if tradeUpdated: + # 推送成交 + trade = VtTradeData() + trade.gatewayName = order.gatewayName + + trade.symbol = order.symbol + trade.vtSymbol = order.vtSymbol + + trade.orderID = order.orderID + trade.vtOrderID = order.vtOrderID + + self.tradeID += 1 + trade.tradeID = str(self.tradeID) + trade.vtTradeID = '.'.join([self.gatewayName, trade.tradeID]) + + trade.direction = order.direction + trade.price = order.price + trade.volume = newTradedVolume + trade.tradeTime = datetime.now().strftime('%H:%M:%S') + + self.gateway.onTrade(trade) + + #---------------------------------------------------------------------- + def onQryAccount(self, data, reqid): + """""" + if self.checkError(u'查询资金', data): + return + + for d in data['data']: + account = VtAccountData() + account.gatewayName = self.gatewayName + + account.accountID = d['asset_id'] + account.vtAccountID = '.'.join([account.gatewayName, account.accountID]) + account.balance = float(d['balance']) + account.available = account.balance - float(d['locked_balance']) + + self.gateway.onAccount(account) + + #---------------------------------------------------------------------- + def onQryContract(self, data, reqid): + """""" + if self.checkError(u'查询合约', data): + return + + for d in data['data']: + contract = VtContractData() + contract.gatewayName = self.gatewayName + + contract.symbol = d['name'] + contract.exchange = EXCHANGE_BIGONE + contract.vtSymbol = '.'.join([contract.symbol, contract.exchange]) + contract.name = contract.vtSymbol + contract.productClass = PRODUCT_SPOT + contract.priceTick = pow(10, -int(d['quoteScale'])) + contract.size = 1 + + self.gateway.onContract(contract) + + self.writeLog(u'合约信息查询完成') + + #---------------------------------------------------------------------- + def onQryTickers(self, data, reqid): + """""" + if self.checkError(u'查询行情', data): + return + + dt = datetime.now() + date = dt.strftime('%Y%m%d') + time = dt.strftime('%H:%M:%S') + + for d in data['data']: + symbol = str(d['market_id']) + tick = self.getTick(symbol) + + tick.openPrice = float(d['open']) + #tick.highPrice = float(d['high']) + #tick.lowPrice = float(d['low']) + tick.lastPrice = float(d['close']) + #tick.volume = float(d['volume']) + tick.datetime = datetime + tick.date = date + tick.time = time + + # 只有订阅了深度行情才推送 + if tick.bidPrice1: + self.gateway.onTick(tick) + + #---------------------------------------------------------------------- + def onQryDepth(self, data, reqid): + """""" + if self.checkError(u'查询深度', data): + return + + d = data['data'] + symbol = d['market_id'] + + tick = self.getTick(symbol) + + for n, bid in enumerate(d['bids'][:5]): + tick.__setattr__('bidPrice%s' %(n+1), float(bid['price'])) + tick.__setattr__('bidVolume%s' %(n+1), float(bid['amount'])) + + for n, ask in enumerate(d['asks'][:5]): + tick.__setattr__('askPrice%s' %(n+1), float(ask['price'])) + tick.__setattr__('askVolume%s' %(n+1), float(ask['amount'])) + + tick.datetime = datetime.now() + tick.date = tick.datetime.strftime('%Y%m%d') + tick.time = tick.datetime.strftime('%H:%M:%S') + + if tick.lastPrice: + self.gateway.onTick(tick) + + #---------------------------------------------------------------------- + def getTick(self, symbol): + """""" + tick = self.tickDict.get(symbol, None) + + if not tick: + tick = VtTickData() + tick.gatewayName = self.gatewayName + tick.symbol = symbol + tick.exchange = EXCHANGE_BIGONE + tick.vtSymbol = '.'.join([tick.symbol, tick.exchange]) + self.tickDict[symbol] = tick + + return tick + + #---------------------------------------------------------------------- + def checkError(self, name, data): + """""" + error = data.get('errors', None) + if not error: + return False + + msg = str(error) + self.writeLog(u'%s触发错误:%s' %(name, msg)) + return True + +#---------------------------------------------------------------------- +def printDict(d): + """""" + print('-' * 30) + l = d.keys() + l.sort() + for k in l: + print(k, d[k]) + \ No newline at end of file diff --git a/vnpy/trader/gateway/binanceGateway/BINANCE_connect.json b/vnpy/trader/gateway/binanceGateway/BINANCE_connect.json new file mode 100644 index 00000000..01d370e4 --- /dev/null +++ b/vnpy/trader/gateway/binanceGateway/BINANCE_connect.json @@ -0,0 +1,5 @@ +{ + "apiKey": "", + "secretKey": "", + "symbols": ["BTCUSDT", "ETHUSDT", "ETHBTC"] +} \ No newline at end of file diff --git a/vnpy/trader/gateway/binanceGateway/__init__.py b/vnpy/trader/gateway/binanceGateway/__init__.py new file mode 100644 index 00000000..05256200 --- /dev/null +++ b/vnpy/trader/gateway/binanceGateway/__init__.py @@ -0,0 +1,10 @@ +# encoding: UTF-8 + +from vnpy.trader import vtConstant +from .binanceGateway import BinanceGateway + +gatewayClass = BinanceGateway +gatewayName = 'BINANCE' +gatewayDisplayName = u'币安' +gatewayType = vtConstant.GATEWAYTYPE_BTC +gatewayQryEnabled = True \ No newline at end of file diff --git a/vnpy/trader/gateway/binanceGateway/binanceGateway.py b/vnpy/trader/gateway/binanceGateway/binanceGateway.py new file mode 100644 index 00000000..d133021f --- /dev/null +++ b/vnpy/trader/gateway/binanceGateway/binanceGateway.py @@ -0,0 +1,490 @@ +# encoding: UTF-8 + +''' +vnpy.api.binance的gateway接入 +''' +from __future__ import print_function + +import os +import json +from datetime import datetime, timedelta +from copy import copy + +from vnpy.api.binance import BinanceApi +from vnpy.trader.vtGateway import * +from vnpy.trader.vtFunction import getJsonPath, getTempPath + + +# 委托状态类型映射 +statusMapReverse = {} +statusMapReverse['NEW'] = STATUS_NOTTRADED +statusMapReverse['PARTIALLY_FILLED'] = STATUS_PARTTRADED +statusMapReverse['FILLED'] = STATUS_ALLTRADED +statusMapReverse['CANCELED'] = STATUS_CANCELLED +statusMapReverse['REJECTED'] = STATUS_REJECTED +statusMapReverse['EXPIRED'] = STATUS_CANCELLED + +# 方向映射 +directionMap = {} +directionMap[DIRECTION_LONG] = 'BUY' +directionMap[DIRECTION_SHORT] = 'SELL' +directionMapReverse = {v:k for k,v in directionMap.items()} + +# 价格类型映射 +priceTypeMap = {} +priceTypeMap[PRICETYPE_LIMITPRICE] = 'LIMIT' +priceTypeMap[PRICETYPE_MARKETPRICE] = 'MARKET' + + + + +#---------------------------------------------------------------------- +def print_dict(d): + """""" + print('-' * 30) + l = d.keys() + l.sort() + for k in l: + print('%s:%s' %(k, d[k])) + + +######################################################################## +class BinanceGateway(VtGateway): + """币安接口""" + + #---------------------------------------------------------------------- + def __init__(self, eventEngine, gatewayName=''): + """Constructor""" + super(BinanceGateway, self).__init__(eventEngine, gatewayName) + + self.api = GatewayApi(self) + + self.qryEnabled = False # 是否要启动循环查询 + + self.fileName = self.gatewayName + '_connect.json' + self.filePath = getJsonPath(self.fileName, __file__) + + #---------------------------------------------------------------------- + def connect(self): + """连接""" + try: + f = file(self.filePath) + except IOError: + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = u'读取连接配置出错,请检查' + self.onLog(log) + return + + # 解析json文件 + setting = json.load(f) + try: + apiKey = str(setting['apiKey']) + secretKey = str(setting['secretKey']) + symbols = setting['symbols'] + except KeyError: + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = u'连接配置缺少字段,请检查' + self.onLog(log) + return + + # 创建行情和交易接口对象 + self.api.connect(apiKey, secretKey, symbols) + + # 初始化并启动查询 + #self.initQuery() + + #---------------------------------------------------------------------- + def subscribe(self, subscribeReq): + """订阅行情""" + pass + + #---------------------------------------------------------------------- + def sendOrder(self, orderReq): + """发单""" + return self.api.sendOrder(orderReq) + + #---------------------------------------------------------------------- + def cancelOrder(self, cancelOrderReq): + """撤单""" + self.api.cancel(cancelOrderReq) + + #---------------------------------------------------------------------- + def close(self): + """关闭""" + self.api.close() + + #---------------------------------------------------------------------- + def queryAccount(self): + """""" + self.api.queryAccount() + + #---------------------------------------------------------------------- + def initQuery(self): + """初始化连续查询""" + if self.qryEnabled: + # 需要循环的查询函数列表 + self.qryFunctionList = [self.queryAccount] + + self.qryCount = 0 # 查询触发倒计时 + self.qryTrigger = 1 # 查询触发点 + self.qryNextFunction = 0 # 上次运行的查询函数索引 + + self.startQuery() + + #---------------------------------------------------------------------- + def query(self, event): + """注册到事件处理引擎上的查询函数""" + self.qryCount += 1 + + if self.qryCount > self.qryTrigger: + # 清空倒计时 + self.qryCount = 0 + + # 执行查询函数 + function = self.qryFunctionList[self.qryNextFunction] + function() + + # 计算下次查询函数的索引,如果超过了列表长度,则重新设为0 + self.qryNextFunction += 1 + if self.qryNextFunction == len(self.qryFunctionList): + self.qryNextFunction = 0 + + #---------------------------------------------------------------------- + def startQuery(self): + """启动连续查询""" + self.eventEngine.register(EVENT_TIMER, self.query) + + #---------------------------------------------------------------------- + def setQryEnabled(self, qryEnabled): + """设置是否要启动循环查询""" + self.qryEnabled = qryEnabled + + +######################################################################## +class GatewayApi(BinanceApi): + """API实现""" + + #---------------------------------------------------------------------- + def __init__(self, gateway): + """Constructor""" + super(GatewayApi, self).__init__() + + self.gateway = gateway # gateway对象 + self.gatewayName = gateway.gatewayName # gateway对象名称 + + self.date = datetime.now().strftime('%y%m%d%H%M%S') + self.orderId = 0 + + self.tickDict = {} + + #---------------------------------------------------------------------- + def connect(self, apiKey, secretKey, symbols): + """连接服务器""" + self.init(apiKey, secretKey) + self.start() + self.writeLog(u'交易API启动成功') + + l = [] + for symbol in symbols: + symbol = symbol.lower() + l.append(symbol+'@ticker') + l.append(symbol+'@depth5') + self.initDataStream(l) + self.writeLog(u'行情推送订阅成功') + + self.startStream() + + # 初始化查询 + self.queryExchangeInfo() + self.queryAccount() + + for symbol in symbols: + self.queryOpenOrders(symbol.upper()) + + #---------------------------------------------------------------------- + def writeLog(self, content): + """发出日志""" + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = content + self.gateway.onLog(log) + + #---------------------------------------------------------------------- + def onError(self, data, reqid): + """""" + err = VtErrorData() + err.gatewayName = self.gatewayName + err.errorID = data['code'] + err.errorMsg = data['msg'] + self.gateway.onError(err) + + #---------------------------------------------------------------------- + def onQueryExchangeInfo(self, data, reqid): + """""" + for d in data['symbols']: + contract = VtContractData() + contract.gatewayName = self.gatewayName + + contract.symbol = d['symbol'] + contract.exchange = EXCHANGE_BINANCE + contract.vtSymbol = '.'.join([contract.symbol, contract.exchange]) + contract.name = contract.vtSymbol + contract.productClass = PRODUCT_SPOT + contract.size = 1 + + for f in d['filters']: + if f['filterType'] == 'PRICE_FILTER': + contract.priceTick = float(f['tickSize']) + + self.gateway.onContract(contract) + + #---------------------------------------------------------------------- + def onNewOrder(self, data, reqid): + """""" + pass + + #---------------------------------------------------------------------- + def onCancelOrder(self, data, reqid): + """""" + pass + + #---------------------------------------------------------------------- + def onQueryOpenOrders(self, data, reqid): + """""" + for d in data: + order = VtOrderData() + order.gatewayName = self.gatewayName + + order.symbol = d['symbol'] + order.exchange = EXCHANGE_BINANCE + order.vtSymbol = '.'.join([order.symbol, order.exchange]) + + order.orderID = d['clientOrderId'] + order.vtOrderID = '.'.join([order.gatewayName, order.orderID]) + + order.direction = directionMapReverse[d['side']] + order.price = float(d['price']) + order.totalVolume = float(d['origQty']) + order.tradedVolume = float(d['executedQty']) + date, order.orderTime = self.generateDateTime(d['time']) + order.status = statusMapReverse[d['status']] + + self.gateway.onOrder(order) + + #---------------------------------------------------------------------- + def onQueryAllOrders(self, data, reqid): + """""" + pass + + #---------------------------------------------------------------------- + def onQueryAccount(self, data, reqid): + """""" + for d in data['balances']: + free = float(d['free']) + locked = float(d['locked']) + + account = VtAccountData() + account.gatewayName = self.gatewayName + + account.accountID = d['asset'] + account.vtAccountID = '.'.join([account.gatewayName, account.accountID]) + account.balance = free + locked + account.available = free + + self.gateway.onAccount(account) + + #---------------------------------------------------------------------- + def onQueryMyTrades(self, data, reqid): + """""" + pass + + #---------------------------------------------------------------------- + def onStartStream(self, data, reqid): + """""" + key = data['listenKey'] + self.initUserStream(key) + self.writeLog(u'交易推送订阅成功') + + #---------------------------------------------------------------------- + def onKeepaliveStream(self, data, reqid): + """""" + self.writeLog(u'交易推送刷新成功') + + #---------------------------------------------------------------------- + def onCloseStream(self, data, reqid): + """""" + self.writeLog(u'交易推送关闭') + + #---------------------------------------------------------------------- + def onUserData(self, data): + """""" + if data['e'] == 'outboundAccountInfo': + self.onPushAccount(data) + elif data['e'] == 'executionReport': + self.onPushOrder(data) + + #---------------------------------------------------------------------- + def onPushAccount(self, data): + """""" + for d in data['B']: + free = float(d['f']) + locked = float(d['l']) + + account = VtAccountData() + account.gatewayName = self.gatewayName + + account.accountID = d['a'] + account.vtAccountID = '.'.join([account.gatewayName, account.accountID]) + account.balance = free + locked + account.available = free + + self.gateway.onAccount(account) + + #---------------------------------------------------------------------- + def onPushOrder(self, d): + """""" + # 委托更新 + order = VtOrderData() + order.gatewayName = self.gatewayName + + order.symbol = d['s'] + order.exchange = EXCHANGE_BINANCE + order.vtSymbol = '.'.join([order.symbol, order.exchange]) + + if d['C'] != 'null': + order.orderID = d['C'] # 撤单原始委托号 + else: + order.orderID = d['c'] + order.vtOrderID = '.'.join([order.gatewayName, order.orderID]) + + order.direction = directionMapReverse[d['S']] + order.price = float(d['p']) + order.totalVolume = float(d['q']) + order.tradedVolume = float(d['z']) + date, order.orderTime = self.generateDateTime(d['T']) + order.status = statusMapReverse[d['X']] + + self.gateway.onOrder(order) + + # 成交更新 + if float(d['l']): + trade = VtTradeData() + trade.gatewayName = self.gatewayName + + trade.symbol = order.symbol + trade.exchange = order.exchange + trade.vtSymbol = order.vtSymbol + trade.orderID = order.orderID + trade.vtOrderID = order.vtOrderID + trade.tradeID = str(d['t']) + trade.vtTradeID = '.'.join([trade.gatewayName, trade.tradeID]) + trade.direction = order.direction + trade.price = float(d['L']) + trade.volume = float(d['l']) + date, trade.tradeTime = self.generateDateTime(d['E']) + + self.gateway.onTrade(trade) + + #---------------------------------------------------------------------- + def onMarketData(self, data): + """""" + name = data['stream'] + symbol, channel = name.split('@') + symbol = symbol.upper() + + if symbol in self.tickDict: + tick = self.tickDict[symbol] + else: + tick = VtTickData() + tick.gatewayName = self.gatewayName + tick.symbol = symbol + tick.exchange = EXCHANGE_BINANCE + tick.vtSymbol = '.'.join([tick.symbol, tick.exchange]) + + self.tickDict[symbol] = tick + + data = data['data'] + if channel == 'ticker': + tick.volume = float(data['v']) + tick.openPrice = float(data['o']) + tick.highPrice = float(data['h']) + tick.lowPrice = float(data['l']) + tick.lastPrice = float(data['c']) + tick.date, tick.time = self.generateDateTime(data['E']) + else: + tick.askPrice1, tick.askVolume1, buf = data['asks'][0] + tick.askPrice2, tick.askVolume2, buf = data['asks'][1] + tick.askPrice3, tick.askVolume3, buf = data['asks'][2] + tick.askPrice4, tick.askVolume4, buf = data['asks'][3] + tick.askPrice5, tick.askVolume5, buf = data['asks'][4] + + tick.bidPrice1, tick.bidVolume1, buf = data['bids'][0] + tick.bidPrice2, tick.bidVolume2, buf = data['bids'][1] + tick.bidPrice3, tick.bidVolume3, buf = data['bids'][2] + tick.bidPrice4, tick.bidVolume4, buf = data['bids'][3] + tick.bidPrice5, tick.bidVolume5, buf = data['bids'][4] + + tick.askPrice1 = float(tick.askPrice1) + tick.askPrice2 = float(tick.askPrice2) + tick.askPrice3 = float(tick.askPrice3) + tick.askPrice4 = float(tick.askPrice4) + tick.askPrice5 = float(tick.askPrice5) + + tick.bidPrice1 = float(tick.bidPrice1) + tick.bidPrice2 = float(tick.bidPrice2) + tick.bidPrice3 = float(tick.bidPrice3) + tick.bidPrice4 = float(tick.bidPrice4) + tick.bidPrice5 = float(tick.bidPrice5) + + tick.askVolume1 = float(tick.askVolume1) + tick.askVolume2 = float(tick.askVolume2) + tick.askVolume3 = float(tick.askVolume3) + tick.askVolume4 = float(tick.askVolume4) + tick.askVolume5 = float(tick.askVolume5) + + tick.bidVolume1 = float(tick.bidVolume1) + tick.bidVolume2 = float(tick.bidVolume2) + tick.bidVolume3 = float(tick.bidVolume3) + tick.bidVolume4 = float(tick.bidVolume4) + tick.bidVolume5 = float(tick.bidVolume5) + + self.gateway.onTick(copy(tick)) + + #---------------------------------------------------------------------- + def onDataStreamError(self, msg): + """""" + self.writeLog(msg) + + #---------------------------------------------------------------------- + def onUserStreamError(self, msg): + """""" + self.writeLog(msg) + + #---------------------------------------------------------------------- + def generateDateTime(self, s): + """生成时间""" + dt = datetime.fromtimestamp(float(s)/1e3) + time = dt.strftime("%H:%M:%S.%f") + date = dt.strftime("%Y%m%d") + return date, time + + #---------------------------------------------------------------------- + def sendOrder(self, orderReq): + """""" + self.orderId += 1 + orderId = self.date + str(self.orderId).rjust(6, '0') + vtOrderID = '.'.join([self.gatewayName, orderId]) + side = directionMap.get(orderReq.direction, '') + type_ = priceTypeMap.get(orderReq.priceType, PRICETYPE_LIMITPRICE) + + self.newOrder(orderReq.symbol, side, type_, orderReq.price, + orderReq.volume, 'GTC', newClientOrderId=orderId) + + return vtOrderID + + #---------------------------------------------------------------------- + def cancel(self, cancelOrderReq): + """""" + self.cancelOrder(cancelOrderReq.symbol, origClientOrderId=cancelOrderReq.orderID) \ No newline at end of file diff --git a/vnpy/trader/gateway/bitfinexGateway/BITFINEX_connect.json b/vnpy/trader/gateway/bitfinexGateway/BITFINEX_connect.json new file mode 100644 index 00000000..ae25ff9c --- /dev/null +++ b/vnpy/trader/gateway/bitfinexGateway/BITFINEX_connect.json @@ -0,0 +1,5 @@ +{ + "apiKey": "", + "secretKey": "", + "symbols": ["BTCUSD", "ETHUSD", "ETHBTC"] +} \ No newline at end of file diff --git a/vnpy/trader/gateway/bitfinexGateway/__init__.py b/vnpy/trader/gateway/bitfinexGateway/__init__.py new file mode 100644 index 00000000..831aa287 --- /dev/null +++ b/vnpy/trader/gateway/bitfinexGateway/__init__.py @@ -0,0 +1,10 @@ +# encoding: UTF-8 + +from vnpy.trader import vtConstant +from .bitfinexGateway import BitfinexGateay + +gatewayClass = BitfinexGateay +gatewayName = 'BITFINEX' +gatewayDisplayName = u'BITFINEX' +gatewayType = vtConstant.GATEWAYTYPE_BTC +gatewayQryEnabled = True \ No newline at end of file diff --git a/vnpy/trader/gateway/bitfinexGateway/bitfinexGateway.py b/vnpy/trader/gateway/bitfinexGateway/bitfinexGateway.py new file mode 100644 index 00000000..46947cd9 --- /dev/null +++ b/vnpy/trader/gateway/bitfinexGateway/bitfinexGateway.py @@ -0,0 +1,550 @@ +# encoding: UTF-8 + +''' +vnpy.api.bitfinex的gateway接入 +''' + +import os +import json +import hashlib +import hmac +import time +from datetime import datetime, timedelta +from copy import copy +from math import pow + +from vnpy.api.bitfinex import BitfinexApi +from vnpy.trader.vtGateway import * +from vnpy.trader.vtFunction import getJsonPath, getTempPath + +# 委托状态类型映射 +statusMapReverse = {} +statusMapReverse['ACTIVE'] = STATUS_NOTTRADED +statusMapReverse['PARTIALLY FILLED'] = STATUS_PARTTRADED +statusMapReverse['EXECUTED'] = STATUS_ALLTRADED +statusMapReverse['CANCELED'] = STATUS_CANCELLED + +# 价格类型映射 +priceTypeMap = {} +priceTypeMap[PRICETYPE_LIMITPRICE] = 'EXCHANGE LIMIT' +priceTypeMap[PRICETYPE_MARKETPRICE] = 'EXCHANGE MARKET' + + + +######################################################################## +class BitfinexGateay(VtGateway): + """Bitfinex接口""" + + #---------------------------------------------------------------------- + def __init__(self, eventEngine, gatewayName=''): + """Constructor""" + super(BitfinexGateay, self).__init__(eventEngine, gatewayName) + + self.api = GatewayApi(self) + + self.qryEnabled = False # 是否要启动循环查询 + + self.fileName = self.gatewayName + '_connect.json' + self.filePath = getJsonPath(self.fileName, __file__) + + #---------------------------------------------------------------------- + def connect(self): + """连接""" + try: + f = file(self.filePath) + except IOError: + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = u'读取连接配置出错,请检查' + self.onLog(log) + return + + # 解析json文件 + setting = json.load(f) + try: + apiKey = str(setting['apiKey']) + secretKey = str(setting['secretKey']) + symbols = setting['symbols'] + except KeyError: + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = u'连接配置缺少字段,请检查' + self.onLog(log) + return + + # 创建行情和交易接口对象 + self.api.connect(apiKey, secretKey, symbols) + + # 初始化并启动查询 + #self.initQuery() + + #---------------------------------------------------------------------- + def subscribe(self, subscribeReq): + """订阅行情""" + pass + + #---------------------------------------------------------------------- + def sendOrder(self, orderReq): + """发单""" + return self.api.sendOrder(orderReq) + + #---------------------------------------------------------------------- + def cancelOrder(self, cancelOrderReq): + """撤单""" + self.api.cancelOrder(cancelOrderReq) + + #---------------------------------------------------------------------- + def close(self): + """关闭""" + self.api.close() + + #---------------------------------------------------------------------- + def queryAccount(self): + """""" + self.api.queryAccount() + + #---------------------------------------------------------------------- + def initQuery(self): + """初始化连续查询""" + if self.qryEnabled: + # 需要循环的查询函数列表 + self.qryFunctionList = [self.queryAccount] + + self.qryCount = 0 # 查询触发倒计时 + self.qryTrigger = 1 # 查询触发点 + self.qryNextFunction = 0 # 上次运行的查询函数索引 + + self.startQuery() + + #---------------------------------------------------------------------- + def query(self, event): + """注册到事件处理引擎上的查询函数""" + self.qryCount += 1 + + if self.qryCount > self.qryTrigger: + # 清空倒计时 + self.qryCount = 0 + + # 执行查询函数 + function = self.qryFunctionList[self.qryNextFunction] + function() + + # 计算下次查询函数的索引,如果超过了列表长度,则重新设为0 + self.qryNextFunction += 1 + if self.qryNextFunction == len(self.qryFunctionList): + self.qryNextFunction = 0 + + #---------------------------------------------------------------------- + def startQuery(self): + """启动连续查询""" + self.eventEngine.register(EVENT_TIMER, self.query) + + #---------------------------------------------------------------------- + def setQryEnabled(self, qryEnabled): + """设置是否要启动循环查询""" + self.qryEnabled = qryEnabled + + +######################################################################## +class GatewayApi(BitfinexApi): + """API实现""" + + #---------------------------------------------------------------------- + def __init__(self, gateway): + """Constructor""" + super(GatewayApi, self).__init__() + + self.gateway = gateway # gateway对象 + self.gatewayName = gateway.gatewayName # gateway对象名称 + + self.orderId = 1000000 + self.date = int(datetime.now().strftime('%y%m%d%H%M%S')) * self.orderId + + self.apiKey = '' + self.secretKey = '' + self.symbols = [] + self.currencys = [] + + self.tickDict = {} + self.bidDict = {} + self.askDict = {} + self.orderLocalDict = {} + + self.channelDict = {} # ChannelID : (Channel, Symbol) + + #---------------------------------------------------------------------- + def connect(self, apiKey, secretKey, symbols): + """连接服务器""" + self.apiKey = apiKey + self.secretKey = secretKey + self.symbols = symbols + + self.start() + self.writeLog(u'交易API启动成功') + + #---------------------------------------------------------------------- + def onConnect(self): + """""" + for symbol in self.symbols: + self.subscribe(symbol, 'ticker') + self.subscribe(symbol, 'book') + self.writeLog(u'行情推送订阅成功') + + self.authenticate() + self.writeLog(u'交易推送订阅成功') + + self.sendRestReq('/symbols_details', self.onSymbolDetails) + + #---------------------------------------------------------------------- + def subscribe(self, symbol, channel): + """""" + req = { + 'event': 'subscribe', + 'channel': channel, + 'symbol': symbol + } + self.sendReq(req) + + #---------------------------------------------------------------------- + def authenticate(self): + """""" + nonce = int(time.time() * 1000000) + authPayload = 'AUTH' + str(nonce) + signature = hmac.new( + self.secretKey.encode(), + msg = authPayload.encode(), + digestmod = hashlib.sha384 + ).hexdigest() + + req = { + 'apiKey': self.apiKey, + 'event': 'auth', + 'authPayload': authPayload, + 'authNonce': nonce, + 'authSig': signature + } + + self.sendReq(req) + + #---------------------------------------------------------------------- + def writeLog(self, content): + """发出日志""" + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = content + self.gateway.onLog(log) + + #---------------------------------------------------------------------- + def generateDateTime(self, s): + """生成时间""" + dt = datetime.fromtimestamp(s/1000.0) + date = dt.strftime('%Y-%m-%d') + time = dt.strftime("%H:%M:%S.%f") + return date, time + + #---------------------------------------------------------------------- + def sendOrder(self, orderReq): + """""" + self.orderId += 1 + orderId = self.date + self.orderId + vtOrderID = '.'.join([self.gatewayName, str(orderId)]) + + if orderReq.direction == DIRECTION_LONG: + amount = orderReq.volume + else: + amount = -orderReq.volume + + o = { + 'cid': orderId, + 'type': priceTypeMap[orderReq.priceType], + 'symbol': 't' + orderReq.symbol, + 'amount': str(amount), + 'price': str(orderReq.price) + } + + req = [0, 'on', None, o] + self.sendReq(req) + + return vtOrderID + + #---------------------------------------------------------------------- + def cancelOrder(self, cancelOrderReq): + """""" + orderId = int(cancelOrderReq.orderID) + date = cancelOrderReq.sessionID + + req = [ + 0, + 'oc', + None, + { + 'cid': orderId, + 'cid_date': date, + } + ] + + self.sendReq(req) + + #---------------------------------------------------------------------- + def calc(self): + """""" + l = [] + for currency in self.currencys: + l.append(['wallet_exchange_' + currency]) + + req = [0, 'calc', None, l] + self.sendReq(req) + + #---------------------------------------------------------------------- + def onData(self, data): + """""" + if isinstance(data, dict): + self.onResponse(data) + else: + self.onUpdate(data) + + #---------------------------------------------------------------------- + def onResponse(self, data): + """""" + if 'event' not in data: + return + + if data['event'] == 'subscribed': + symbol = str(data['symbol'].replace('t', '')) + #symbol = str(data['symbol']) + self.channelDict[data['chanId']] = (data['channel'], symbol) + + #---------------------------------------------------------------------- + def onUpdate(self, data): + """""" + if data[1] == u'hb': + return + + channelID = data[0] + + if not channelID: + self.onTradeUpdate(data) + else: + self.onDataUpdate(data) + + #---------------------------------------------------------------------- + def onDataUpdate(self, data): + """""" + channelID = data[0] + channel, symbol = self.channelDict[channelID] + symbol = str(symbol.replace('t', '')) + #symbol = str(symbol) + + # 获取Tick对象 + if symbol in self.tickDict: + tick = self.tickDict[symbol] + else: + tick = VtTickData() + tick.gatewayName = self.gatewayName + tick.symbol = symbol + tick.exchange = EXCHANGE_BITFINEX + tick.vtSymbol = '.'.join([tick.symbol, tick.exchange]) + + self.tickDict[symbol] = tick + + l = data[1] + + # 常规行情更新 + if channel == 'ticker': + tick.volume = float(l[-3]) + tick.highPrice = float(l[-2]) + tick.lowPrice = float(l[-1]) + tick.lastPrice = float(l[-4]) + tick.openPrice = float(tick.lastPrice - l[4]) + # 深度报价更新 + elif channel == 'book': + bid = self.bidDict.setdefault(symbol, {}) + ask = self.askDict.setdefault(symbol, {}) + + if len(l) > 3: + for price, count, amount in l: + price = float(price) + count = int(count) + amount = float(amount) + + if amount > 0: + bid[price] = amount + else: + ask[price] = -amount + else: + price, count, amount = l + price = float(price) + count = int(count) + amount = float(amount) + + if not count: + if price in bid: + del bid[price] + elif price in ask: + del ask[price] + else: + if amount > 0: + bid[price ] = amount + else: + ask[price] = -amount + + # Bitfinex的深度数据更新是逐档推送变动情况,而非5档一起推 + # 因此会出现没有Bid或者Ask的情况,这里使用try...catch过滤 + # 只有买卖深度满足5档时才做推送 + try: + # BID + bidPriceList = bid.keys() + bidPriceList.sort(reverse=True) + + tick.bidPrice1 = bidPriceList[0] + tick.bidPrice2 = bidPriceList[1] + tick.bidPrice3 = bidPriceList[2] + tick.bidPrice4 = bidPriceList[3] + tick.bidPrice5 = bidPriceList[4] + + tick.bidVolume1 = bid[tick.bidPrice1] + tick.bidVolume2 = bid[tick.bidPrice2] + tick.bidVolume3 = bid[tick.bidPrice3] + tick.bidVolume4 = bid[tick.bidPrice4] + tick.bidVolume5 = bid[tick.bidPrice5] + + # ASK + askPriceList = ask.keys() + askPriceList.sort() + + tick.askPrice1 = askPriceList[0] + tick.askPrice2 = askPriceList[1] + tick.askPrice3 = askPriceList[2] + tick.askPrice4 = askPriceList[3] + tick.askPrice5 = askPriceList[4] + + tick.askVolume1 = ask[tick.askPrice1] + tick.askVolume2 = ask[tick.askPrice2] + tick.askVolume3 = ask[tick.askPrice3] + tick.askVolume4 = ask[tick.askPrice4] + tick.askVolume5 = ask[tick.askPrice5] + except IndexError: + return + + dt = datetime.now() + tick.date = dt.strftime('%Y%m%d') + tick.time = dt.strftime('%H:%M:%S.%f') + tick.datetime = dt + + # 推送 + self.gateway.onTick(copy(tick)) + + #---------------------------------------------------------------------- + def onTradeUpdate(self, data): + """""" + name = data[1] + info = data[2] + + if name == 'ws': + for l in info: + self.onWallet(l) + self.writeLog(u'账户资金获取成功') + elif name == 'wu': + self.onWallet(info) + elif name == 'os': + for l in info: + self.onOrder(l) + self.writeLog(u'活动委托获取成功') + elif name in ['on', 'ou', 'oc']: + self.onOrder(info) + elif name == 'te': + self.onTrade(info) + + #---------------------------------------------------------------------- + def onWallet(self, data): + """""" + if str(data[0]) == 'exchange': + account = VtAccountData() + account.gatewayName = self.gatewayName + + account.accountID = str(data[1]) + account.vtAccountID = '.'.join([account.gatewayName, account.accountID]) + account.balance = float(data[2]) + account.available = float(data[-1]) + + self.gateway.onAccount(account) + + #---------------------------------------------------------------------- + def onOrder(self, data): + """""" + order = VtOrderData() + order.gatewayName = self.gatewayName + + order.symbol = str(data[3].replace('t', '')) + order.exchange = EXCHANGE_BITFINEX + order.vtSymbol = '.'.join([order.symbol, order.exchange]) + + order.orderID = str(data[2]) + order.vtOrderID = '.'.join([order.gatewayName, order.orderID]) + + if data[7] > 0: + order.direction = DIRECTION_LONG + elif data[7] < 0: + order.direction = DIRECTION_SHORT + + order.price = float(data[16]) + order.totalVolume = abs(data[7]) + order.tradedVolume = order.totalVolume - abs(data[6]) + + orderStatus = str(data[13].split('@')[0]) + orderStatus = orderStatus.replace(' ', '') + order.status = statusMapReverse[orderStatus] + + order.sessionID, order.orderTime = self.generateDateTime(data[4]) + if order.status == STATUS_CANCELLED: + buf, order.cancelTime = self.generateDateTime(data[5]) + + self.orderLocalDict[data[0]] = order.orderID + + self.gateway.onOrder(order) + + self.calc() + + #---------------------------------------------------------------------- + def onTrade(self, data): + """""" + trade = VtTradeData() + trade.gatewayName = self.gatewayName + + trade.symbol = data[1].replace('t', '') + trade.exchange = EXCHANGE_BITFINEX + trade.vtSymbol = '.'.join([trade.symbol, trade.exchange]) + trade.orderID = self.orderLocalDict[data[3]] + trade.vtOrderID = '.'.join([trade.gatewayName, trade.orderID]) + trade.tradeID = str(data[0]) + trade.vtTradeID = '.'.join([trade.gatewayName, trade.tradeID]) + + if data[4] > 0: + trade.direction = DIRECTION_LONG + else: + trade.direction = DIRECTION_SHORT + + trade.price = data[5] + trade.volume = abs(data[4]) + buf, trade.tradeTime = self.generateDateTime(data[2]) + + self.gateway.onTrade(trade) + + #---------------------------------------------------------------------- + def onSymbolDetails(self, data): + """""" + for d in data: + contract = VtContractData() + contract.gatewayName = self.gatewayName + + contract.symbol = d['pair'].upper() + contract.exchange = EXCHANGE_BITFINEX + contract.vtSymbol = '.'.join([contract.symbol, contract.exchange]) + contract.name = contract.vtSymbol + contract.productClass = PRODUCT_SPOT + contract.priceTick = pow(10, d["price_precision"]) + contract.size = 1 + + self.gateway.onContract(contract) + + self.writeLog(u'合约信息查询成功') diff --git a/vnpy/trader/gateway/bithumb/bithumbGateway.py b/vnpy/trader/gateway/bithumb/bithumbGateway.py new file mode 100644 index 00000000..091198fd --- /dev/null +++ b/vnpy/trader/gateway/bithumb/bithumbGateway.py @@ -0,0 +1,741 @@ +# encoding: UTF-8 + +''' +vnpy.api.bithumb的gateway接入 +''' +import json +from collections import defaultdict +from datetime import datetime + +from vnpy.api.bithumb import BithumbRestApi +from vnpy.trader.vtFunction import getJsonPath +from vnpy.trader.vtGateway import * + +# 方向映射 +directionMap = { + constant.DIRECTION_LONG: 'bid', + constant.DIRECTION_SHORT: 'ask' +} +directionMapReverse = {v: k for k, v in directionMap.items()} + +# 从https://www.bithumb.com/u1/US127中https://api.bithumb.com/trade/place的API说明中得到 +minimum_ticks = { + 'BTC': 0.001, + 'ETH': 0.01, + 'DASH': 0.01, + 'LTC': 0.01, + 'ETC': 0.1, + 'XRP': 10, + 'BCH': 0.001, + 'XMR': 0.01, + 'ZEC': 0.01, + 'QTUM': 0.1, + 'BTG': 0.1, + 'EOS': 0.1, + 'ICX': 1, + 'VEN': 1, + 'TRX': 100, + 'ELF': 10, + 'MITH': 10, + 'MCO': 10, + 'OMG': 0.1, + 'KNC': 1, + 'GNT': 10, + 'HSR': 1, + 'ZIL': 100, + 'ETHOS': 1, + 'PAY': 1, + 'WAX': 10, + 'POWR': 10, + 'LRC': 10, + 'GTO': 10, + 'STEEM': 10, + 'STRAT': 1, + 'ZRX': 1, + 'REP': 0.1, + 'AE': 1, + 'XEM': 10, + 'SNT': 10, + 'ADA': 10 +} + + +######################################################################## +class BithumbGateway(VtGateway): + + #---------------------------------------------------------------------- + def __init__(self, eventEngine, gatewayName='BithumbGateway'): + super(BithumbGateway, self).__init__(eventEngine, gatewayName) + + self.restApi = RestApi(self) # type: RestApi + + self.qryEnabled = False + + self.fileName = self.gatewayName + '_connect.json' + self.filePath = getJsonPath(self.fileName, __file__) + + #---------------------------------------------------------------------- + def connect(self): + """连接""" + try: + f = file(self.filePath) + except IOError: + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = u'读取连接配置出错,请检查' + self.onLog(log) + return + + # 解析json文件 + setting = json.load(f) + try: + apiKey = str(setting['apiKey']) + apiSecret = str(setting['apiSecret']) + # symbols = setting['symbols'] + except KeyError: + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = u'连接配置缺少字段,请检查' + self.onLog(log) + return + + # 创建行情和交易接口对象 + self.restApi.connect(apiKey, apiSecret) + + # 初始化并启动查询 + self.initQuery() + + #---------------------------------------------------------------------- + def subscribe(self, subscribeReq): + """订阅行情""" + pass + + #---------------------------------------------------------------------- + def sendOrder(self, orderReq): + """发单""" + return self.restApi.sendOrder(orderReq) + + #---------------------------------------------------------------------- + def cancelOrder(self, cancelOrderReq): + """撤单""" + self.restApi.cancelOrder(cancelOrderReq) + + #---------------------------------------------------------------------- + def close(self): + """关闭""" + self.restApi.close() + + #---------------------------------------------------------------------- + def initQuery(self): + """初始化连续查询""" + # if self.qryEnabled: + # 需要循环的查询函数列表 + # self.qryFunctionList = [self.restApi.qryTickers, + # self.restApi.qryDepth, + # self.restApi.qryPosition, + # self.restApi.qryOrder] + # + # self.qryCount = 0 # 查询触发倒计时 + # self.qryTrigger = 1 # 查询触发点 + # self.qryNextFunction = 0 # 上次运行的查询函数索引 + # + # self.startQuery() + pass + + #---------------------------------------------------------------------- + def query(self, event): + """注册到事件处理引擎上的查询函数""" + # self.qryCount += 1 + # + # if self.qryCount > self.qryTrigger: + # # 清空倒计时 + # self.qryCount = 0 + # + # # 执行查询函数 + # function = self.qryFunctionList[self.qryNextFunction] + # function() + # + # # 计算下次查询函数的索引,如果超过了列表长度,则重新设为0 + # self.qryNextFunction += 1 + # if self.qryNextFunction == len(self.qryFunctionList): + # self.qryNextFunction = 0 + pass + + #---------------------------------------------------------------------- + def startQuery(self): + """启动连续查询""" + self.eventEngine.register(EVENT_TIMER, self.query) + + #---------------------------------------------------------------------- + def setQryEnabled(self, qryEnabled): + """设置是否要启动循环查询""" + self.qryEnabled = qryEnabled + + +######################################################################## +# noinspection PyUnusedLocal +class RestApi(BithumbRestApi): + """REST API实现""" + + #---------------------------------------------------------------------- + def __init__(self, gateway): + """Constructor""" + super(RestApi, self).__init__() + + self.gateway = gateway # type: BithumbGateway # gateway对象 + self.gatewayName = gateway.gatewayName # gateway对象名称 + + self.localID = 0 + self.tradeID = 0 + + self.orders = {} # type: dict[str, VtOrderData] # localID:order + self.sysLocalDict = {} # type: dict[str, str] # sysID: localID + self.localSysDict = {} # type: dict[str, str] # localID: sysID + self.reqOrderDict = {} # type: dict[int, VtOrderData] # reqID:order + self.cancelDict = {} # type: dict[str, VtCancelOrderReq] # localID:req + + self.tickDict = {} + + #---------------------------------------------------------------------- + def connect(self, apiKey, apiSecret): + """连接服务器""" + self.init(apiKey, apiSecret) + self.start() + + # self.symbols = symbols + self.writeLog(u'REST API启动成功') + + self.qryContract() + + #---------------------------------------------------------------------- + def writeLog(self, content): + """发出日志""" + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = content + self.gateway.onLog(log) + + #---------------------------------------------------------------------- + def generateLocalOrder(self, ): + self.localID += 1 + localID = str(self.localID) + order = VtOrderData() + order.gatewayName = self.gatewayName + order.status = constant.STATUS_UNKNOWN + order.exchange = constant.EXCHANGE_BITHUMB + order.orderID = localID + order.vtOrderID = '.'.join([self.gatewayName, localID]) + self.orders[localID] = order + return order + + #---------------------------------------------------------------------- + def sendOrder(self, orderReq): + """下单""" + req = { + 'order_currency': orderReq.symbol, + 'Payment_currency': orderReq.currency, # todo: 无论如何服务器都会以KRW作为单位 + 'type': directionMap[orderReq.direction], + 'price': int(orderReq.price), + 'units': orderReq.volume + } + + reqid = self.addReq('POST', '/trade/place', self.onSendOrder, postdict=req) + + # 缓存委托数据对象 + order = self.generateLocalOrder() + self.fillLocalOrder(order, + orderReq.symbol, + orderReq.price, + orderReq.volume, + orderReq.direction) + + self.reqOrderDict[reqid] = order + return order.vtOrderID + + #---------------------------------------------------------------------- + def onSendOrder(self, data, reqid): # type: (dict, int)->None + """下单回执""" + if self.checkError(u'委托', data): + return + + order = self.reqOrderDict[reqid] + localID = order.orderID + sysID = data['order_id'] + + self.saveSysIDForOrder(order, sysID) + + self.gateway.onOrder(order) + + # 发出等待的撤单委托 + if localID in self.cancelDict: + req = self.cancelDict[localID] + self.cancelOrder(req) + del self.cancelDict[localID] + + #---------------------------------------------------------------------- + @staticmethod + def fillLocalOrder(order, symbol, price, totalVolume, direction): + order.symbol = symbol + order.vtSymbol = '.'.join([order.symbol, order.exchange]) + order.price = price + order.totalVolume = totalVolume + order.direction = direction + + #---------------------------------------------------------------------- + def saveSysIDForOrder(self, order, sysID): # type: (VtOrderData, str)->None + self.sysLocalDict[sysID] = order.orderID + self.localSysDict[order.orderID] = sysID + + #---------------------------------------------------------------------- + def qryOrder(self, order): # type: (VtOrderData)->None + sysID = self.getSysIDForOrder(order) + req = { + 'currency': order.symbol, + 'order_id': sysID + } + self.addReq('POST', '/info/orders', self.onQryOrders, postdict=req) + + #---------------------------------------------------------------------- + def qryOrders(self, currency='XML'): # type: (VtOrderData)->None + sysID = self.getSysIDForOrder(order) + req = { + 'currency': order.symbol, + } + self.addReq('POST', '/info/orders', self.onQryOrders, postdict=req) + + #---------------------------------------------------------------------- + def onQryOrders(self, data, reqid): + if self.checkError(u'订单查询', data): + return + orders = data['data'] + for detail in orders: + sysID = detail['order_id'] + order = self.getOrderBySysID(sysID) + if not order: + # 查询到了新的order(以前的order) + order = self.generateLocalOrder() + self.fillLocalOrder(order, + detail['order_currency'], + detail['price'], + detail['units'], + directionMapReverse[detail['type']]) + order.tradedVolume = order.totalVolume - detail['units_remaining'] + # todo: payment_currency + # payment_currency = detail['payment_currency'] + self.saveSysIDForOrder(order, sysID) + + # 推送 + self.gateway.onOrder(order) + continue + + originalTradeVolume = order.tradedVolume + order.tradedVolume = newTradeVolume = order.totalVolume - detail['units_remaining'] + + if newTradeVolume != originalTradeVolume: + # 推送更新 + self.gateway.onOrder(order) + + # 尝试更新状态 + # todo: 这一句还未测试,不知道成交之后date_completed是不是就会有值 + order.status = constant.STATUS_ALLTRADED if detail['date_completed'] else order.status + if order.status == constant.STATUS_ALLTRADED: + # 推送成交 + self.pushOrderAsTraded(order) + + #---------------------------------------------------------------------- + def pushOrderAsTraded(self, order): + trade = VtTradeData() + trade.gatewayName = order.gatewayName + trade.symbol = order.symbol + trade.vtSymbol = order.vtSymbol + trade.orderID = order.orderID + trade.vtOrderID = order.vtOrderID + self.tradeID += 1 + trade.tradeID = str(self.tradeID) + trade.vtTradeID = '.'.join([self.gatewayName, trade.tradeID]) + trade.direction = order.direction + trade.price = order.price + trade.volume = order.tradedVolume + trade.tradeTime = datetime.now().strftime('%H:%M:%S') + self.gateway.onTrade(trade) + + #---------------------------------------------------------------------- + def cancelOrder(self, cancelOrderReq): # type: (self, VtCancelOrderReq)->None + """""" + localID = cancelOrderReq.orderID + order = self.getOrderByLocalID(localID) + + if self.isOrderPosted(order): + sysID = self.getSysIDForOrder(order) + req = { + 'type': directionMap[order.direction], + 'order_id': sysID, + 'currency': cancelOrderReq.symbol + } + self.addReq('POST', + '/trade/cancel', + callback=lambda data, reqid: self.onCancelOrder(localID, data, reqid), + postdict=req) + else: + self.cancelDict[localID] = cancelOrderReq + + #---------------------------------------------------------------------- + def onCancelOrder(self, localID, data, reqid): + if self.checkError(u'撤单', data): + return + order = self.getOrderByLocalID(localID) + order.status = constant.STATUS_CANCELLED + + #---------------------------------------------------------------------- + def qryContract(self): + """""" + contract = VtContractData() + contract.gatewayName = self.gatewayName + + for symbol, tick in minimum_ticks.items(): + contract.symbol = symbol + contract.exchange = constant.EXCHANGE_BITHUMB + contract.vtSymbol = '.'.join([contract.symbol, contract.exchange]) + contract.name = contract.vtSymbol + contract.productClass = constant.PRODUCT_SPOT + contract.priceTick = tick + contract.size = 1 + self.gateway.onContract(contract) + + #---------------------------------------------------------------------- + def qryPublicTick(self, symbol='ALL'): + """ symbol 可以是'BTC', 'ETC'等等电子货币符号;也可以使用'ALL',表示要获取所有货币的行情""" + url = '/public/ticker/' + symbol + if symbol.upper() == 'ALL': + self.addReq('GET', url, self.onQryMultiPublicTicker) + else: + self.addReq('GET', url, + callback=lambda data, reqid: self.onQrySinglePublicTicker(symbol, data, id)) + + #---------------------------------------------------------------------- + def qryPublicOrderBook(self, symbol='ALL'): + """ symbol 可以是'BTC', 'ETC'等等电子货币符号;也可以使用'ALL',表示要获取所有货币的行情""" + url = '/public/orderbook/' + symbol + if symbol.upper() == 'ALL': + self.addReq('GET', url, self.onQryMultiPublicOrderBook) + else: + self.addReq('GET', url, + callback=lambda data, reqid: self.onQrySinglePublicOrderBook(symbol, data, id)) + + #---------------------------------------------------------------------- + def qryPrivateTick(self, symbol, currency='CNY'): + """""" + req = { + 'order_currency': symbol, + 'payment_currency': currency, + } + self.addReq('POST', '/info/ticker', self.onQryPrivateTicker, postdict=req) + + #---------------------------------------------------------------------- + def qryPosition(self, symbol='ALL'): + """""" + req = { + 'currency': symbol, + } + self.addReq('POST', '/info/balance', self.onQryPosition, postdict=req) + + pass + + #---------------------------------------------------------------------- + def onQryPosition(self, data, reqid): # type: (self, dict, int)->None + """""" + if self.checkError(u'查询持仓', data): + return + + datas = data['data'] # type: dict + + # 先分类一下 + infos = defaultdict(dict) # type: dict[str, dict[str, str]] + for key, val in datas.items(): # type: str, str + split_position = key.rfind('_') + infoType, symbol = key[:split_position], key[split_position+1:] + infos[symbol.upper()][infoType] = val + + for symbol in infos.keys(): + info = infos[symbol] + if symbol == u'LAST': # 过滤掉xcoin_last,这个值表示的是最后一次交易量 + continue + if symbol == u'KRW': + accountData = VtAccountData() + # todo: accountID必须从另一个API获取 + # accountData.accountID = + accountData.balance = info['total'] + accountData.available = info['available'] + self.gateway.onAccount(accountData) + pass + else: + pos = VtPositionData() + pos.gatewayName = self.gatewayName + + pos.symbol = symbol + pos.exchange = constant.EXCHANGE_BITHUMB + pos.vtSymbol = '.'.join([pos.symbol, pos.exchange]) + pos.direction = constant.DIRECTION_NET + pos.vtPositionName = '.'.join([pos.vtSymbol, pos.direction]) + pos.position = float(info['total']) + pos.frozen = float(info['in_use']) + + self.gateway.onPosition(pos) + + #---------------------------------------------------------------------- + def parsePublicTickerData(self, symbol, info): + dt = datetime.now() + date = dt.strftime('%Y%m%d') + time = dt.strftime('%H:%M:%S') + + tick = self.getTick(symbol) + + tick.openPrice = float(info['opening_price']) + tick.highPrice = float(info['max_price']) + tick.lowPrice = float(info['min_price']) + tick.lastPrice = float(info['closing_price']) # todo: 也许应该是'buy_price'? + tick.volume = float(info['volume_1day']) + tick.datetime = datetime + tick.date = date + tick.time = time + + # 只有订阅了深度行情才推送 + if tick.bidPrice1: + self.gateway.onTick(tick) + + #---------------------------------------------------------------------- + def onQrySinglePublicTicker(self, symbol, data, reqid): + if self.checkError(u'查询行情', data): + return + + info = data['data'] + self.parsePublicTickerData(symbol=symbol, info=info) + + #---------------------------------------------------------------------- + def onQryMultiPublicTicker(self, data, reqid): + if self.checkError(u'查询行情', data): + return + + for symbol, info in data['data'].items(): + # 里面可能会出现一对:date: int这样的值,所以要过滤掉 + if isinstance(info, dict): + self.parsePublicTickerData(symbol=symbol, info=info) + pass + + #---------------------------------------------------------------------- + def parsePublicOrderBookData(self, symbol, info): + dt = datetime.now() + date = dt.strftime('%Y%m%d') + time = dt.strftime('%H:%M:%S') + + tick = self.getTick(symbol) + + for i in range(5): + tick.__setattr__('askPrice' + str(i + 1), float(info['asks'][i]['price'])) + tick.__setattr__('askVolume' + str(i + 1), float(info['asks'][i]['quantity'])) + + for i in range(5): + tick.__setattr__('bidPrice' + str(i + 1), float(info['bids'][i]['price'])) + tick.__setattr__('bidVolume' + str(i + 1), float(info['bids'][i]['quantity'])) + + tick.datetime = datetime + tick.date = date + tick.time = time + + # 只有订阅了深度行情才推送 + if tick.bidPrice1: + self.gateway.onTick(tick) + + #---------------------------------------------------------------------- + def onQrySinglePublicOrderBook(self, symbol, data, reqid): + if self.checkError(u'五档行情', data): + return + + info = data['data'] + self.parsePublicTickerData(symbol=symbol, info=info) + pass + + #---------------------------------------------------------------------- + def onQryMultiPublicOrderBook(self, data, reqid): + if self.checkError(u'五档行情', data): + return + + for symbol, info in data['data'].items(): + self.parsePublicTickerData(symbol=symbol, info=info) + pass + + #---------------------------------------------------------------------- + def onQryPrivateTicker(self, data, reqid): + pass + + #---------------------------------------------------------------------- + def getTick(self, symbol): + """""" + tick = self.tickDict.get(symbol, None) # type: VtTickData + + if not tick: + tick = VtTickData() + tick.gatewayName = self.gatewayName + tick.symbol = symbol + tick.exchange = constant.EXCHANGE_BITHUMB + tick.vtSymbol = '.'.join([tick.symbol, tick.exchange]) + self.tickDict[symbol] = tick + + return tick + + #---------------------------------------------------------------------- + def checkError(self, name, data): + """""" + status = data.get('status', None) + if status == u'0000': + return False + elif not status: + self.writeLog(u'%s触发错误:%s' % (name, u"未知的响应报文 : %s".format(data))) + return True + + msg = data.get('message', u'unknown') + self.writeLog(u'%s触发错误:%s' % (name, msg)) + return True + + #---------------------------------------------------------------------- + def getOrderByLocalID(self, localID): # type: (str)->VtOrderData + """如果没有该订单,这个函数会出错""" + return self.orders[localID] + + #---------------------------------------------------------------------- + def getOrderByVtOrderID(self, vtOrderId): # type: (str)->VtOrderData + """如果没有该订单,这个函数会出错""" + localID = vtOrderId[vtOrderId.rfind('.') + 1:] + return self.getOrderByLocalID(localID) + + #---------------------------------------------------------------------- + def getOrderBySysID(self, sysID): # type: (str)->VtOrderData + return self.getOrderByLocalID(self.getLocalIDBySysID(sysID)) + + #---------------------------------------------------------------------- + def getLocalIDBySysID(self, sysID): # type: (str)->str + return self.sysLocalDict[sysID] + + #---------------------------------------------------------------------- + def isOrderPosted(self, order): # type: (VtOrderData)->bool + """检查服务器是否响应了一个下单请求,如果已经响应了返回True,否则False""" + return order.orderID in self.localSysDict + + #---------------------------------------------------------------------- + def getSysIDForOrder(self, order): # type: (VtOrderData)->str + return self.localSysDict[order.orderID] + + #---------------------------------------------------------------------- + def hasSysID(self, sysID): + return sysID in self.sysLocalDict + + + +if __name__ == '__main__': + # default test secret: + API_KEY = '0c2f5621ac18d26d51ce640b25eb44f9' + API_SECRET = '62bb8b4e263476f443f8d3dbf0aad6bc' + + api = BithumbRestApi() + api.init(apiKey=API_KEY, apiSecret=API_SECRET) + api.start(1) + + eventEngine = EventEngine2() + rest = RestApi(BithumbGateway(eventEngine=eventEngine)) + rest.connect(API_KEY, API_SECRET) + + translateDict = { + u'\uac70\ub798 \uccb4\uacb0\ub0b4\uc5ed\uc774 ' + u'\uc874\uc7ac\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.': "交易记录不存在", + + u'\uac70\ub798 \uc9c4\ud589\uc911\uc778 \ub0b4\uc5ed\uc774 ' + u'\uc874\uc7ac\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.': "没有正在进行的交易", + + u'\ub9e4\uc218\uae08\uc561\uc774 \uc0ac\uc6a9\uac00\ub2a5 KRW' + u' \ub97c \ucd08\uacfc\ud558\uc600\uc2b5\ub2c8\ub2e4.': "购买金额超过可用KRW", + + u'\ub9e4\uc218\uac74\uc758 \uc0c1\ud0dc\uac00 \uc9c4\ud589\uc911\uc774 \uc544\ub2d9\ub2c8\ub2e4. ' + u'\ucde8\uc18c\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.': "购买查询的状态未在进行中。 它无法取消。", + + u'\uc9c0\uc6d0\ud558\uc9c0 \uc54a\ub294 \ud654\ud3d0\uc785\ub2c8\ub2e4. [347]': "不支持该货币单位。[347]", + } + + def printError(jsonObj): + if rest.checkError('', data=jsonObj): + print('error : ') + msg = jsonObj['message'] + print(translateDict.get(msg, msg)) + + def manualCancelOrder(sysID): + def onTradeCancel(jsonObj, reqid): + print('onTradeCancel : \n{}'.format(jsonObj)) + printError(jsonObj) + + rest.addReq('POST', '/trade/cancel', onTradeCancel, + postdict={'type': 'bid', 'order_id': sysID, 'currency': 'XMR'}) + + def apiCancelOrder(localId): + cancelReq = VtCancelOrderReq() + cancelReq.symbol = order.symbol + cancelReq.orderID = localId + rest.cancelOrder(cancelReq) + + # query tick + rest.qryPublicTick('BTC') + rest.qryPublicTick('ALL') + rest.qryPosition('BTC') + rest.qryPosition('ALL') + + # send order + sendOrderReq = VtOrderReq() + sendOrderReq.symbol = 'XMR' + sendOrderReq.direction = constant.DIRECTION_LONG + sendOrderReq.volume = minimum_ticks['XMR'] + # sendOrderReq.price = 700 + # sendOrderReq.currency = 'CNY' # 不可用 + # sendOrderReq.price = 16.6461 + # sendOrderReq.currency = 'USD' # 不可用 + sendOrderReq.price = 17500 + sendOrderReq.currency = 'KRW' + + vtOrderId = rest.sendOrder(sendOrderReq) + order = rest.getOrderByVtOrderID(vtOrderId) + + # todo: order的状态表示不够清晰 + while not rest.isOrderPosted(order): + time.sleep(0.1) + + sysID = rest.getSysIDForOrder(order) + print("sysID : ") + print(sysID) + + def onOrders(jsonObj, reqid): + print('on_orders : \n{}'.format(jsonObj)) + printError(jsonObj) + + for detail in jsonObj['data']: + sysID = detail['order_id'] + if rest.hasSysID(sysID): + apiCancelOrder(rest.getLocalIDBySysID(sysID)) + else: + manualCancelOrder(sysID) + + after = '1531926544794' # 2018-07-18T15:09:04.794Z + # rest.addReq('POST', '/info/orders', on_orders, + # postdict={'order_id': sysID, 'type': 'bid', 'after': after, 'currency': 'XMR'}) # got + # + # rest.addReq('POST', '/info/orders', on_orders, + # postdict={'order_id': sysID, 'type': 'bid', 'currency': 'XMR'}) # got + # + # rest.addReq('POST', '/info/orders', on_orders, + # postdict={'after': after, 'currency': 'XMR'}) # got + rest.addReq('POST', '/info/orders', onOrders, postdict={'currency': 'XMR'}) # got + + # rest.addReq('POST', '/info/orders', on_orders, + # postdict={'order_id': sysID, 'type': 'bid', 'after': after}) # 没有正在进行的交易 + # + # rest.addReq('POST', '/info/orders', on_orders, postdict={'after': after}) # 没有正在进行的交易 + # rest.addReq('POST', '/info/orders', on_orders, postdict={}) # 没有正在进行的交易 + # rest.addReq('POST', '/info/orders', on_orders, postdict={'currency': 'ALL'}) # 不支持该货币单位 + + raw_input() diff --git a/vnpy/trader/gateway/bitmexGateway/BITMEX_connect.json b/vnpy/trader/gateway/bitmexGateway/BITMEX_connect.json new file mode 100644 index 00000000..4aef1612 --- /dev/null +++ b/vnpy/trader/gateway/bitmexGateway/BITMEX_connect.json @@ -0,0 +1,6 @@ +{ + "apiKey": "", + "apiSecret": "", + "sessionCount": 3, + "symbols": ["XBTUSD", "EOSM18", "XRPM18"] +} \ No newline at end of file diff --git a/vnpy/trader/gateway/bitmexGateway/__init__.py b/vnpy/trader/gateway/bitmexGateway/__init__.py new file mode 100644 index 00000000..acf7d3a6 --- /dev/null +++ b/vnpy/trader/gateway/bitmexGateway/__init__.py @@ -0,0 +1,10 @@ +# encoding: UTF-8 + +from vnpy.trader import vtConstant +from .bitmexGateway import BitmexGateway + +gatewayClass = BitmexGateway +gatewayName = 'BITMEX' +gatewayDisplayName = 'BITMEX' +gatewayType = vtConstant.GATEWAYTYPE_BTC +gatewayQryEnabled = False \ No newline at end of file diff --git a/vnpy/trader/gateway/bitmexGateway/bitmexGateway.py b/vnpy/trader/gateway/bitmexGateway/bitmexGateway.py new file mode 100644 index 00000000..599ead71 --- /dev/null +++ b/vnpy/trader/gateway/bitmexGateway/bitmexGateway.py @@ -0,0 +1,534 @@ +# encoding: UTF-8 + +''' +vnpy.api.bitmex的gateway接入 +''' +from __future__ import print_function + +import os +import json +import hashlib +import hmac +import time +import traceback +from datetime import datetime, timedelta +from copy import copy +from math import pow + +from vnpy.api.bitmex import BitmexRestApi, BitmexWebsocketApi +from vnpy.trader.vtGateway import * +from vnpy.trader.vtFunction import getJsonPath, getTempPath + +# 委托状态类型映射 +statusMapReverse = {} +statusMapReverse['New'] = STATUS_NOTTRADED +statusMapReverse['Partially filled'] = STATUS_PARTTRADED +statusMapReverse['Filled'] = STATUS_ALLTRADED +statusMapReverse['Canceled'] = STATUS_CANCELLED +statusMapReverse['Rejected'] = STATUS_REJECTED + +# 方向映射 +directionMap = {} +directionMap[DIRECTION_LONG] = 'Buy' +directionMap[DIRECTION_SHORT] = 'Sell' +directionMapReverse = {v:k for k,v in directionMap.items()} + +# 价格类型映射 +priceTypeMap = {} +priceTypeMap[PRICETYPE_LIMITPRICE] = 'Limit' +priceTypeMap[PRICETYPE_MARKETPRICE] = 'Market' + + + +######################################################################## +class BitmexGateway(VtGateway): + """Bitfinex接口""" + + #---------------------------------------------------------------------- + def __init__(self, eventEngine, gatewayName=''): + """Constructor""" + super(BitmexGateway, self).__init__(eventEngine, gatewayName) + + self.restApi = RestApi(self) + self.wsApi = WebsocketApi(self) + + self.qryEnabled = False # 是否要启动循环查询 + + self.fileName = self.gatewayName + '_connect.json' + self.filePath = getJsonPath(self.fileName, __file__) + + #---------------------------------------------------------------------- + def connect(self): + """连接""" + try: + f = file(self.filePath) + except IOError: + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = u'读取连接配置出错,请检查' + self.onLog(log) + return + + # 解析json文件 + setting = json.load(f) + try: + apiKey = str(setting['apiKey']) + apiSecret = str(setting['apiSecret']) + sessionCount = int(setting['sessionCount']) + symbols = setting['symbols'] + except KeyError: + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = u'连接配置缺少字段,请检查' + self.onLog(log) + return + + # 创建行情和交易接口对象 + self.restApi.connect(apiKey, apiSecret, sessionCount) + self.wsApi.connect(apiKey, apiSecret, symbols) + + #---------------------------------------------------------------------- + def subscribe(self, subscribeReq): + """订阅行情""" + pass + + #---------------------------------------------------------------------- + def sendOrder(self, orderReq): + """发单""" + return self.restApi.sendOrder(orderReq) + + #---------------------------------------------------------------------- + def cancelOrder(self, cancelOrderReq): + """撤单""" + self.restApi.cancelOrder(cancelOrderReq) + + #---------------------------------------------------------------------- + def close(self): + """关闭""" + self.restApi.close() + self.wsApi.close() + + #---------------------------------------------------------------------- + def initQuery(self): + """初始化连续查询""" + if self.qryEnabled: + # 需要循环的查询函数列表 + self.qryFunctionList = [self.queryAccount] + + self.qryCount = 0 # 查询触发倒计时 + self.qryTrigger = 1 # 查询触发点 + self.qryNextFunction = 0 # 上次运行的查询函数索引 + + self.startQuery() + + #---------------------------------------------------------------------- + def query(self, event): + """注册到事件处理引擎上的查询函数""" + self.qryCount += 1 + + if self.qryCount > self.qryTrigger: + # 清空倒计时 + self.qryCount = 0 + + # 执行查询函数 + function = self.qryFunctionList[self.qryNextFunction] + function() + + # 计算下次查询函数的索引,如果超过了列表长度,则重新设为0 + self.qryNextFunction += 1 + if self.qryNextFunction == len(self.qryFunctionList): + self.qryNextFunction = 0 + + #---------------------------------------------------------------------- + def startQuery(self): + """启动连续查询""" + self.eventEngine.register(EVENT_TIMER, self.query) + + #---------------------------------------------------------------------- + def setQryEnabled(self, qryEnabled): + """设置是否要启动循环查询""" + self.qryEnabled = qryEnabled + + +######################################################################## +class RestApi(BitmexRestApi): + """REST API实现""" + + #---------------------------------------------------------------------- + def __init__(self, gateway): + """Constructor""" + super(RestApi, self).__init__() + + self.gateway = gateway # gateway对象 + self.gatewayName = gateway.gatewayName # gateway对象名称 + + self.orderId = 1000000 + self.date = int(datetime.now().strftime('%y%m%d%H%M%S')) * self.orderId + + #---------------------------------------------------------------------- + def connect(self, apiKey, apiSecret, sessionCount): + """连接服务器""" + self.init(apiKey, apiSecret) + self.start(sessionCount) + + self.writeLog(u'REST API启动成功') + + #---------------------------------------------------------------------- + def writeLog(self, content): + """发出日志""" + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = content + self.gateway.onLog(log) + + #---------------------------------------------------------------------- + def sendOrder(self, orderReq): + """""" + self.orderId += 1 + orderId = self.date + self.orderId + vtOrderID = '.'.join([self.gatewayName, str(orderId)]) + + req = { + 'symbol': orderReq.symbol, + 'side': directionMap[orderReq.direction], + 'ordType': priceTypeMap[orderReq.priceType], + 'price': orderReq.price, + 'orderQty': orderReq.volume, + 'clOrdID': str(orderId) + } + self.addReq('POST', '/order', self.onSendOrder, postdict=req) + + return vtOrderID + + #---------------------------------------------------------------------- + def cancelOrder(self, cancelOrderReq): + """""" + orderID = cancelOrderReq.orderID + if orderID.isdigit(): + req = {'clOrdID': orderID} + else: + req = {'orderID': orderID} + + self.addReq('DELETE', '/order', self.onCancelOrder, params=req) + + #---------------------------------------------------------------------- + def onSendOrder(self, data, reqid): + """""" + pass + + #---------------------------------------------------------------------- + def onCancelOrder(self, data, reqid): + """""" + pass + + #---------------------------------------------------------------------- + def onError(self, code, error): + """""" + e = VtErrorData() + e.errorID = code + e.errorID = error + self.gateway.onError(e) + + +######################################################################## +class WebsocketApi(BitmexWebsocketApi): + """""" + + #---------------------------------------------------------------------- + def __init__(self, gateway): + """Constructor""" + super(WebsocketApi, self).__init__() + + self.gateway = gateway + self.gatewayName = gateway.gatewayName + + self.apiKey = '' + self.apiSecret = '' + + self.callbackDict = { + 'trade': self.onTick, + 'orderBook10': self.onDepth, + 'execution': self.onTrade, + 'order': self.onOrder, + 'position': self.onPosition, + 'margin': self.onAccount, + 'instrument': self.onContract + } + + self.tickDict = {} + self.accountDict = {} + self.orderDict = {} + self.tradeSet = set() + + #---------------------------------------------------------------------- + def connect(self, apiKey, apiSecret, symbols): + """""" + self.apiKey = apiKey + self.apiSecret = apiSecret + + for symbol in symbols: + tick = VtTickData() + tick.gatewayName = self.gatewayName + tick.symbol = symbol + tick.exchange = EXCHANGE_BITMEX + tick.vtSymbol = '.'.join([tick.symbol, tick.exchange]) + self.tickDict[symbol] = tick + + self.start() + + #---------------------------------------------------------------------- + def onConnect(self): + """连接回调""" + self.writeLog(u'Websocket API连接成功') + self.authenticate() + + #---------------------------------------------------------------------- + def onData(self, data): + """数据回调""" + if 'request' in data: + req = data['request'] + success = data['success'] + + if success: + if req['op'] == 'authKey': + self.writeLog(u'Websocket API验证授权成功') + self.subscribe() + + elif 'table' in data: + name = data['table'] + callback = self.callbackDict[name] + + if isinstance(data['data'], list): + for d in data['data']: + callback(d) + else: + callback(data['data']) + + #if data['action'] == 'update' and data['table'] != 'instrument': + #callback(data['data']) + #elif data['action'] == 'partial': + #for d in data['data']: + #callback(d) + + + #---------------------------------------------------------------------- + def onError(self, msg): + """错误回调""" + self.writeLog(msg) + + #---------------------------------------------------------------------- + def writeLog(self, content): + """发出日志""" + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = content + self.gateway.onLog(log) + + #---------------------------------------------------------------------- + def authenticate(self): + """""" + expires = int(time.time()) + method = 'GET' + path = '/realtime' + msg = method + path + str(expires) + signature = hmac.new(self.apiSecret, msg, digestmod=hashlib.sha256).hexdigest() + + req = { + 'op': 'authKey', + 'args': [self.apiKey, expires, signature] + } + self.sendReq(req) + + #---------------------------------------------------------------------- + def subscribe(self): + """""" + req = { + 'op': 'subscribe', + 'args': ['instrument', 'trade', 'orderBook10', 'execution', 'order', 'position', 'margin'] + } + self.sendReq(req) + + #---------------------------------------------------------------------- + def onTick(self, d): + """""" + symbol = d['symbol'] + + tick = self.tickDict.get(symbol, None) + if not tick: + return + + tick.lastPrice = d['price'] + + date, time = str(d['timestamp']).split('T') + tick.date = date.replace('-', '') + tick.time = time.replace('Z', '') + + self.gateway.onTick(tick) + + #---------------------------------------------------------------------- + def onDepth(self, d): + """""" + symbol = d['symbol'] + tick = self.tickDict.get(symbol, None) + if not tick: + return + + for n, buf in enumerate(d['bids'][:5]): + price, volume = buf + tick.__setattr__('bidPrice%s' %(n+1), price) + tick.__setattr__('bidVolume%s' %(n+1), volume) + + for n, buf in enumerate(d['asks'][:5]): + price, volume = buf + tick.__setattr__('askPrice%s' %(n+1), price) + tick.__setattr__('askVolume%s' %(n+1), volume) + + date, time = str(d['timestamp']).split('T') + tick.date = date.replace('-', '') + tick.time = time.replace('Z', '') + + self.gateway.onTick(tick) + + #---------------------------------------------------------------------- + def onTrade(self, d): + """""" + if not d['lastQty']: + return + + tradeID = d['execID'] + if tradeID in self.tradeSet: + return + self.tradeSet.add(tradeID) + + trade = VtTradeData() + trade.gatewayName = self.gatewayName + + trade.symbol = d['symbol'] + trade.exchange = EXCHANGE_BITMEX + trade.vtSymbol = '.'.join([trade.symbol, trade.exchange]) + if d['clOrdID']: + orderID = d['clOrdID'] + else: + orderID = d['orderID'] + trade.orderID = orderID + trade.vtOrderID = '.'.join([trade.gatewayName, trade.orderID]) + + + trade.tradeID = tradeID + trade.vtTradeID = '.'.join([trade.gatewayName, trade.tradeID]) + + trade.direction = directionMapReverse[d['side']] + trade.price = d['lastPx'] + trade.volume = d['lastQty'] + trade.tradeTime = d['timestamp'][0:10].replace('-', '') + + self.gateway.onTrade(trade) + + #---------------------------------------------------------------------- + def onOrder(self, d): + """""" + if 'ordStatus' not in d: + return + + sysID = d['orderID'] + if sysID in self.orderDict: + order = self.orderDict[sysID] + else: + order = VtOrderData() + order.gatewayName = self.gatewayName + + order.symbol = d['symbol'] + order.exchange = EXCHANGE_BITMEX + order.vtSymbol = '.'.join([order.symbol, order.exchange]) + + if d['clOrdID']: + orderID = d['clOrdID'] + else: + orderID = sysID + order.orderID = orderID + order.vtOrderID = '.'.join([self.gatewayName, order.orderID]) + + order.direction = directionMapReverse[d['side']] + + if d['price']: + order.price = d['price'] + + order.totalVolume = d['orderQty'] + order.orderTime = d['timestamp'][0:10].replace('-', '') + + self.orderDict[sysID] = order + + order.tradedVolume = d.get('cumQty', order.tradedVolume) + order.status = statusMapReverse.get(d['ordStatus'], STATUS_UNKNOWN) + + self.gateway.onOrder(order) + + #---------------------------------------------------------------------- + def onPosition(self, d): + """""" + pos = VtPositionData() + pos.gatewayName = self.gatewayName + + pos.symbol = d['symbol'] + pos.exchange = EXCHANGE_BITMEX + pos.vtSymbol = '.'.join([pos.symbol, pos.exchange]) + + pos.direction = DIRECTION_NET + pos.vtPositionName = '.'.join([pos.vtSymbol, pos.direction]) + pos.position = d['currentQty'] + pos.frozen = 0 # 期货没有冻结概念,会直接反向开仓 + + self.gateway.onPosition(pos) + + #---------------------------------------------------------------------- + def onAccount(self, d): + """""" + accoundID = str(d['account']) + + if accoundID in self.accountDict: + account = self.accountDict[accoundID] + else: + account = VtAccountData() + account.gatewayName = self.gatewayName + + account.accountID = accoundID + account.vtAccountID = '.'.join([account.gatewayName, account.accountID]) + + self.accountDict[accoundID] = account + + account.balance = d.get('marginBalance', account.balance) + account.available = d.get('availableMargin', account.available) + account.closeProfit = d.get('realisedPnl', account.closeProfit) + account.positionProfit = d.get('unrealisedPnl', account.positionProfit) + + self.gateway.onAccount(account) + + #---------------------------------------------------------------------- + def onContract(self, d): + """""" + if 'tickSize' not in d: + return + + contract = VtContractData() + contract.gatewayName = self.gatewayName + + contract.symbol = d['symbol'] + contract.exchange = EXCHANGE_BITMEX + contract.vtSymbol = '.'.join([contract.symbol, contract.exchange]) + contract.name = contract.vtSymbol + contract.productClass = PRODUCT_FUTURES + contract.priceTick = d['tickSize'] + contract.size = d['multiplier'] + + self.gateway.onContract(contract) + + +#---------------------------------------------------------------------- +def printDict(d): + """""" + print('-' * 30) + l = d.keys() + l.sort() + for k in l: + print(k, d[k]) + \ No newline at end of file diff --git a/vnpy/trader/gateway/ccxtGateway/__init__.py b/vnpy/trader/gateway/ccxtGateway/__init__.py new file mode 100644 index 00000000..f494e6e4 --- /dev/null +++ b/vnpy/trader/gateway/ccxtGateway/__init__.py @@ -0,0 +1,10 @@ +# encoding: UTF-8 + +from vnpy.trader import vtConstant +from .ccxtGateway import CcxtGateway + +gatewayClass = CcxtGateway +gatewayName = 'CCXT' +gatewayDisplayName = 'CCXT' +gatewayType = vtConstant.GATEWAYTYPE_BTC +gatewayQryEnabled = True \ No newline at end of file diff --git a/vnpy/trader/gateway/ccxtGateway/ccxtGateway.py b/vnpy/trader/gateway/ccxtGateway/ccxtGateway.py new file mode 100644 index 00000000..5461e304 --- /dev/null +++ b/vnpy/trader/gateway/ccxtGateway/ccxtGateway.py @@ -0,0 +1,547 @@ +# encoding: UTF-8 + +''' +ccxt的gateway接入 +''' + +import os +import json +import time +import traceback +from queue import Queue, Empty +from multiprocessing.dummy import Pool +from datetime import datetime, timedelta +from copy import copy +from math import pow + +import ccxt + +from vnpy.trader.vtGateway import * +from vnpy.trader.vtFunction import getJsonPath, getTempPath + + + +# 委托状态类型映射 +statusMapReverse = {} +statusMapReverse['open'] = STATUS_NOTTRADED +statusMapReverse['closed'] = STATUS_ALLTRADED +statusMapReverse['canceled'] = STATUS_CANCELLED + +# 方向映射 +directionMap = {} +directionMap[DIRECTION_LONG] = 'buy' +directionMap[DIRECTION_SHORT] = 'sell' +directionMapReverse = {v:k for k,v in directionMap.items()} + +# 类型映射 +priceTypeMap = {} +priceTypeMap[PRICETYPE_MARKETPRICE] = 'market' +priceTypeMap[PRICETYPE_LIMITPRICE] = 'limit' + + +######################################################################## +class CcxtGateway(VtGateway): + """CCXT接口""" + + #---------------------------------------------------------------------- + def __init__(self, eventEngine, gatewayName=''): + """Constructor""" + super(CcxtGateway, self).__init__(eventEngine, gatewayName) + + self.api = CcxtApi(self) + + self.qryEnabled = False # 是否要启动循环查询 + + self.fileName = self.gatewayName + '_connect.json' + self.filePath = getJsonPath(self.fileName, __file__) + + #---------------------------------------------------------------------- + def connect(self): + """连接""" + try: + f = file(self.filePath) + except IOError: + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = u'读取连接配置出错,请检查' + self.onLog(log) + return + + # 解析json文件 + setting = json.load(f) + try: + exchange = str(setting['exchange']) + apiKey = str(setting['apiKey']) + apiSecret = str(setting['apiSecret']) + symbols = setting['symbols'] + except KeyError: + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = u'连接配置缺少字段,请检查' + self.onLog(log) + return + + # 创建行情和交易接口对象 + self.api.connect(exchange, apiKey, apiSecret, symbols) + + # 初始化并启动查询 + self.initQuery() + + #---------------------------------------------------------------------- + def subscribe(self, subscribeReq): + """订阅行情""" + pass + + #---------------------------------------------------------------------- + def sendOrder(self, orderReq): + """发单""" + return self.api.sendOrder(orderReq) + + #---------------------------------------------------------------------- + def cancelOrder(self, cancelOrderReq): + """撤单""" + self.api.cancelOrder(cancelOrderReq) + + #---------------------------------------------------------------------- + def close(self): + """关闭""" + self.api.close() + + #---------------------------------------------------------------------- + def initQuery(self): + """初始化连续查询""" + if self.qryEnabled: + # 需要循环的查询函数列表 + self.qryFunctionList = [self.api.qryMarketData, + self.api.qryAccount, + self.api.qryOrder] + + self.qryCount = 0 # 查询触发倒计时 + self.qryTrigger = 1 # 查询触发点 + self.qryNextFunction = 0 # 上次运行的查询函数索引 + + self.startQuery() + + #---------------------------------------------------------------------- + def query(self, event): + """注册到事件处理引擎上的查询函数""" + self.qryCount += 1 + + if self.qryCount > self.qryTrigger: + # 清空倒计时 + self.qryCount = 0 + + # 执行查询函数 + function = self.qryFunctionList[self.qryNextFunction] + function() + + # 计算下次查询函数的索引,如果超过了列表长度,则重新设为0 + self.qryNextFunction += 1 + if self.qryNextFunction == len(self.qryFunctionList): + self.qryNextFunction = 0 + + #---------------------------------------------------------------------- + def startQuery(self): + """启动连续查询""" + self.eventEngine.register(EVENT_TIMER, self.query) + + #---------------------------------------------------------------------- + def setQryEnabled(self, qryEnabled): + """设置是否要启动循环查询""" + self.qryEnabled = qryEnabled + + +######################################################################## +class CcxtApi(object): + """REST API实现""" + + #---------------------------------------------------------------------- + def __init__(self, gateway): + """Constructor""" + super(CcxtApi, self).__init__() + + self.gateway = gateway # gateway对象 + self.gatewayName = gateway.gatewayName # gateway对象名称 + + self.api = None + self.active = False + self.queue = Queue() + self.pool = None + + self.reqID = 0 + self.localID = 0 + self.tradeID = 0 + + self.orderDict = {} # sysID:order + self.localSysDict = {} # localID:sysID + self.reqOrderDict = {} # reqID:order + self.cancelDict = {} # localID:req + + self.depthQryDict = {} # reqID:symbol + + self.tickDict = {} + + #---------------------------------------------------------------------- + def run(self, i): + """""" + while self.active: + try: + req = self.queue.get(timeout=1) + self.processReq(req) + except Empty: + pass + + #---------------------------------------------------------------------- + def addReq(self, func, args, callback): + """""" + self.reqID += 1 + req = (func, args, callback, self.reqID) + self.queue.put(req) + return self.reqID + + #---------------------------------------------------------------------- + def processReq(self, req): + """""" + try: + func, args, callback, reqID = req + data = func(*args) + callback(data, reqID) + except: + code = 'api' + msg = traceback.format_exc() + self.onError(code, msg) + + #---------------------------------------------------------------------- + def connect(self, exchange, apiKey, apiSecret, symbols): + """连接服务器""" + self.exchange = exchange.upper() + self.symbols = symbols + + # 初始化CCXT对象 + config = { + 'apiKey': apiKey, + 'secret': apiSecret + } + apiClass = ccxt.__getattribute__(exchange) + self.api = apiClass(config) + #self.api = ccxt.huobipro() + + # 启动线程池 + n = 10 + self.active = True + self.pool = Pool(n) + self.pool.map_async(self.run, range(n)) + + self.writeLog(u'CCXT API(%s)启动成功' %exchange) + + # 初始化查询 + self.qryContract() + + #---------------------------------------------------------------------- + def close(self): + """""" + self.active = False + if self.pool: + self.pool.close() + self.pool.join() + + #---------------------------------------------------------------------- + def writeLog(self, content): + """发出日志""" + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = content + self.gateway.onLog(log) + + #---------------------------------------------------------------------- + def sendOrder(self, orderReq): + """""" + self.localID += 1 + orderID = str(self.localID) + vtOrderID = '.'.join([self.gatewayName, orderID]) + + req = [ + orderReq.symbol, + priceTypeMap[orderReq.priceType], + directionMap[orderReq.direction], + orderReq.volume, + orderReq.price + ] + + func = self.api.createOrder + reqid = self.addReq(func, req, self.onSendOrder) + + # 缓存委托数据对象 + order = VtOrderData() + order.gatewayName = self.gatewayName + order.symbol = orderReq.symbol + order.exchange = self.exchange + order.vtSymbol = '.'.join([order.symbol, order.exchange]) + order.orderID = orderID + order.vtOrderID = vtOrderID + order.price = orderReq.price + order.totalVolume = orderReq.volume + order.direction = orderReq.direction + order.status = STATUS_UNKNOWN + + self.reqOrderDict[reqid] = order + + return vtOrderID + + #---------------------------------------------------------------------- + def cancelOrder(self, cancelOrderReq): + """""" + localID = cancelOrderReq.orderID + + if localID in self.localSysDict: + sysID = self.localSysDict[localID] + func = self.api.cancelOrder + reqid = self.addReq(func, [sysID], self.onCancelOrder) + else: + self.cancelDict[localID] = cancelOrderReq + + #---------------------------------------------------------------------- + def qryContract(self): + """""" + func = self.api.fetchMarkets + self.addReq(func, [], self.onQryContract) + + #---------------------------------------------------------------------- + def qryTicker(self): + """""" + for symbol in self.symbols: + func = self.api.fetchTicker + self.addReq(func, [symbol], self.onQryTicker) + + #---------------------------------------------------------------------- + def qryDepth(self): + """""" + for symbol in self.symbols: + func = self.api.fetchOrderBook + i = self.addReq(func, [symbol], self.onQryDepth) + self.depthQryDict[i] = symbol + + #---------------------------------------------------------------------- + def qryOrder(self): + """""" + for symbol in self.symbols: + func = self.api.fetchOrders + i = self.addReq(func, [symbol], self.onQryOrder) + + #---------------------------------------------------------------------- + def qryAccount(self): + """""" + func = self.api.fetchBalance + self.addReq(func, [], self.onQryAccount) + + #---------------------------------------------------------------------- + def onSendOrder(self, data, reqid): + """""" + order = self.reqOrderDict[reqid] + localID = order.orderID + sysID = data['id'] + + self.localSysDict[localID] = sysID + self.orderDict[sysID] = order + + self.gateway.onOrder(order) + + # 发出等待的撤单委托 + if localID in self.cancelDict: + req = self.cancelDict[localID] + self.cancelOrder(req) + del self.cancelDict[localID] + + #---------------------------------------------------------------------- + def onCancelOrder(self, data, reqid): + """""" + pass + + #---------------------------------------------------------------------- + def onError(self, code, error): + """""" + msg = u'发生异常,错误代码:%s,错误信息:%s' %(code, error) + self.writeLog(msg) + + #---------------------------------------------------------------------- + def onQryOrder(self, data, reqid): + """""" + for d in data: + orderUpdated = False + tradeUpdated = False + + # 获取委托对象 + sysID = d['id'] + if sysID in self.orderDict: + order = self.orderDict[sysID] + else: + order = VtOrderData() + order.gatewayName = self.gatewayName + + order.symbol = d['symbol'] + order.exchange = EXCHANGE_BIGONE + order.vtSymbol = '.'.join([order.symbol, order.exchange]) + + self.localID += 1 + localID = str(self.localID) + self.localSysDict[localID] = sysID + + order.orderID = localID + order.vtOrderID = '.'.join([order.gatewayName, order.orderID]) + + order.direction = directionMapReverse[d['side']] + order.price = float(d['price']) + order.totalVolume = float(d['amount']) + + dt = datetime.fromtimestamp(d['timestamp']/1000) + order.orderTime = dt.strftime('%H:%M:%S') + + self.orderDict[sysID] = order + orderUpdated = True + + # 检查是否委托有变化 + newTradedVolume = float(d['filled']) + newStatus = statusMapReverse[d['status']] + + if newTradedVolume != float(order.tradedVolume) or newStatus != order.status: + orderUpdated = True + + if newTradedVolume != float(order.tradedVolume): + tradeUpdated = True + newVolume = newTradedVolume - order.tradedVolume + + order.tradedVolume = newTradedVolume + order.status = newStatus + + # 若有更新才推送 + if orderUpdated: + self.gateway.onOrder(order) + + if tradeUpdated: + # 推送成交 + trade = VtTradeData() + trade.gatewayName = order.gatewayName + + trade.symbol = order.symbol + trade.vtSymbol = order.vtSymbol + + trade.orderID = order.orderID + trade.vtOrderID = order.vtOrderID + + self.tradeID += 1 + trade.tradeID = str(self.tradeID) + trade.vtTradeID = '.'.join([self.gatewayName, trade.tradeID]) + + trade.direction = order.direction + trade.price = order.price + trade.volume = newTradedVolume + trade.tradeTime = datetime.now().strftime('%H:%M:%S') + + self.gateway.onTrade(trade) + + #---------------------------------------------------------------------- + def onQryAccount(self, data, reqid): + """""" + total = data['total'] + used = data['used'] + free = data['free'] + + for currency in total.keys(): + account = VtAccountData() + account.gatewayName = self.gatewayName + + account.accountID = currency + account.vtAccountID = '.'.join([self.gatewayName, account.accountID]) + + account.balance = total[currency] + account.available = free[currency] + + self.gateway.onAccount(account) + + #---------------------------------------------------------------------- + def onQryContract(self, data, reqid): + """""" + for d in data: + contract = VtContractData() + contract.gatewayName = self.gatewayName + + contract.symbol = d['symbol'] + contract.exchange = self.exchange + contract.vtSymbol = '.'.join([contract.symbol, contract.exchange]) + contract.name = contract.vtSymbol + contract.productClass = PRODUCT_SPOT + contract.priceTick = pow(10, -int(d['precision']['price'])) + contract.size = 1 + + self.gateway.onContract(contract) + + self.writeLog(u'合约信息查询完成') + + #---------------------------------------------------------------------- + def onQryTicker(self, data, reqid): + """""" + symbol = data['symbol'] + tick = self.getTick(symbol) + + tick.openPrice = float(data['open']) + tick.highPrice = float(data['high']) + tick.lowPrice = float(data['low']) + tick.lastPrice = float(data['close']) + tick.volume = float(data['quoteVolume']) + + if data['timestamp']: + tick.datetime = datetime.fromtimestamp(data['timestamp']/1000) + else: + tick.datetime = datetime.now() + tick.date = tick.datetime.strftime('%Y%m%d') + tick.time = tick.datetime.strftime('%H:%M:%S') + + # 只有订阅了深度行情才推送 + if tick.bidPrice1: + self.gateway.onTick(tick) + + #---------------------------------------------------------------------- + def onQryDepth(self, data, reqid): + """""" + symbol = self.depthQryDict.pop(reqid) + tick = self.getTick(symbol) + + for n, bid in enumerate(data['bids'][:5]): + tick.__setattr__('bidPrice%s' %(n+1), float(bid[0])) + tick.__setattr__('bidVolume%s' %(n+1), float(bid[1])) + + for n, ask in enumerate(data['asks'][:5]): + tick.__setattr__('askPrice%s' %(n+1), float(ask[0])) + tick.__setattr__('askVolume%s' %(n+1), float(ask[1])) + + if data['timestamp']: + tick.datetime = datetime.fromtimestamp(data['timestamp']/1000) + else: + tick.datetime = datetime.now() + tick.date = tick.datetime.strftime('%Y%m%d') + tick.time = tick.datetime.strftime('%H:%M:%S') + + if tick.lastPrice: + self.gateway.onTick(tick) + + #---------------------------------------------------------------------- + def getTick(self, symbol): + """""" + tick = self.tickDict.get(symbol, None) + + if not tick: + tick = VtTickData() + tick.gatewayName = self.gatewayName + tick.symbol = symbol + tick.exchange = self.exchange + tick.vtSymbol = '.'.join([tick.symbol, tick.exchange]) + self.tickDict[symbol] = tick + + return tick + + #---------------------------------------------------------------------- + def qryMarketData(self): + """""" + self.qryDepth() + self.qryTicker() + \ No newline at end of file diff --git a/vnpy/trader/gateway/coinbaseGateway/COINBASE_connect.json b/vnpy/trader/gateway/coinbaseGateway/COINBASE_connect.json new file mode 100644 index 00000000..9926112b --- /dev/null +++ b/vnpy/trader/gateway/coinbaseGateway/COINBASE_connect.json @@ -0,0 +1,7 @@ +{ + "apiKey": "", + "secretKey": "", + "passphrase": "", + "sessionCount": 10, + "symbols": ["ETH-USD"] +} \ No newline at end of file diff --git a/vnpy/trader/gateway/coinbaseGateway/__init__.py b/vnpy/trader/gateway/coinbaseGateway/__init__.py new file mode 100644 index 00000000..757613d2 --- /dev/null +++ b/vnpy/trader/gateway/coinbaseGateway/__init__.py @@ -0,0 +1,10 @@ +# encoding: UTF-8 + +from vnpy.trader import vtConstant +from .coinbaseGateway import CoinbaseGateway + +gatewayClass = CoinbaseGateway +gatewayName = 'COINBASE' +gatewayDisplayName = 'COINBASE' +gatewayType = vtConstant.GATEWAYTYPE_BTC +gatewayQryEnabled = True \ No newline at end of file diff --git a/vnpy/trader/gateway/coinbaseGateway/coinbaseGateway.py b/vnpy/trader/gateway/coinbaseGateway/coinbaseGateway.py new file mode 100644 index 00000000..f46ecaf9 --- /dev/null +++ b/vnpy/trader/gateway/coinbaseGateway/coinbaseGateway.py @@ -0,0 +1,664 @@ +# encoding: UTF-8 + +''' +vnpy.api.bitmex的gateway接入 +''' +from __future__ import print_function + +import os +import json +import hashlib +import hmac +import time +import traceback +import base64 +import uuid +from datetime import datetime, timedelta +from copy import copy + +from vnpy.api.coinbase import CoinbaseRestApi, CoinbaseWebsocketApi +from vnpy.trader.vtGateway import * +from vnpy.trader.vtFunction import getJsonPath, getTempPath + +# 方向映射 +directionMap = {} +directionMap[DIRECTION_LONG] = 'buy' +directionMap[DIRECTION_SHORT] = 'sell' +directionMapReverse = {v:k for k,v in directionMap.items()} + +# 价格类型映射 +priceTypeMap = {} +priceTypeMap[PRICETYPE_LIMITPRICE] = 'limit' +priceTypeMap[PRICETYPE_MARKETPRICE] = 'market' + +# 数据缓存字典 +cancelDict = {} # orderID:req +orderDict = {} # sysID:order +orderSysDict = {} # orderID:sysID + + +######################################################################## +class CoinbaseGateway(VtGateway): + """Bitfinex接口""" + + #---------------------------------------------------------------------- + def __init__(self, eventEngine, gatewayName=''): + """Constructor""" + super(CoinbaseGateway, self).__init__(eventEngine, gatewayName) + + self.restApi = RestApi(self) + self.wsApi = WebsocketApi(self) + + self.qryEnabled = False # 是否要启动循环查询 + + self.fileName = self.gatewayName + '_connect.json' + self.filePath = getJsonPath(self.fileName, __file__) + + #---------------------------------------------------------------------- + def connect(self): + """连接""" + try: + f = file(self.filePath) + except IOError: + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = u'读取连接配置出错,请检查' + self.onLog(log) + return + + # 解析json文件 + setting = json.load(f) + try: + apiKey = str(setting['apiKey']) + secretKey = str(setting['secretKey']) + passphrase = str(setting['passphrase']) + sessionCount = int(setting['sessionCount']) + symbols = setting['symbols'] + except KeyError: + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = u'连接配置缺少字段,请检查' + self.onLog(log) + return + + # 创建行情和交易接口对象 + self.restApi.connect(apiKey, secretKey, passphrase, sessionCount) + self.wsApi.connect(apiKey, secretKey, passphrase, symbols) + + # 初始化并启动查询 + self.initQuery() + + #---------------------------------------------------------------------- + def subscribe(self, subscribeReq): + """订阅行情""" + pass + + #---------------------------------------------------------------------- + def sendOrder(self, orderReq): + """发单""" + return self.restApi.sendOrder(orderReq) + + #---------------------------------------------------------------------- + def cancelOrder(self, cancelOrderReq): + """撤单""" + self.restApi.cancelOrder(cancelOrderReq) + + #---------------------------------------------------------------------- + def close(self): + """关闭""" + self.restApi.close() + self.wsApi.close() + + #---------------------------------------------------------------------- + def initQuery(self): + """初始化连续查询""" + if self.qryEnabled: + # 需要循环的查询函数列表 + self.qryFunctionList = [self.restApi.qryAccount] + + self.qryCount = 0 # 查询触发倒计时 + self.qryTrigger = 1 # 查询触发点 + self.qryNextFunction = 0 # 上次运行的查询函数索引 + + self.startQuery() + + #---------------------------------------------------------------------- + def query(self, event): + """注册到事件处理引擎上的查询函数""" + self.qryCount += 1 + + if self.qryCount > self.qryTrigger: + # 清空倒计时 + self.qryCount = 0 + + # 执行查询函数 + function = self.qryFunctionList[self.qryNextFunction] + function() + + # 计算下次查询函数的索引,如果超过了列表长度,则重新设为0 + self.qryNextFunction += 1 + if self.qryNextFunction == len(self.qryFunctionList): + self.qryNextFunction = 0 + + #---------------------------------------------------------------------- + def startQuery(self): + """启动连续查询""" + self.eventEngine.register(EVENT_TIMER, self.query) + + #---------------------------------------------------------------------- + def setQryEnabled(self, qryEnabled): + """设置是否要启动循环查询""" + self.qryEnabled = qryEnabled + + +######################################################################## +class RestApi(CoinbaseRestApi): + """REST API实现""" + + #---------------------------------------------------------------------- + def __init__(self, gateway): + """Constructor""" + super(RestApi, self).__init__() + + self.gateway = gateway # gateway对象 + self.gatewayName = gateway.gatewayName # gateway对象名称 + + self.orderSysDict = {} + self.sysOrderDict = {} + self.cancelDict = {} + + #---------------------------------------------------------------------- + def connect(self, apiKey, secretKey, passphrase, sessionCount): + """连接服务器""" + self.init(apiKey, secretKey, passphrase) + self.start(sessionCount) + + self.writeLog(u'REST API启动成功') + + self.qryContract() + self.qryOrder() + + #---------------------------------------------------------------------- + def writeLog(self, content): + """发出日志""" + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = content + self.gateway.onLog(log) + + #---------------------------------------------------------------------- + def sendOrder(self, orderReq): + """""" + orderId = uuid.uuid1() + vtOrderID = '.'.join([self.gatewayName, str(orderId)]) + + req = { + 'product_id': orderReq.symbol, + 'side': directionMap[orderReq.direction], + 'price': str(orderReq.price), + 'size': str(orderReq.volume), + 'client_oid': str(orderId), + 'type': priceTypeMap[orderReq.priceType] + } + self.addReq('POST', '/orders', self.onSendOrder, postdict=req) + + return vtOrderID + + #---------------------------------------------------------------------- + def cancelOrder(self, cancelOrderReq): + """""" + orderID = cancelOrderReq.orderID + if orderID not in orderSysDict: + cancelDict[orderID] = cancelOrderReq + return + + sysID = orderSysDict[orderID] + path = '/orders/%s' %sysID + self.addReq('DELETE', path, self.onCancelOrder) + + #---------------------------------------------------------------------- + def qryContract(self): + """""" + self.addReq('GET', '/products', self.onQryContract) + + #---------------------------------------------------------------------- + def qryAccount(self): + """""" + self.addReq('GET', '/accounts', self.onQryAccount) + + #---------------------------------------------------------------------- + def qryOrder(self): + """""" + req = {'status': 'all'} + self.addReq('GET', '/orders', self.onQryOrder, params=req) + + #---------------------------------------------------------------------- + def onSendOrder(self, data, reqid): + """""" + pass + + #---------------------------------------------------------------------- + def onCancelOrder(self, data, reqid): + """""" + pass + + #---------------------------------------------------------------------- + def onError(self, code, error): + """""" + e = VtErrorData() + e.gatewayName = self.gatewayName + e.errorID = code + e.errorMsg = error + self.gateway.onError(e) + + #---------------------------------------------------------------------- + def onQryContract(self, data, reqid): + """""" + for d in data: + contract = VtContractData() + contract.gatewayName = self.gatewayName + + contract.symbol = d['id'] + contract.exchange = EXCHANGE_COINBASE + contract.vtSymbol = '.'.join([contract.symbol, contract.exchange]) + contract.name = contract.vtSymbol + + contract.size = 1 + contract.priceTick = float(d['quote_increment']) + contract.productClass = PRODUCT_SPOT + + self.gateway.onContract(contract) + + self.writeLog(u'合约信息查询成功') + + #---------------------------------------------------------------------- + def onQryAccount(self, data, reqid): + """""" + for d in data: + account = VtAccountData() + account.gatewayName = self.gatewayName + + account.accountID = d['currency'] + account.vtAccountID = '.'.join([self.gatewayName, account.accountID]) + + account.balance = float(d['balance']) + account.available = float(d['available']) + + self.gateway.onAccount(account) + + #---------------------------------------------------------------------- + def onQryOrder(self, data, reqid): + """""" + for d in data: + order = VtOrderData() + order.gatewayName = self.gatewayName + + order.orderID = d['id'] + order.vtOrderID = '.'.join([self.gatewayName, order.orderID]) + + order.symbol = d['product_id'] + order.exchange = EXCHANGE_COINBASE + order.vtSymbol = '.'.join([order.symbol, order.exchange]) + + order.direction = directionMapReverse[d['side']] + if 'price' in d: + order.price = float(d['price']) + order.totalVolume = float(d['size']) + order.tradedVolume = float(d['filled_size']) + + date, time = d['created_at'].split('T') + time = time.replace('Z', '') + order.orderTime = time + + if d['status'] == 'open': + if not order.tradedVolume: + order.status = STATUS_NOTTRADED + else: + order.status = STATUS_PARTTRADED + else: + if order.tradedVolume == order.totalVolume: + order.status = STATUS_ALLTRADED + else: + order.status = STATUS_CANCELLED + + self.gateway.onOrder(order) + + orderDict[order.orderID] = order + orderSysDict[order.orderID] = order.orderID + + self.writeLog(u'委托信息查询成功') + + +######################################################################## +class WebsocketApi(CoinbaseWebsocketApi): + """""" + + #---------------------------------------------------------------------- + def __init__(self, gateway): + """Constructor""" + super(WebsocketApi, self).__init__() + + self.gateway = gateway + self.gatewayName = gateway.gatewayName + + self.apiKey = '' + self.secretKey = '' + self.passphrase = '' + + self.callbackDict = { + 'ticker': self.onTicker, + 'snapshot': self.onSnapshot, + 'l2update': self.onL2update, + 'received': self.onOrderReceived, + 'open': self.onOrderOpen, + 'done': self.onOrderDone, + 'match': self.onMatch + } + + self.tickDict = {} + self.orderDict = {} + self.tradeSet = set() + + self.bidDict = {} + self.askDict = {} + + #---------------------------------------------------------------------- + def connect(self, apiKey, secretKey, passphrase, symbols): + """""" + self.apiKey = apiKey + self.secretKey = secretKey + self.passphrase = passphrase + self.symbols = symbols + + for symbol in symbols: + tick = VtTickData() + tick.gatewayName = self.gatewayName + tick.symbol = symbol + tick.exchange = EXCHANGE_COINBASE + tick.vtSymbol = '.'.join([tick.symbol, tick.exchange]) + self.tickDict[symbol] = tick + + self.start() + + #---------------------------------------------------------------------- + def onConnect(self): + """连接回调""" + self.writeLog(u'Websocket API连接成功') + + self.subscribe() + + #---------------------------------------------------------------------- + def onData(self, data): + """数据回调""" + if 'type' in data: + cb = self.callbackDict.get(data['type'], None) + if cb: + cb(data) + else: + self.writeLog(str(data)) + + #---------------------------------------------------------------------- + def onError(self, msg): + """错误回调""" + self.writeLog(msg) + + #---------------------------------------------------------------------- + def writeLog(self, content): + """发出日志""" + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = content + self.gateway.onLog(log) + + #---------------------------------------------------------------------- + def authenticate(self): + """""" + timestamp = str(time.time()) + method = 'GET' + path = '/users/self/verify' + msg = timestamp + method + path + msg = msg.encode('ascii') + hmacKey = base64.b64decode(self.secretKey) + signature = hmac.new(hmacKey, msg, hashlib.sha256) + signature64 = base64.b64encode(signature.digest()).decode('utf-8').rstrip('\n') + + d = { + 'key': self.apiKey, + 'passphrase': self.passphrase, + 'timestamp': timestamp, + 'signature': signature64 + } + return d + + #---------------------------------------------------------------------- + def subscribe(self): + """""" + req = { + 'type': 'subscribe', + 'product_ids': self.symbols, + 'channels': ['ticker', 'level2', 'user'] + } + + d = self.authenticate() + req.update(d) + self.sendReq(req) + + #---------------------------------------------------------------------- + def onTicker(self, d): + """""" + symbol = d['product_id'] + + tick = self.tickDict.get(symbol, None) + if not tick: + return + + tick.openPrice = float(d['open_24h']) + tick.highPrice = float(d['high_24h']) + tick.lowPrice = float(d['low_24h']) + tick.lastPrice = float(d['price']) + tick.volume = float(d['volume_24h']) + + tick.datetime = datetime.now() + tick.date = tick.datetime.strftime('%Y%m%d') + tick.time = tick.datetime.strftime('%H:%M:%S') + + self.gateway.onTick(copy(tick)) + + #---------------------------------------------------------------------- + def onSnapshot(self, d): + """""" + symbol = d['product_id'] + tick = self.tickDict.get(symbol, None) + if not tick: + return + + bid = self.bidDict.setdefault(symbol, {}) + ask = self.askDict.setdefault(symbol, {}) + + for price, amount in d['bids']: + bid[float(price)] = float(amount) + + for price, amount in d['asks']: + ask[float(price)] = float(amount) + + self.generateTick(symbol) + + #---------------------------------------------------------------------- + def generateTick(self, symbol): + """""" + tick = self.tickDict[symbol] + bid = self.bidDict[symbol] + ask = self.askDict[symbol] + + + bidPriceList = bid.keys() + tick.bidPrice1 = bidPriceList[0] + tick.bidPrice2 = bidPriceList[1] + tick.bidPrice3 = bidPriceList[2] + tick.bidPrice4 = bidPriceList[3] + tick.bidPrice5 = bidPriceList[4] + + tick.bidVolume1 = bid[tick.bidPrice1] + tick.bidVolume2 = bid[tick.bidPrice2] + tick.bidVolume3 = bid[tick.bidPrice3] + tick.bidVolume4 = bid[tick.bidPrice4] + tick.bidVolume5 = bid[tick.bidPrice5] + + askPriceList = ask.keys() + askPriceList.sort() + + tick.askPrice1 = askPriceList[0] + tick.askPrice2 = askPriceList[1] + tick.askPrice3 = askPriceList[2] + tick.askPrice4 = askPriceList[3] + tick.askPrice5 = askPriceList[4] + + tick.askVolume1 = ask[tick.askPrice1] + tick.askVolume2 = ask[tick.askPrice2] + tick.askVolume3 = ask[tick.askPrice3] + tick.askVolume4 = ask[tick.askPrice4] + tick.askVolume5 = ask[tick.askPrice5] + + tick.datetime = datetime.now() + tick.date = tick.datetime.strftime('%Y%m%d') + tick.time = tick.datetime.strftime('%H:%M:%S') + + self.gateway.onTick(copy(tick)) + + #---------------------------------------------------------------------- + def onL2update(self, d): + """""" + symbol = d['product_id'] + tick = self.tickDict.get(symbol, None) + if not tick: + return + + bid = self.bidDict.setdefault(symbol, {}) + ask = self.askDict.setdefault(symbol, {}) + + for direction, price, amount in d['changes']: + price = float(price) + amount = float(amount) + + if direction == 'buy': + if amount: + bid[price] = amount + elif price in bid: + del bid[price] + else: + if amount: + ask[price] = amount + elif price in ask: + del ask[price] + + self.generateTick(symbol) + + #---------------------------------------------------------------------- + def onMatch(self, d): + """""" + trade = VtTradeData() + trade.gatewayName = self.gatewayName + + trade.symbol = d['product_id'] + trade.exchange = EXCHANGE_COINBASE + trade.vtSymbol = '.'.join([trade.symbol, trade.exchange]) + + if d['maker_order_id'] in orderDict: + order = orderDict[d['maker_order_id']] + else: + order = orderDict[d['taker_order_id']] + + trade.orderID = order.orderID + trade.vtOrderID = order.vtOrderID + + trade.tradeID = str(d['trade_id']) + trade.vtTradeID = '.'.join([trade.gatewayName, trade.tradeID]) + + trade.direction = order.direction + trade.price = float(d['price']) + trade.volume = float(d['size']) + + date, time = d['time'].split('T') + time = time.replace('Z', '') + trade.tradeTime = time + + self.gateway.onTrade(trade) + + #---------------------------------------------------------------------- + def onOrderReceived(self, d): + """""" + sysID = d['order_id'] + orderID = d['client_oid'] + + order = VtOrderData() + order.gatewayName = self.gatewayName + + order.orderID = orderID + order.vtOrderID = '.'.join([self.gatewayName, order.orderID]) + + order.symbol = d['product_id'] + order.exchange = EXCHANGE_COINBASE + order.vtSymbol = '.'.join([order.symbol, order.exchange]) + + order.direction = directionMapReverse[d['side']] + if 'price' in d: + order.price = float(d['price']) + order.totalVolume = float(d['size']) + + date, time = d['time'].split('T') + time = time.replace('Z', '') + order.orderTime = time + + order.status = STATUS_NOTTRADED + + self.gateway.onOrder(order) + + # 缓存委托 + orderDict[sysID] = order + orderSysDict[orderID] = sysID + + # 执行待撤单 + if orderID in cancelDict: + req = cancelDict.pop(orderID) + self.gateway.cancelOrder(req) + + #---------------------------------------------------------------------- + def onOrderOpen(self, d): + """""" + order = orderDict.get(d['order_id'], None) + if not order: + return + + order.tradedVolume = order.totalVolume - float(d['remaining_size']) + if order.tradedVolume: + order.status = STATUS_PARTTRADED + self.gateway.onOrder(order) + + #---------------------------------------------------------------------- + def onOrderDone(self, d): + """""" + #print('done') + #print(d) + order = orderDict.get(d['order_id'], None) + if not order: + return + + order.tradedVolume = order.totalVolume - float(d['remaining_size']) + + if order.tradedVolume == order.totalVolume: + order.status = STATUS_ALLTRADED + else: + order.status = STATUS_CANCELLED + + self.gateway.onOrder(order) + + +#---------------------------------------------------------------------- +def printDict(d): + """""" + print('-' * 30) + l = d.keys() + l.sort() + for k in l: + print(k, d[k]) + \ No newline at end of file diff --git a/vnpy/trader/gateway/cshshlpGateway/__init__.py b/vnpy/trader/gateway/cshshlpGateway/__init__.py index ff1cd68e..e340ebe5 100644 --- a/vnpy/trader/gateway/cshshlpGateway/__init__.py +++ b/vnpy/trader/gateway/cshshlpGateway/__init__.py @@ -1,7 +1,8 @@ # encoding: UTF-8 +from __future__ import absolute_import from vnpy.trader import vtConstant -from cshshlpGateway import CshshlpGateway +from .cshshlpGateway import CshshlpGateway gatewayClass = CshshlpGateway gatewayName = 'CSHSHLP' diff --git a/vnpy/trader/gateway/ctpGateway/__init__.py b/vnpy/trader/gateway/ctpGateway/__init__.py index 3078d854..8a8d5827 100644 --- a/vnpy/trader/gateway/ctpGateway/__init__.py +++ b/vnpy/trader/gateway/ctpGateway/__init__.py @@ -1,7 +1,8 @@ # encoding: UTF-8 +from __future__ import absolute_import from vnpy.trader import vtConstant -from ctpGateway import CtpGateway +from .ctpGateway import CtpGateway gatewayClass = CtpGateway gatewayName = 'CTP' diff --git a/vnpy/trader/gateway/ctpGateway/ctpGateway.py b/vnpy/trader/gateway/ctpGateway/ctpGateway.py index 2d49c8c3..0aa11e72 100644 --- a/vnpy/trader/gateway/ctpGateway/ctpGateway.py +++ b/vnpy/trader/gateway/ctpGateway/ctpGateway.py @@ -247,10 +247,6 @@ class CtpMdApi(MdApi): self.brokerID = EMPTY_STRING # 经纪商代码 self.address = EMPTY_STRING # 服务器地址 - self.tradingDt = None # 交易日datetime对象 - self.tradingDate = EMPTY_STRING # 交易日期字符串 - self.tickTime = None # 最新行情time对象 - #---------------------------------------------------------------------- def onFrontConnected(self): """服务器连接""" @@ -298,14 +294,6 @@ class CtpMdApi(MdApi): for subscribeReq in self.subscribedSymbols: self.subscribe(subscribeReq) - # 获取交易日 - #self.tradingDate = data['TradingDay'] - #self.tradingDt = datetime.strptime(self.tradingDate, '%Y%m%d') - - # 登录时通过本地时间来获取当前的日期 - self.tradingDt = datetime.now() - self.tradingDate = self.tradingDt.strftime('%Y%m%d') - # 否则,推送错误信息 else: err = VtErrorData() @@ -388,18 +376,7 @@ class CtpMdApi(MdApi): # 大商所日期转换 if tick.exchange is EXCHANGE_DCE: - newTime = datetime.strptime(tick.time, '%H:%M:%S.%f').time() # 最新tick时间戳 - - # 如果新tick的时间小于夜盘分隔,且上一个tick的时间大于夜盘分隔,则意味着越过了12点 - if (self.tickTime and - newTime < NIGHT_TRADING and - self.tickTime > NIGHT_TRADING): - self.tradingDt += timedelta(1) # 日期加1 - self.tradingDate = self.tradingDt.strftime('%Y%m%d') # 生成新的日期字符串 - - tick.date = self.tradingDate # 使用本地维护的日期 - - self.tickTime = newTime # 更新上一个tick时间 + tick.date = datetime.now().strftime('%Y%m%d') self.gateway.onTick(tick) @@ -745,9 +722,15 @@ class CtpTdApi(TdApi): pos.direction = posiDirectionMapReverse.get(data['PosiDirection'], '') pos.vtPositionName = '.'.join([pos.vtSymbol, pos.direction]) + exchange = self.symbolExchangeDict.get(pos.symbol, EXCHANGE_UNKNOWN) + # 针对上期所持仓的今昨分条返回(有昨仓、无今仓),读取昨仓数据 - if data['YdPosition'] and not data['TodayPosition']: - pos.ydPosition = data['Position'] + if exchange == EXCHANGE_SHFE: + if data['YdPosition'] and not data['TodayPosition']: + pos.ydPosition = data['Position'] + # 否则基于总持仓和今持仓来计算昨仓数据 + else: + pos.ydPosition = data['Position'] - data['TodayPosition'] # 计算成本 size = self.symbolSizeDict[pos.symbol] diff --git a/vnpy/trader/gateway/ctpGateway/language/__init__.py b/vnpy/trader/gateway/ctpGateway/language/__init__.py index 2ca0ef80..8f68ed7f 100644 --- a/vnpy/trader/gateway/ctpGateway/language/__init__.py +++ b/vnpy/trader/gateway/ctpGateway/language/__init__.py @@ -1,15 +1,16 @@ # encoding: UTF-8 +from __future__ import absolute_import import json import os import traceback # 默认设置 -from chinese import text +from .chinese import text # 获取全局配置 from vnpy.trader.vtGlobal import globalSetting # 打开配置文件,读取语言配置 if globalSetting['language'] == 'english': - from english import text + from .english import text diff --git a/vnpy/trader/gateway/fcoinGateway/__init__.py b/vnpy/trader/gateway/fcoinGateway/__init__.py new file mode 100644 index 00000000..bd76f422 --- /dev/null +++ b/vnpy/trader/gateway/fcoinGateway/__init__.py @@ -0,0 +1,10 @@ +# encoding: UTF-8 + +from vnpy.trader import vtConstant +from .fcoinGateway import FcoinGateway + +gatewayClass = FcoinGateway +gatewayName = 'FCOIN' +gatewayDisplayName = 'FCOIN' +gatewayType = vtConstant.GATEWAYTYPE_BTC +gatewayQryEnabled = True \ No newline at end of file diff --git a/vnpy/trader/gateway/fcoinGateway/fcoinGateway.py b/vnpy/trader/gateway/fcoinGateway/fcoinGateway.py new file mode 100644 index 00000000..8c9a054d --- /dev/null +++ b/vnpy/trader/gateway/fcoinGateway/fcoinGateway.py @@ -0,0 +1,582 @@ +# encoding: UTF-8 + +''' +vnpy.api.fcoin的gateway接入 +''' +from __future__ import print_function + +import os +import json +import time +import traceback +from datetime import datetime, timedelta +from copy import copy +from math import pow + +from vnpy.api.fcoin import FcoinRestApi, FcoinWebsocketApi +from vnpy.trader.vtGateway import * +from vnpy.trader.vtFunction import getJsonPath, getTempPath + +# 委托状态类型映射 +statusMapReverse = {} +statusMapReverse['submitted'] = STATUS_NOTTRADED +statusMapReverse['partial_filled'] = STATUS_PARTTRADED +statusMapReverse['partial_canceled'] = STATUS_CANCELLED +statusMapReverse['filled'] = STATUS_ALLTRADED +statusMapReverse['canceled'] = STATUS_CANCELLED +statusMapReverse['pending_cancel'] = STATUS_UNKNOWN + +# 方向映射 +directionMap = {} +directionMap[DIRECTION_LONG] = 'buy' +directionMap[DIRECTION_SHORT] = 'sell' +directionMapReverse = {v:k for k,v in directionMap.items()} + +# 价格类型映射 +priceTypeMap = {} +priceTypeMap[PRICETYPE_LIMITPRICE] = 'limit' +priceTypeMap[PRICETYPE_MARKETPRICE] = 'market' + + + +######################################################################## +class FcoinGateway(VtGateway): + """FCOIN接口""" + + #---------------------------------------------------------------------- + def __init__(self, eventEngine, gatewayName=''): + """Constructor""" + super(FcoinGateway, self).__init__(eventEngine, gatewayName) + + self.restApi = RestApi(self) + self.wsApi = WebsocketApi(self) + + self.qryEnabled = False # 是否要启动循环查询 + + self.fileName = self.gatewayName + '_connect.json' + self.filePath = getJsonPath(self.fileName, __file__) + + #---------------------------------------------------------------------- + def connect(self): + """连接""" + try: + f = file(self.filePath) + except IOError: + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = u'读取连接配置出错,请检查' + self.onLog(log) + return + + # 解析json文件 + setting = json.load(f) + try: + apiKey = str(setting['apiKey']) + apiSecret = str(setting['apiSecret']) + symbols = setting['symbols'] + except KeyError: + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = u'连接配置缺少字段,请检查' + self.onLog(log) + return + + # 创建行情和交易接口对象 + self.restApi.connect(apiKey, apiSecret, symbols) + self.wsApi.connect(apiKey, apiSecret, symbols) + + # 初始化并启动查询 + self.initQuery() + + #---------------------------------------------------------------------- + def subscribe(self, subscribeReq): + """订阅行情""" + pass + + #---------------------------------------------------------------------- + def sendOrder(self, orderReq): + """发单""" + return self.restApi.sendOrder(orderReq) + + #---------------------------------------------------------------------- + def cancelOrder(self, cancelOrderReq): + """撤单""" + self.restApi.cancelOrder(cancelOrderReq) + + #---------------------------------------------------------------------- + def close(self): + """关闭""" + self.restApi.close() + self.wsApi.close() + + #---------------------------------------------------------------------- + def initQuery(self): + """初始化连续查询""" + if self.qryEnabled: + # 需要循环的查询函数列表 + self.qryFunctionList = [self.restApi.qryPosition, + self.restApi.qryOrderSubmitted, + self.restApi.qryOrderPartialFilled, + self.restApi.qryOrderCanceled, + self.restApi.qryOrderFilled, + self.restApi.qryOrderPartialCanceled] + + self.qryCount = 0 # 查询触发倒计时 + self.qryTrigger = 3 # 查询触发点 + self.qryNextFunction = 0 # 上次运行的查询函数索引 + + self.startQuery() + + #---------------------------------------------------------------------- + def query(self, event): + """注册到事件处理引擎上的查询函数""" + self.qryCount += 1 + + if self.qryCount > self.qryTrigger: + # 清空倒计时 + self.qryCount = 0 + + # 执行查询函数 + function = self.qryFunctionList[self.qryNextFunction] + function() + + # 计算下次查询函数的索引,如果超过了列表长度,则重新设为0 + self.qryNextFunction += 1 + if self.qryNextFunction == len(self.qryFunctionList): + self.qryNextFunction = 0 + + #---------------------------------------------------------------------- + def startQuery(self): + """启动连续查询""" + self.eventEngine.register(EVENT_TIMER, self.query) + + #---------------------------------------------------------------------- + def setQryEnabled(self, qryEnabled): + """设置是否要启动循环查询""" + self.qryEnabled = qryEnabled + + +######################################################################## +class RestApi(FcoinRestApi): + """REST API实现""" + + #---------------------------------------------------------------------- + def __init__(self, gateway): + """Constructor""" + super(RestApi, self).__init__() + + self.gateway = gateway # gateway对象 + self.gatewayName = gateway.gatewayName # gateway对象名称 + + self.localID = 0 + self.tradeID = 0 + + self.orderDict = {} # sysID:order + self.localSysDict = {} # localID:sysID + self.reqOrderDict = {} # reqID:order + self.cancelDict = {} # localID:req + + #---------------------------------------------------------------------- + def connect(self, apiKey, apiSecret, symbols): + """连接服务器""" + self.init(apiKey, apiSecret) + self.start() + + self.symbols = symbols + self.writeLog(u'REST API启动成功') + + self.qryContract() + + #---------------------------------------------------------------------- + def writeLog(self, content): + """发出日志""" + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = content + self.gateway.onLog(log) + + #---------------------------------------------------------------------- + def sendOrder(self, orderReq): + """""" + #orderReq.price = 300.0 + #orderReq.volume = 0.01 + + self.localID += 1 + orderID = str(self.localID) + vtOrderID = '.'.join([self.gatewayName, orderID]) + + req = { + 'symbol': orderReq.symbol, + 'side': directionMap[orderReq.direction], + 'type': priceTypeMap[orderReq.priceType], + 'price': orderReq.price, + 'amount': orderReq.volume + } + + reqid = self.addReq('POST', '/orders', self.onSendOrder, postdict=req) + + # 缓存委托数据对象 + order = VtOrderData() + order.gatewayName = self.gatewayName + order.symbol = orderReq.symbol + order.exchange = EXCHANGE_FCOIN + order.vtSymbol = '.'.join([order.symbol, order.exchange]) + order.orderID = orderID + order.vtOrderID = vtOrderID + order.price = orderReq.price + order.totalVolume = orderReq.volume + order.direction = orderReq.direction + order.status = STATUS_UNKNOWN + + self.reqOrderDict[reqid] = order + + return vtOrderID + + #---------------------------------------------------------------------- + def cancelOrder(self, cancelOrderReq): + """""" + localID = cancelOrderReq.orderID + + if localID in self.localSysDict: + sysID = self.localSysDict[localID] + path = '/orders/%s/submit-cancel' %sysID + self.addReq('POST', path, self.onCancelOrder) + else: + self.cancelDict[localID] = cancelOrderReq + + #---------------------------------------------------------------------- + def qryContract(self): + """""" + self.addReq('GET', '/public/symbols', self.onQryContract) + + #---------------------------------------------------------------------- + def qryOrder(self, state): + """""" + for symbol in self.symbols: + req = { + 'symbol': symbol, + 'states': state, + 'limit': 50 + } + self.addReq('GET', '/orders', self.onQryOrder, params=req) + + #---------------------------------------------------------------------- + def qryOrderSubmitted(self): + """""" + self.qryOrder('submitted') + + #---------------------------------------------------------------------- + def qryOrderPartialFilled(self): + """""" + self.qryOrder('partial_filled') + + #---------------------------------------------------------------------- + def qryOrderPartialCanceled(self): + """""" + self.qryOrder('partial_canceled') + + #---------------------------------------------------------------------- + def qryOrderFilled(self): + """""" + self.qryOrder('filled') + + #---------------------------------------------------------------------- + def qryOrderCanceled(self): + """""" + self.qryOrder('canceled') + + #---------------------------------------------------------------------- + def qryPosition(self): + """""" + self.addReq('GET', '/accounts/balance', self.onQryPosition) + + #---------------------------------------------------------------------- + def onSendOrder(self, data, reqid): + """""" + if 'msg' in data: + self.writeLog(data['msg']) + return + + if 'data' in data: + order = self.reqOrderDict[reqid] + localID = order.orderID + sysID = data['data'] + + self.localSysDict[localID] = sysID + self.orderDict[sysID] = order + + self.gateway.onOrder(order) + + # 发出等待的撤单委托 + if localID in self.cancelDict: + req = self.cancelDict[localID] + self.cancelOrder(req) + del self.cancelDict[localID] + + #---------------------------------------------------------------------- + def onCancelOrder(self, data, reqid): + """""" + pass + + #---------------------------------------------------------------------- + def onError(self, code, error): + """""" + msg = u'发生异常,错误代码:%s,错误信息:%s' %(code, error) + self.writeLog(msg) + + #---------------------------------------------------------------------- + def onQryOrder(self, data, reqid): + """""" + data['data'].reverse() + + for d in data['data']: + orderUpdated = False + tradeUpdated = False + + # 获取委托对象 + sysID = d['id'] + if sysID in self.orderDict: + order = self.orderDict[sysID] + else: + order = VtOrderData() + order.gatewayName = self.gatewayName + + order.symbol = d['symbol'] + order.exchange = EXCHANGE_FCOIN + order.vtSymbol = '.'.join([order.symbol, order.exchange]) + + self.localID += 1 + localID = str(self.localID) + self.localSysDict[localID] = sysID + + order.orderID = localID + order.vtOrderID = '.'.join([order.gatewayName, order.orderID]) + + order.direction = directionMapReverse[d['side']] + order.price = float(d['price']) + order.totalVolume = float(d['amount']) + + dt = datetime.fromtimestamp(d['created_at']/1000) + order.orderTime = dt.strftime('%H:%M:%S') + + self.orderDict[sysID] = order + orderUpdated = True + + # 检查是否委托有变化 + newTradedVolume = float(d['filled_amount']) + newStatus = statusMapReverse[d['state']] + + if newTradedVolume != float(order.tradedVolume) or newStatus != order.status: + orderUpdated = True + + if newTradedVolume != float(order.tradedVolume): + tradeUpdated = True + newVolume = newTradedVolume - order.tradedVolume + + order.tradedVolume = newTradedVolume + order.status = newStatus + + # 若有更新才推送 + if orderUpdated: + self.gateway.onOrder(order) + + if tradeUpdated: + # 推送成交 + trade = VtTradeData() + trade.gatewayName = order.gatewayName + + trade.symbol = order.symbol + trade.vtSymbol = order.vtSymbol + + trade.orderID = order.orderID + trade.vtOrderID = order.vtOrderID + + self.tradeID += 1 + trade.tradeID = str(self.tradeID) + trade.vtTradeID = '.'.join([self.gatewayName, trade.tradeID]) + + trade.direction = order.direction + trade.price = order.price + trade.volume = newTradedVolume + trade.tradeTime = datetime.now().strftime('%H:%M:%S') + + self.gateway.onTrade(trade) + + #---------------------------------------------------------------------- + def onQryPosition(self, data, reqid): + """""" + for d in data['data']: + account = VtAccountData() + account.gatewayName = self.gatewayName + + account.accountID = d['currency'] + account.vtAccountID = '.'.join([account.gatewayName, account.accountID]) + account.balance = float(d['balance']) + account.available = account.balance - float(d['frozen']) + + self.gateway.onAccount(account) + + #---------------------------------------------------------------------- + def onQryContract(self, data, reqid): + """""" + for d in data['data']: + contract = VtContractData() + contract.gatewayName = self.gatewayName + + contract.symbol = d['name'] + contract.exchange = EXCHANGE_FCOIN + contract.vtSymbol = '.'.join([contract.symbol, contract.exchange]) + contract.name = contract.vtSymbol + contract.productClass = PRODUCT_SPOT + contract.priceTick = pow(10, -int(d['price_decimal'])) + contract.size = 1 + + self.gateway.onContract(contract) + + self.writeLog(u'合约信息查询完成') + + +######################################################################## +class WebsocketApi(FcoinWebsocketApi): + """""" + + #---------------------------------------------------------------------- + def __init__(self, gateway): + """Constructor""" + super(WebsocketApi, self).__init__() + + self.gateway = gateway + self.gatewayName = gateway.gatewayName + + self.apiKey = '' + self.apiSecret = '' + self.symbols = [] + + self.tickDict = {} + + #---------------------------------------------------------------------- + def connect(self, apiKey, apiSecret, symbols): + """""" + self.apiKey = apiKey + self.apiSecret = apiSecret + self.symbols = symbols + + self.start() + + #---------------------------------------------------------------------- + def onConnect(self): + """连接回调""" + self.writeLog(u'Websocket API连接成功') + + #---------------------------------------------------------------------- + def subscribe(self): + """""" + l = [] + for symbol in self.symbols: + l.append('ticker.' + symbol) + l.append('depth.L20.' + symbol) + + tick = VtTickData() + tick.gatewayName = self.gatewayName + tick.symbol = symbol + tick.exchange = EXCHANGE_FCOIN + tick.vtSymbol = '.'.join([tick.symbol, tick.exchange]) + self.tickDict[symbol] = tick + + req = { + 'cmd': 'sub', + 'args': l, + 'id': 1 + } + self.sendReq(req) + + #---------------------------------------------------------------------- + def onData(self, data): + """数据回调""" + type_ = data['type'] + if 'hello' in type_: + self.subscribe() + elif 'ticker' in type_: + self.onTick(data) + elif 'depth' in type_: + self.onDepth(data) + + #---------------------------------------------------------------------- + def onError(self, msg): + """错误回调""" + self.writeLog(msg) + + #---------------------------------------------------------------------- + def writeLog(self, content): + """发出日志""" + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = content + self.gateway.onLog(log) + + #---------------------------------------------------------------------- + def onTick(self, d): + """""" + symbol = d['type'].split('.')[-1] + tick = self.tickDict[symbol] + + ticker = d['ticker'] + tick.openPrice = ticker[6] + tick.highPrice = ticker[7] + tick.lowPrice = ticker[8] + tick.lastPrice = ticker[0] + tick.volume = ticker[9] + + if tick.askPrice1: + self.gateway.onTick(copy(tick)) + + #---------------------------------------------------------------------- + def onDepth(self, d): + """""" + symbol = d['type'].split('.')[-1] + tick = self.tickDict[symbol] + + bids = d['bids'] + asks = d['asks'] + + tick.bidPrice1 = bids[0] + tick.bidPrice2 = bids[2] + tick.bidPrice3 = bids[4] + tick.bidPrice4 = bids[6] + tick.bidPrice5 = bids[8] + + tick.askPrice1 = asks[0] + tick.askPrice2 = asks[2] + tick.askPrice3 = asks[4] + tick.askPrice4 = asks[6] + tick.askPrice5 = asks[8] + + tick.bidVolume1 = bids[1] + tick.bidVolume2 = bids[3] + tick.bidVolume3 = bids[5] + tick.bidVolume4 = bids[7] + tick.bidVolume5 = bids[9] + + tick.askVolume1 = asks[1] + tick.askVolume2 = asks[3] + tick.askVolume3 = asks[5] + tick.askVolume4 = asks[7] + tick.askVolume5 = asks[9] + + tick.datetime = datetime.fromtimestamp(d['ts']/1000) + tick.date = tick.datetime.strftime('%Y%m%d') + tick.time = tick.datetime.strftime('%H:%M:%S') + + if tick.lastPrice: + self.gateway.onTick(copy(tick)) + + +#---------------------------------------------------------------------- +def printDict(d): + """""" + print('-' * 30) + l = d.keys() + l.sort() + for k in l: + print(k, d[k]) + \ No newline at end of file diff --git a/vnpy/trader/gateway/femasGateway/__init__.py b/vnpy/trader/gateway/femasGateway/__init__.py index 646b363e..e6897beb 100644 --- a/vnpy/trader/gateway/femasGateway/__init__.py +++ b/vnpy/trader/gateway/femasGateway/__init__.py @@ -1,7 +1,8 @@ # encoding: UTF-8 +from __future__ import absolute_import from vnpy.trader import vtConstant -from femasGateway import FemasGateway +from .femasGateway import FemasGateway gatewayClass = FemasGateway gatewayName = 'FEMAS' diff --git a/vnpy/trader/gateway/femasGateway/femasGateway.py b/vnpy/trader/gateway/femasGateway/femasGateway.py index c5962a49..88ba3d42 100644 --- a/vnpy/trader/gateway/femasGateway/femasGateway.py +++ b/vnpy/trader/gateway/femasGateway/femasGateway.py @@ -5,6 +5,7 @@ vn.femas的gateway接入 考虑到飞马只对接期货(目前只有中金所), vtSymbol直接使用symbol ''' +from __future__ import print_function import os @@ -599,10 +600,10 @@ class FemasTdApi(TdApi): # 如果登录成功,推送日志信息 if error['ErrorID'] == 0: for k, v in data.items(): - print k, ':', v + print(k, ':', v) if data['MaxOrderLocalID']: self.localID = int(data['MaxOrderLocalID']) # 目前最大本地报单号 - print 'id now', self.localID + print('id now', self.localID) self.loginStatus = True self.gateway.mdConnected = True diff --git a/vnpy/trader/gateway/futuGateway/Futu_connect.json b/vnpy/trader/gateway/futuGateway/Futu_connect.json index d26a492d..723cefb8 100644 --- a/vnpy/trader/gateway/futuGateway/Futu_connect.json +++ b/vnpy/trader/gateway/futuGateway/Futu_connect.json @@ -3,5 +3,5 @@ "port": 11111, "market": "HK", "password": "123123", - "env": 1 + "env": "REAL" } \ No newline at end of file diff --git a/vnpy/trader/gateway/futuGateway/__init__.py b/vnpy/trader/gateway/futuGateway/__init__.py index 0b477e7f..3778ebc6 100644 --- a/vnpy/trader/gateway/futuGateway/__init__.py +++ b/vnpy/trader/gateway/futuGateway/__init__.py @@ -1,7 +1,8 @@ # encoding: UTF-8 +from __future__ import absolute_import from vnpy.trader import vtConstant -from futuGateway import FutuGateway +from .futuGateway import FutuGateway gatewayClass = FutuGateway gatewayName = 'FUTU' diff --git a/vnpy/trader/gateway/futuGateway/futuGateway.py b/vnpy/trader/gateway/futuGateway/futuGateway.py index 63f5ba3c..bb7eb19c 100644 --- a/vnpy/trader/gateway/futuGateway/futuGateway.py +++ b/vnpy/trader/gateway/futuGateway/futuGateway.py @@ -11,11 +11,11 @@ from time import sleep from datetime import datetime from copy import copy -import futuquant as ft -from futuquant.open_context import (RET_ERROR, RET_OK, PriceRegularMode, - StockQuoteHandlerBase, OrderBookHandlerBase, - USTradeOrderHandlerBase, USTradeDealHandlerBase, - HKTradeOrderHandlerBase, HKTradeDealHandlerBase) +from futuquant import (OpenQuoteContext, OpenHKTradeContext, OpenUSTradeContext, + RET_ERROR, RET_OK, + TrdEnv, TrdSide, OrderType, OrderStatus, ModifyOrderOp, + StockQuoteHandlerBase, OrderBookHandlerBase, + TradeOrderHandlerBase, TradeDealHandlerBase) from vnpy.trader.vtGateway import * from vnpy.trader.vtConstant import GATEWAYTYPE_INTERNATIONAL @@ -34,23 +34,20 @@ productMap[PRODUCT_WARRANT] = 'WARRANT' productMap[PRODUCT_BOND] = 'BOND' directionMap = {} -directionMap[DIRECTION_LONG] = '0' -directionMap[DIRECTION_SHORT] = '1' +directionMap[DIRECTION_LONG] = TrdSide.BUY +directionMap[DIRECTION_SHORT] = TrdSide.SELL directionMapReverse = {v:k for k,v in directionMap.items()} statusMapReverse = {} -statusMapReverse['0'] = STATUS_UNKNOWN -statusMapReverse['1'] = STATUS_NOTTRADED -statusMapReverse['2'] = STATUS_PARTTRADED -statusMapReverse['3'] = STATUS_ALLTRADED -statusMapReverse['4'] = STATUS_CANCELLED -statusMapReverse['5'] = STATUS_REJECTED -statusMapReverse['6'] = STATUS_CANCELLED -statusMapReverse['7'] = STATUS_CANCELLED -statusMapReverse['8'] = STATUS_UNKNOWN -statusMapReverse['21'] = STATUS_UNKNOWN -statusMapReverse['22'] = STATUS_UNKNOWN -statusMapReverse['23'] = STATUS_UNKNOWN +statusMapReverse[OrderStatus.NONE] = STATUS_UNKNOWN +statusMapReverse[OrderStatus.SUBMITTED] = STATUS_NOTTRADED +statusMapReverse[OrderStatus.FILLED_PART] = STATUS_PARTTRADED +statusMapReverse[OrderStatus.FILLED_ALL] = STATUS_ALLTRADED +statusMapReverse[OrderStatus.CANCELLED_ALL] = STATUS_CANCELLED +statusMapReverse[OrderStatus.CANCELLED_PART] = STATUS_CANCELLED +statusMapReverse[OrderStatus.SUBMIT_FAILED] = STATUS_REJECTED +statusMapReverse[OrderStatus.FAILED] = STATUS_REJECTED +statusMapReverse[OrderStatus.DISABLED] = STATUS_CANCELLED @@ -70,13 +67,13 @@ class FutuGateway(VtGateway): self.ip = 0 self.market = '' self.password = '' - self.env = 1 # 默认仿真交易 + self.env = TrdEnv.SIMULATE # 默认仿真交易 self.fileName = self.gatewayName + '_connect.json' self.filePath = getJsonPath(self.fileName, __file__) self.tickDict = {} - self.tradeSet = set() # 保存成交编号的集合,防止重复推送 + self.tradeSet = set() # 保存成交编号的集合,防止重复推送 self.qryEnabled = True self.qryThread = Thread(target=self.qryData) @@ -138,7 +135,7 @@ class FutuGateway(VtGateway): #---------------------------------------------------------------------- def connectQuote(self): """连接行情功能""" - self.quoteCtx = ft.OpenQuoteContext(self.host, self.port) + self.quoteCtx = OpenQuoteContext(self.host, self.port) # 继承实现处理器类 class QuoteHandler(StockQuoteHandlerBase): @@ -177,16 +174,12 @@ class FutuGateway(VtGateway): """连接交易功能""" # 连接交易接口 if self.market == 'US': - self.tradeCtx = ft.OpenUSTradeContext(self.host, self.port) - OrderHandlerBase = USTradeOrderHandlerBase - DealHandlerBase = USTradeDealHandlerBase + self.tradeCtx = OpenUSTradeContext(self.host, self.port) else: - self.tradeCtx = ft.OpenHKTradeContext(self.host, self.port) - OrderHandlerBase = HKTradeOrderHandlerBase - DealHandlerBase = HKTradeDealHandlerBase - + self.tradeCtx = OpenHKTradeContext(self.host, self.port) + # 继承实现处理器类 - class OrderHandler(OrderHandlerBase): + class OrderHandler(TradeOrderHandlerBase): """委托处理器""" gateway = self # 缓存Gateway对象 @@ -197,7 +190,7 @@ class FutuGateway(VtGateway): self.gateway.processOrder(content) return RET_OK, content - class DealHandler(DealHandlerBase): + class DealHandler(TradeDealHandlerBase): """订单簿处理器""" gateway = self @@ -209,8 +202,11 @@ class FutuGateway(VtGateway): return RET_OK, content # 只有港股实盘交易才需要解锁 - if self.market == 'HK' and self.env == 0: - self.tradeCtx.unlock_trade(self.password) + code, data = self.tradeCtx.unlock_trade(self.password) + if code == RET_OK: + self.writeLog(u'交易接口解锁成功') + else: + self.writeLog(u'交易接口解锁失败,原因:%s' %data) # 设置回调处理对象 self.tradeCtx.set_handler(OrderHandler()) @@ -219,11 +215,6 @@ class FutuGateway(VtGateway): # 启动交易接口 self.tradeCtx.start() - # 订阅委托推送 - self.tradeCtx.subscribe_order_deal_push([], - order_deal_push=True, - envtype=self.env) - self.writeLog(u'交易接口连接成功') #---------------------------------------------------------------------- @@ -239,26 +230,25 @@ class FutuGateway(VtGateway): def sendOrder(self, orderReq): """发单""" side = directionMap[orderReq.direction] - priceType = 0 # 只支持限价单 + priceType = OrderType.NORMAL # 只支持限价单 # 设置价格调整模式为向内调整(即买入调整后价格比原始价格低) if orderReq.direction == DIRECTION_LONG: - priceMode = PriceRegularMode.LOWER + adjustLimit = 0.05 else: - priceMode = PriceRegularMode.UPPER - + adjustLimit = -0.05 + code, data = self.tradeCtx.place_order(orderReq.price, orderReq.volume, - orderReq.symbol, side, - priceType, self.env, - order_deal_push=True, - price_mode=priceMode) + orderReq.symbol, side, priceType, + trd_env=self.env, + adjust_limit=adjustLimit) if code: self.writeError(code, u'委托失败:%s' %data) return '' for ix, row in data.iterrows(): - orderID = str(row['orderid']) + orderID = str(row['order_id']) vtOrderID = '.'.join([self.gatewayName, orderID]) @@ -267,8 +257,8 @@ class FutuGateway(VtGateway): #---------------------------------------------------------------------- def cancelOrder(self, cancelOrderReq): """撤单""" - code, data = self.tradeCtx.set_order_status(0, int(cancelOrderReq.orderID), - self.env) + code, data = self.tradeCtx.modify_order(ModifyOrderOp.CANCEL, cancelOrderReq.orderID, + 0, 0, trd_env=self.env) if code: self.writeError(code, u'撤单失败:%s' %data) @@ -293,7 +283,7 @@ class FutuGateway(VtGateway): contract.name = row['name'] contract.productClass = vtProductClass contract.size = int(row['lot_size']) - contract.priceTick = 0.01 + contract.priceTick = 0.001 self.onContract(contract) @@ -302,7 +292,7 @@ class FutuGateway(VtGateway): #---------------------------------------------------------------------- def qryAccount(self): """查询账户资金""" - code, data = self.tradeCtx.accinfo_query(self.env) + code, data = self.tradeCtx.accinfo_query(trd_env=self.env, acc_id=0) if code: self.writeError(code, u'查询账户资金失败:%s' %data) @@ -314,16 +304,15 @@ class FutuGateway(VtGateway): account.accountID = '%s_%s' %(self.gatewayName, self.market) account.vtAccountID = '.'.join([self.gatewayName, account.accountID]) - account.balance = float(row['ZCJZ']) - account.margin = float(row['GPBZJ']) - account.available = float(row['XJJY']) + account.balance = float(row['total_assets']) + account.available = float(row['avl_withdrawal_cash']) self.onAccount(account) #---------------------------------------------------------------------- def qryPosition(self): """查询持仓""" - code, data = self.tradeCtx.position_list_query(envtype=self.env) + code, data = self.tradeCtx.position_list_query(trd_env=self.env, acc_id=0) if code: self.writeError(code, u'查询持仓失败:%s' %data) @@ -339,10 +328,10 @@ class FutuGateway(VtGateway): pos.direction = DIRECTION_LONG pos.vtPositionName = '.'.join([pos.vtSymbol, pos.direction]) - pos.position = int(row['qty']) + pos.position = float(row['qty']) pos.price = float(row['cost_price']) pos.positionProfit = float(row['pl_val']) - pos.frozen = int(row['qty']) - int(row['can_sell_qty']) + pos.frozen = float(row['qty']) - float(row['can_sell_qty']) if pos.price < 0: pos.price = 0 if pos.positionProfit > 100000000: pos.positionProfit = 0 @@ -352,7 +341,7 @@ class FutuGateway(VtGateway): #---------------------------------------------------------------------- def qryOrder(self): """查询委托""" - code, data = self.tradeCtx.order_list_query("", envtype=self.env) + code, data = self.tradeCtx.order_list_query("", trd_env=self.env) if code: self.writeError(code, u'查询委托失败:%s' %data) @@ -458,7 +447,7 @@ class FutuGateway(VtGateway): #---------------------------------------------------------------------- def processOrderBook(self, data): """订单簿推送""" - symbol = data['stock_code'] + symbol = data['code'] tick = self.tickDict.get(symbol, None) if not tick: @@ -488,34 +477,34 @@ class FutuGateway(VtGateway): """处理委托推送""" for ix, row in data.iterrows(): # 如果状态是已经删除,则直接忽略 - if str(row['status']) == '7': + if row['order_status'] == OrderStatus.DELETED: continue + print(row['order_status']) order = VtOrderData() order.gatewayName = self.gatewayName order.symbol = row['code'] order.vtSymbol = order.symbol - order.orderID = str(row['orderid']) + order.orderID = str(row['order_id']) order.vtOrderID = '.'.join([self.gatewayName, order.orderID]) order.price = float(row['price']) - order.totalVolume = int(row['qty']) - order.tradedVolume = int(row['dealt_qty']) - - t = datetime.fromtimestamp(float(row['submited_time'])) - order.orderTime = t.strftime('%H:%M:%S') + order.totalVolume = float(row['qty']) + order.tradedVolume = float(row['dealt_qty']) + order.orderTime = row['create_time'].split(' ')[-1] - order.status = statusMapReverse.get(str(row['status']), STATUS_UNKNOWN) - order.direction = directionMapReverse[str(row['order_side'])] + order.status = statusMapReverse.get(row['order_status'], STATUS_UNKNOWN) + order.direction = directionMapReverse[row['trd_side']] + self.onOrder(order) #---------------------------------------------------------------------- def processDeal(self, data): """处理成交推送""" for ix, row in data.iterrows(): - tradeID = row['dealid'] + tradeID = str(row['deal_id']) if tradeID in self.tradeSet: continue self.tradeSet.add(tradeID) @@ -529,14 +518,13 @@ class FutuGateway(VtGateway): trade.tradeID = tradeID trade.vtTradeID = '.'.join([self.gatewayName, trade.tradeID]) - trade.orderID = row['orderid'] + trade.orderID = row['order_id'] trade.vtOrderID = '.'.join([self.gatewayName, trade.orderID]) trade.price = float(row['price']) trade.volume = float(row['qty']) - trade.direction = directionMapReverse[str(row['order_side'])] + trade.direction = directionMapReverse[row['trd_side']] - t = datetime.fromtimestamp(float(row['time'])) - trade.tradeTime = t.strftime('%H:%M:%S') + trade.tradeTime = row['create_time'].split(' ')[-1] self.onTrade(trade) \ No newline at end of file diff --git a/vnpy/trader/gateway/fxcmGateway/__init__.py b/vnpy/trader/gateway/fxcmGateway/__init__.py index 26e36c12..53254b47 100644 --- a/vnpy/trader/gateway/fxcmGateway/__init__.py +++ b/vnpy/trader/gateway/fxcmGateway/__init__.py @@ -1,7 +1,8 @@ # encoding: UTF-8 +from __future__ import absolute_import from vnpy.trader import vtConstant -from fxcmGateway import FxcmGateway +from .fxcmGateway import FxcmGateway gatewayClass = FxcmGateway gatewayName = 'FXCM' diff --git a/vnpy/trader/gateway/fxcmGateway/fxcmGateway.py b/vnpy/trader/gateway/fxcmGateway/fxcmGateway.py index 712b0692..f0345dd6 100644 --- a/vnpy/trader/gateway/fxcmGateway/fxcmGateway.py +++ b/vnpy/trader/gateway/fxcmGateway/fxcmGateway.py @@ -1,5 +1,6 @@ # encoding: UTF-8 +from __future__ import print_function import os import json from datetime import datetime @@ -434,22 +435,22 @@ class Api(FxcmApi): #---------------------------------------------------------------------- def onOpenTrade(self, data, reqid): """开仓回调""" - print data, reqid + print(data, reqid) #---------------------------------------------------------------------- def onCloseTrade(self, data, reqid): """平仓回调""" - print data, reqid + print(data, reqid) #---------------------------------------------------------------------- def onChangeOrder(self, data, reqid): """改单回调""" - print data, reqid + print(data, reqid) #---------------------------------------------------------------------- def onDeleteOrder(self, data, reqid): """撤单回调""" - print data, reqid + print(data, reqid) #---------------------------------------------------------------------- def onPriceUpdate(self, data): @@ -553,7 +554,7 @@ class Api(FxcmApi): #---------------------------------------------------------------------- def getTime(s): - """把OANDA返回的时间格式转化为简单的时间字符串""" + """把时间格式转化为简单的时间字符串""" month = s[:2] day = s[2:4] year = s[4:8] diff --git a/vnpy/trader/gateway/huobiGateway/__init__.py b/vnpy/trader/gateway/huobiGateway/__init__.py index b6bb6efe..34044316 100644 --- a/vnpy/trader/gateway/huobiGateway/__init__.py +++ b/vnpy/trader/gateway/huobiGateway/__init__.py @@ -1,7 +1,8 @@ # encoding: UTF-8 +from __future__ import absolute_import from vnpy.trader import vtConstant -from huobiGateway import HuobiGateway +from .huobiGateway import HuobiGateway gatewayClass = HuobiGateway gatewayName = 'HUOBI' diff --git a/vnpy/trader/gateway/huobiGateway/huobiGateway.py b/vnpy/trader/gateway/huobiGateway/huobiGateway.py index b3fc31d4..758bb7de 100644 --- a/vnpy/trader/gateway/huobiGateway/huobiGateway.py +++ b/vnpy/trader/gateway/huobiGateway/huobiGateway.py @@ -3,8 +3,8 @@ ''' vn.sec的gateway接入 ''' +from __future__ import print_function -import os import json from datetime import datetime, timedelta from copy import copy @@ -12,7 +12,7 @@ from math import pow from vnpy.api.huobi import TradeApi, DataApi from vnpy.trader.vtGateway import * -from vnpy.trader.vtFunction import getJsonPath, getTempPath +from vnpy.trader.vtFunction import getJsonPath # 委托状态类型映射 @@ -29,11 +29,9 @@ statusMapReverse['canceled'] = STATUS_CANCELLED #---------------------------------------------------------------------- def print_dict(d): """""" - print '-' * 30 - l = d.keys() - l.sort() - for k in l: - print '%s:%s' %(k, d[k]) + print('-' * 30) + for key in sorted(d): + print('%s:%s' % (key, d[key])) ######################################################################## @@ -60,7 +58,7 @@ class HuobiGateway(VtGateway): def connect(self): """连接""" try: - f = file(self.filePath) + f = open(self.filePath) except IOError: log = VtLogData() log.gatewayName = self.gatewayName @@ -75,8 +73,6 @@ class HuobiGateway(VtGateway): accessKey = str(setting['accessKey']) secretKey = str(setting['secretKey']) symbols = setting['symbols'] - proxyHost = str(setting['proxyHost']) - proxyPort = int(setting['proxyPort']) except KeyError: log = VtLogData() log.gatewayName = self.gatewayName @@ -85,8 +81,8 @@ class HuobiGateway(VtGateway): return # 创建行情和交易接口对象 - self.dataApi.connect(exchange, proxyHost, proxyPort) - self.tradeApi.connect(exchange, accessKey, secretKey, symbols) + self.dataApi.connect(exchange, symbols) + self.tradeApi.connect(exchange, symbols, accessKey, secretKey) # 初始化并启动查询 self.initQuery() @@ -94,7 +90,8 @@ class HuobiGateway(VtGateway): #---------------------------------------------------------------------- def subscribe(self, subscribeReq): """订阅行情""" - self.dataApi.subscribe(subscribeReq) + pass + #self.dataApi.subscribe(subscribeReq) #---------------------------------------------------------------------- def sendOrder(self, orderReq): @@ -179,38 +176,40 @@ class HuobiDataApi(DataApi): self.tickDict = {} - self.subscribeDict = {} + #self.subscribeDict = {} #---------------------------------------------------------------------- - def connect(self, exchange, proxyHost, proxyPort): + def connect(self, exchange, symbols): """连接服务器""" if exchange == 'huobi': url = 'wss://api.huobi.pro/ws' else: url = 'wss://api.hadax.com/ws' + + self.symbols = symbols - if proxyHost: - self.connectionStatus = super(HuobiDataApi, self).connect(url, proxyHost, proxyPort) - else: - self.connectionStatus = super(HuobiDataApi, self).connect(url) + self.connectionStatus = super(HuobiDataApi, self).connect(url) self.gateway.mdConnected = True if self.connectionStatus: self.writeLog(u'行情服务器连接成功') - + + for symbol in self.symbols: + self.subscribe(symbol) # 订阅所有之前订阅过的行情 - for req in self.subscribeDict.values(): - self.subscribe(req) + #for req in self.subscribeDict.values(): + # self.subscribe(req) + #---------------------------------------------------------------------- - def subscribe(self, subscribeReq): + def subscribe(self, symbol): """订阅合约""" - self.subscribeDict[subscribeReq.symbol] = subscribeReq + #self.subscribeDict[subscribeReq.symbol] = subscribeReq if not self.connectionStatus: return - symbol = subscribeReq.symbol + #symbol = subscribeReq.symbol if symbol in self.tickDict: return @@ -258,14 +257,14 @@ class HuobiDataApi(DataApi): bids = data['tick']['bids'] for n in range(5): l = bids[n] - tick.__setattr__('bidPrice' + str(n+1), l[0]) - tick.__setattr__('bidVolume' + str(n+1), l[1]) + tick.__setattr__('bidPrice' + str(n+1), float(l[0])) + tick.__setattr__('bidVolume' + str(n+1), float(l[1])) asks = data['tick']['asks'] for n in range(5): l = asks[n] - tick.__setattr__('askPrice' + str(n+1), l[0]) - tick.__setattr__('askVolume' + str(n+1), l[1]) + tick.__setattr__('askPrice' + str(n+1), float(l[0])) + tick.__setattr__('askVolume' + str(n+1), float(l[1])) #print '-' * 50 #for d in data['tick']['asks']: @@ -294,7 +293,7 @@ class HuobiDataApi(DataApi): #---------------------------------------------------------------------- def onTradeDetail(self, data): """成交细节推送""" - print data + print(data) #---------------------------------------------------------------------- def onMarketDetail(self, data): @@ -310,19 +309,18 @@ class HuobiDataApi(DataApi): tick.time = tick.datetime.strftime('%H:%M:%S.%f') t = data['tick'] - tick.openPrice = t['open'] - tick.highPrice = t['high'] - tick.lowPrice = t['low'] - tick.lastPrice = t['close'] - tick.volume = t['vol'] - tick.preClosePrice = tick.openPrice + tick.openPrice = float(t['open']) + tick.highPrice = float(t['high']) + tick.lowPrice = float(t['low']) + tick.lastPrice = float(t['close']) + tick.volume = float(t['vol']) + tick.preClosePrice = float(tick.openPrice) if tick.bidPrice1: newtick = copy(tick) self.gateway.onTick(tick) - ######################################################################## class HuobiTradeApi(TradeApi): """交易API实现""" @@ -355,7 +353,7 @@ class HuobiTradeApi(TradeApi): #self.activeOrderSet = set() # 活动委托集合 #---------------------------------------------------------------------- - def connect(self, exchange, accessKey, secretKey, symbols=''): + def connect(self, exchange, symbols, accessKey, secretKey): """初始化连接""" if not self.connectionStatus: self.symbols = symbols @@ -452,6 +450,10 @@ class HuobiTradeApi(TradeApi): #---------------------------------------------------------------------- def onError(self, msg, reqid): """错误回调""" + # 忽略请求超时错误 + if '429' in msg or 'api-signature-not-valid' in msg: + return + err = VtErrorData() err.gatewayName = self.gatewayName err.errorID = 'Trade' @@ -495,36 +497,33 @@ class HuobiTradeApi(TradeApi): def onGetAccounts(self, data, reqid): """查询账户回调""" for d in data: - self.accountid = str(d['id']) - self.writeLog(u'交易账户%s查询成功' %self.accountid) + if str(d['type']) == 'spot': + self.accountid = str(d['id']) + self.writeLog(u'交易账户%s查询成功' %self.accountid) #---------------------------------------------------------------------- def onGetAccountBalance(self, data, reqid): """查询余额回调""" - posDict = {} - + accountDict = {} + for d in data['list']: - symbol = d['currency'] - pos = posDict.get(symbol, None) + currency = d['currency'] + account = accountDict.get(currency, None) - if not pos: - pos = VtPositionData() - pos.gatewayName = self.gatewayName - pos.symbol = d['currency'] - pos.exchange = EXCHANGE_HUOBI - pos.vtSymbol = '.'.join([pos.symbol, pos.exchange]) - pos.direction = DIRECTION_LONG - pos.vtPositionName = '.'.join([pos.vtSymbol, pos.direction]) - - pos.position += float(d['balance']) + if not account: + account = VtAccountData() + account.gatewayName = self.gatewayName + account.accountID = d['currency'] + account.vtAccountID = '.'.join([account.gatewayName, account.accountID]) + + accountDict[currency] = account + + account.balance += float(d['balance']) if d['type'] == 'fozen': - pos.frozen = float(d['balance']) + account.available = account.balance - float(d['balance']) - posDict[symbol] = pos - - for pos in posDict.values(): - if pos.position: - self.gateway.onPosition(pos) + for account in accountDict.values(): + self.gateway.onAccount(account) #---------------------------------------------------------------------- def onGetOrders(self, data, reqid): @@ -597,13 +596,14 @@ class HuobiTradeApi(TradeApi): if d['canceled-at']: order.cancelTime = datetime.fromtimestamp(d['canceled-at']/1000).strftime('%H:%M:%S') - newTradedVolume = d['field-amount'] + newTradedVolume = float(d['field-amount']) newStatus = statusMapReverse.get(d['state'], STATUS_UNKNOWN) if newTradedVolume != order.tradedVolume or newStatus != order.status: updated = True - order.tradedVolume = float(newTradedVolume) - order.status = newStatus + + order.tradedVolume = newTradedVolume + order.status = newStatus # 只推送有更新的数据 if updated: @@ -693,7 +693,7 @@ class HuobiTradeApi(TradeApi): #---------------------------------------------------------------------- def onGetMatchResult(self, data, reqid): """查询单一成交回调""" - print reqid, data + print(reqid, data) #---------------------------------------------------------------------- def onPlaceOrder(self, data, reqid): @@ -717,4 +717,4 @@ class HuobiTradeApi(TradeApi): #---------------------------------------------------------------------- def onBatchCancel(self, data, reqid): """批量撤单回调""" - print reqid, data + print(reqid, data) diff --git a/vnpy/trader/gateway/ibGateway/__init__.py b/vnpy/trader/gateway/ibGateway/__init__.py index c7be4fdd..8d6335a4 100644 --- a/vnpy/trader/gateway/ibGateway/__init__.py +++ b/vnpy/trader/gateway/ibGateway/__init__.py @@ -1,7 +1,8 @@ # encoding: UTF-8 +from __future__ import absolute_import from vnpy.trader import vtConstant -from ibGateway import IbGateway +from .ibGateway import IbGateway gatewayClass = IbGateway gatewayName = 'IB' diff --git a/vnpy/trader/gateway/ibGateway/ibGateway.py b/vnpy/trader/gateway/ibGateway/ibGateway.py index 9c6c251e..3f70826b 100644 --- a/vnpy/trader/gateway/ibGateway/ibGateway.py +++ b/vnpy/trader/gateway/ibGateway/ibGateway.py @@ -10,6 +10,7 @@ Interactive Brokers的gateway接入,已经替换为vn.ib封装。 4. 目前只支持股票和期货交易,ib api里期权合约的确定是基于Contract对象的多个字段,比较复杂暂时没做 5. 海外市场的交易规则和国内有很多细节上的不同,所以一些字段类型的映射可能不合理,如果发现问题欢迎指出 ''' +from __future__ import print_function import os import json @@ -375,7 +376,7 @@ class IbWrapper(IbApi): newtick = copy(tick) self.gateway.onTick(newtick) else: - print field + print(field) #---------------------------------------------------------------------- def tickSize(self, tickerId, field, size): @@ -385,7 +386,7 @@ class IbWrapper(IbApi): key = tickFieldMap[field] tick.__setattr__(key, size) else: - print field + print(field) #---------------------------------------------------------------------- def tickOptionComputation(self, tickerId, tickType, impliedVol, delta, optPrice, pvDividend, gamma, vega, theta, undPrice): diff --git a/vnpy/trader/gateway/ibGateway/language/__init__.py b/vnpy/trader/gateway/ibGateway/language/__init__.py index 8325ae0c..e9efec4b 100644 --- a/vnpy/trader/gateway/ibGateway/language/__init__.py +++ b/vnpy/trader/gateway/ibGateway/language/__init__.py @@ -1,11 +1,12 @@ # encoding: UTF-8 +from __future__ import absolute_import import json import os import traceback # 默认设置 -from chinese import text +from .chinese import text # 获取目录上级路径 path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..')) @@ -14,10 +15,9 @@ SETTING_FILENAME = os.path.join(path, SETTING_FILENAME) # 打开配置文件,读取语言配置 try: - f = file(SETTING_FILENAME) - setting = json.load(f) + with open(SETTING_FILENAME) as f: + setting = json.load(f) if setting['language'] == 'english': - from english import text - f.close() -except: + from .english import text +except Exception: traceback.print_exc() diff --git a/vnpy/trader/gateway/ksgoldGateway/__init__.py b/vnpy/trader/gateway/ksgoldGateway/__init__.py index b81b6ef3..cc9ef208 100644 --- a/vnpy/trader/gateway/ksgoldGateway/__init__.py +++ b/vnpy/trader/gateway/ksgoldGateway/__init__.py @@ -1,7 +1,8 @@ # encoding: UTF-8 +from __future__ import absolute_import from vnpy.trader import vtConstant -from ksgoldGateway import KsgoldGateway +from .ksgoldGateway import KsgoldGateway gatewayClass = KsgoldGateway gatewayName = 'KSGOLD' diff --git a/vnpy/trader/gateway/ksotpGateway/__init__.py b/vnpy/trader/gateway/ksotpGateway/__init__.py index cf9c5dbb..d18f121a 100644 --- a/vnpy/trader/gateway/ksotpGateway/__init__.py +++ b/vnpy/trader/gateway/ksotpGateway/__init__.py @@ -1,7 +1,8 @@ # encoding: UTF-8 +from __future__ import absolute_import from vnpy.trader import vtConstant -from ksotpGateway import KsotpGateway +from .ksotpGateway import KsotpGateway gatewayClass = KsotpGateway gatewayName = 'KSOTP' diff --git a/vnpy/trader/gateway/lbankGateway/LBANK_connect.json b/vnpy/trader/gateway/lbankGateway/LBANK_connect.json index 5df1ee29..59286a19 100644 --- a/vnpy/trader/gateway/lbankGateway/LBANK_connect.json +++ b/vnpy/trader/gateway/lbankGateway/LBANK_connect.json @@ -1,6 +1,5 @@ { - "apiKey": "请在链行官网申请", - "secretKey": "请在链行官网申请", - "interval": 1, - "debug": false + "apiKey": "", + "secretKey": "", + "symbols": ["eth_usdt", "sc_btc", "btc_usdt"] } diff --git a/vnpy/trader/gateway/lbankGateway/__init__.py b/vnpy/trader/gateway/lbankGateway/__init__.py index 4d5d6e00..72ba49d8 100644 --- a/vnpy/trader/gateway/lbankGateway/__init__.py +++ b/vnpy/trader/gateway/lbankGateway/__init__.py @@ -1,7 +1,8 @@ # encoding: UTF-8 +from __future__ import absolute_import from vnpy.trader import vtConstant -from lbankGateway import LbankGateway +from .lbankGateway import LbankGateway gatewayClass = LbankGateway gatewayName = 'LBANK' diff --git a/vnpy/trader/gateway/lbankGateway/lbankGateway.py b/vnpy/trader/gateway/lbankGateway/lbankGateway.py index 85f82ae3..744eec64 100644 --- a/vnpy/trader/gateway/lbankGateway/lbankGateway.py +++ b/vnpy/trader/gateway/lbankGateway/lbankGateway.py @@ -1,59 +1,54 @@ # encoding: UTF-8 ''' -vn.lhang的gateway接入 +vnpy.api.lhang的gateway接入 ''' +from __future__ import print_function import os import json from datetime import datetime from time import sleep +from copy import copy -from vnpy.api.lbank import LbankApi +from vnpy.api.lbank import LbankRestApi, LbankWebsocketApi from vnpy.trader.vtGateway import * from vnpy.trader.vtFunction import getJsonPath -SYMBOL_BTCCNY = 'BTCCNY' -SYMBOL_ZECCNY = 'ZECCNY' +directionMap = {} +directionMap[DIRECTION_LONG] = 'buy' +directionMap[DIRECTION_SHORT] = 'sell' +directionMapReverse = {v:k for k,v in directionMap.items()} -SYMBOL_MAP = {} -SYMBOL_MAP['btc_cny'] = SYMBOL_BTCCNY -SYMBOL_MAP['zec_cny'] = SYMBOL_ZECCNY -SYMBOL_MAP_REVERSE = {v: k for k, v in SYMBOL_MAP.items()} - - -DIRECTION_MAP = {} -DIRECTION_MAP['buy'] = DIRECTION_LONG -DIRECTION_MAP['sell'] = DIRECTION_SHORT - -STATUS_MAP = {} -STATUS_MAP[0] = STATUS_NOTTRADED -STATUS_MAP[1] = STATUS_PARTTRADED -STATUS_MAP[2] = STATUS_ALLTRADED -STATUS_MAP[4] = STATUS_UNKNOWN -STATUS_MAP[-1] = STATUS_CANCELLED +statusMapReverse = {} +statusMapReverse[0] = STATUS_NOTTRADED +statusMapReverse[1] = STATUS_PARTTRADED +statusMapReverse[2] = STATUS_ALLTRADED +statusMapReverse[4] = STATUS_UNKNOWN +statusMapReverse[-1] = STATUS_CANCELLED ######################################################################## class LbankGateway(VtGateway): - """LBANK接口""" + """FCOIN接口""" #---------------------------------------------------------------------- - def __init__(self, eventEngine, gatewayName='LBANK'): + def __init__(self, eventEngine, gatewayName=''): """Constructor""" super(LbankGateway, self).__init__(eventEngine, gatewayName) + + self.restApi = RestApi(self) - self.api = LbankApi(self) - + self.qryEnabled = False # 是否要启动循环查询 + self.fileName = self.gatewayName + '_connect.json' - self.filePath = getJsonPath(self.fileName, __file__) - + self.filePath = getJsonPath(self.fileName, __file__) + #---------------------------------------------------------------------- def connect(self): """连接""" - # 载入json文件 try: f = file(self.filePath) except IOError: @@ -62,466 +57,562 @@ class LbankGateway(VtGateway): log.logContent = u'读取连接配置出错,请检查' self.onLog(log) return - + # 解析json文件 setting = json.load(f) try: - accessKey = str(setting['apiKey']) + apiKey = str(setting['apiKey']) secretKey = str(setting['secretKey']) - interval = setting['interval'] - debug = setting['debug'] + symbols = setting['symbols'] except KeyError: log = VtLogData() log.gatewayName = self.gatewayName log.logContent = u'连接配置缺少字段,请检查' self.onLog(log) - return - - # 初始化接口 - self.api.connect(accessKey, secretKey, interval, debug) - self.writeLog(u'接口初始化成功') - - # 启动查询 + return + + # 创建行情和交易接口对象 + self.restApi.connect(apiKey, secretKey, symbols) + + # 初始化并启动查询 self.initQuery() - self.startQuery() + + #---------------------------------------------------------------------- + def subscribe(self, subscribeReq): + """订阅行情""" + pass + + #---------------------------------------------------------------------- + def sendOrder(self, orderReq): + """发单""" + return self.restApi.sendOrder(orderReq) + + #---------------------------------------------------------------------- + def cancelOrder(self, cancelOrderReq): + """撤单""" + self.restApi.cancelOrder(cancelOrderReq) + + #---------------------------------------------------------------------- + def close(self): + """关闭""" + self.restApi.close() + #---------------------------------------------------------------------- + def initQuery(self): + """初始化连续查询""" + if self.qryEnabled: + # 需要循环的查询函数列表 + self.qryFunctionList = [self.restApi.qryAccount, + self.restApi.qryWorkingOrder, + self.restApi.qryCompletedOrder, + self.restApi.qryMarketData] + + self.qryCount = 0 # 查询触发倒计时 + self.qryTrigger = 1 # 查询触发点 + self.qryNextFunction = 0 # 上次运行的查询函数索引 + + self.startQuery() + + #---------------------------------------------------------------------- + def query(self, event): + """注册到事件处理引擎上的查询函数""" + self.qryCount += 1 + + if self.qryCount > self.qryTrigger: + # 清空倒计时 + self.qryCount = 0 + + # 执行查询函数 + function = self.qryFunctionList[self.qryNextFunction] + function() + + # 计算下次查询函数的索引,如果超过了列表长度,则重新设为0 + self.qryNextFunction += 1 + if self.qryNextFunction == len(self.qryFunctionList): + self.qryNextFunction = 0 + + #---------------------------------------------------------------------- + def startQuery(self): + """启动连续查询""" + self.eventEngine.register(EVENT_TIMER, self.query) + + #---------------------------------------------------------------------- + def setQryEnabled(self, qryEnabled): + """设置是否要启动循环查询""" + self.qryEnabled = qryEnabled + + +######################################################################## +class RestApi(LbankRestApi): + """REST API实现""" + + #---------------------------------------------------------------------- + def __init__(self, gateway): + """Constructor""" + super(RestApi, self).__init__() + + self.gateway = gateway # gateway对象 + self.gatewayName = gateway.gatewayName # gateway对象名称 + + self.localID = 0 + self.tradeID = 0 + + self.orderDict = {} # sysID:order + self.localSysDict = {} # localID:sysID + self.reqOrderDict = {} # reqID:order + self.cancelDict = {} # localID:req + + self.tickDict = {} # symbol:tick + self.reqSymbolDict = {} + + #---------------------------------------------------------------------- + def connect(self, apiKey, apiSecret, symbols): + """连接服务器""" + self.init(apiKey, apiSecret) + self.start() + + self.symbols = symbols + for symbol in symbols: + tick = VtTickData() + tick.gatewayName = self.gatewayName + tick.symbol = symbol + tick.exchange = EXCHANGE_LBANK + tick.vtSymbol = '.'.join([tick.symbol, tick.exchange]) + self.tickDict[symbol] = tick + + self.writeLog(u'REST API启动成功') + self.qryContract() + #---------------------------------------------------------------------- def writeLog(self, content): """发出日志""" log = VtLogData() log.gatewayName = self.gatewayName log.logContent = content - self.onLog(log) + self.gateway.onLog(log) - #---------------------------------------------------------------------- - def subscribe(self, subscribeReq): - """订阅行情,自动订阅全部行情,无需实现""" - pass - #---------------------------------------------------------------------- def sendOrder(self, orderReq): - """发单""" - self.api.sendOrder(orderReq) + """""" + self.localID += 1 + orderID = str(self.localID) + vtOrderID = '.'.join([self.gatewayName, orderID]) + req = { + 'symbol': orderReq.symbol, + 'type': directionMap[orderReq.direction], + 'price': orderReq.price, + 'amount': orderReq.volume + } + + reqid = self.addReq('POST', '/create_order.do', req, self.onSendOrder) + + # 缓存委托数据对象 + order = VtOrderData() + order.gatewayName = self.gatewayName + order.symbol = orderReq.symbol + order.exchange = EXCHANGE_LBANK + order.vtSymbol = '.'.join([order.symbol, order.exchange]) + order.orderID = orderID + order.vtOrderID = vtOrderID + order.price = orderReq.price + order.totalVolume = orderReq.volume + order.direction = orderReq.direction + order.status = STATUS_UNKNOWN + + self.reqOrderDict[reqid] = order + + return vtOrderID + #---------------------------------------------------------------------- def cancelOrder(self, cancelOrderReq): - """撤单""" - self.api.cancel(cancelOrderReq) + """""" + localID = cancelOrderReq.orderID + if localID in self.localSysDict: + sysID = self.localSysDict[localID] + order = self.orderDict[sysID] + req = { + 'symbol': order.symbol, + 'order_id': sysID + } + self.addReq('POST', '/cancel_order.do', req, self.onCancelOrder) + + else: + self.cancelDict[localID] = cancelOrderReq + + #---------------------------------------------------------------------- + def qryContract(self): + """""" + self.addReq('GET', '/accuracy.do', {}, self.onQryContract) + + #---------------------------------------------------------------------- + def qryCompletedOrder(self): + """""" + for symbol in self.symbols: + req = { + 'symbol': symbol, + 'current_page': '1', + 'page_length': '100' + } + self.addReq('POST', '/orders_info_history.do', req, self.onQryOrder) + + #---------------------------------------------------------------------- + def qryWorkingOrder(self): + """""" + for symbol in self.symbols: + req = { + 'symbol': symbol, + 'current_page': '1', + 'page_length': '100' + } + self.addReq('POST', '/orders_info_no_deal.do', req, self.onQryOrder) + #---------------------------------------------------------------------- def qryAccount(self): - """查询账户资金""" - pass - - #---------------------------------------------------------------------- - def qryPosition(self): - """查询持仓""" - pass - - #---------------------------------------------------------------------- - def close(self): - """关闭""" - self.api.exit() - - #---------------------------------------------------------------------- - def initQuery(self): - """初始化连续查询""" - if self.qryEnabled: - self.qryFunctionList = [self.api.queryPrice, - self.api.queryWorkingOrders, - self.api.queryAccount] - self.startQuery() + """""" + self.addReq('POST', '/user_info.do', {}, self.onQryAccount) #---------------------------------------------------------------------- - def query(self, event): - """注册到事件处理引擎上的查询函数""" - for function in self.qryFunctionList: - function() + def qryDepth(self): + """""" + for symbol in self.symbols: + req = { + 'symbol': symbol, + 'size': '5' + } + i = self.addReq('GET', '/depth.do', req, self.onQryDepth) + self.reqSymbolDict[i] = symbol + + #---------------------------------------------------------------------- + def qryTicker(self): + """""" + for symbol in self.symbols: + req = {'symbol': symbol} + i = self.addReq('GET', '/ticker.do', req, self.onQryTicker) + self.reqSymbolDict[i] = symbol + + #---------------------------------------------------------------------- + def qryMarketData(self): + """""" + self.qryDepth() + self.qryTicker() + + #---------------------------------------------------------------------- + def onSendOrder(self, data, reqid): + """""" + order = self.reqOrderDict[reqid] + localID = order.orderID + sysID = data['order_id'] + + self.localSysDict[localID] = sysID + self.orderDict[sysID] = order + + self.gateway.onOrder(order) + + # 发出等待的撤单委托 + if localID in self.cancelDict: + req = self.cancelDict[localID] + self.cancelOrder(req) + del self.cancelDict[localID] + + #---------------------------------------------------------------------- + def onCancelOrder(self, data, reqid): + """""" + pass + + #---------------------------------------------------------------------- + def onError(self, code, error): + """""" + msg = u'发生异常,错误代码:%s,错误信息:%s' %(code, error) + self.writeLog(msg) + + #---------------------------------------------------------------------- + def onQryOrder(self, data, reqid): + """""" + if 'orders' not in data: + return + + if not isinstance(data['orders'], list): + return + + data['orders'].reverse() + + for d in data['orders']: + orderUpdated = False + tradeUpdated = False + + # 获取委托对象 + sysID = d['order_id'] + if sysID in self.orderDict: + order = self.orderDict[sysID] + else: + order = VtOrderData() + order.gatewayName = self.gatewayName + order.symbol = d['symbol'] + order.exchange = EXCHANGE_LBANK + order.vtSymbol = '.'.join([order.symbol, order.exchange]) + + self.localID += 1 + localID = str(self.localID) + self.localSysDict[localID] = sysID + + order.orderID = localID + order.vtOrderID = '.'.join([order.gatewayName, order.orderID]) + + order.direction = directionMapReverse[d['type']] + order.price = float(d['price']) + order.totalVolume = float(d['amount']) + + dt = datetime.fromtimestamp(d['create_time']/1000) + order.orderTime = dt.strftime('%H:%M:%S') + + self.orderDict[sysID] = order + orderUpdated = True + + # 检查是否委托有变化 + newTradedVolume = float(d['deal_amount']) + newStatus = statusMapReverse[d['status']] + + if newTradedVolume != float(order.tradedVolume) or newStatus != order.status: + orderUpdated = True + + if newTradedVolume != float(order.tradedVolume): + tradeUpdated = True + newVolume = newTradedVolume - order.tradedVolume + + order.tradedVolume = newTradedVolume + order.status = newStatus + + # 若有更新才推送 + if orderUpdated: + self.gateway.onOrder(order) + + if tradeUpdated: + # 推送成交 + trade = VtTradeData() + trade.gatewayName = order.gatewayName + + trade.symbol = order.symbol + trade.vtSymbol = order.vtSymbol + + trade.orderID = order.orderID + trade.vtOrderID = order.vtOrderID + + self.tradeID += 1 + trade.tradeID = str(self.tradeID) + trade.vtTradeID = '.'.join([self.gatewayName, trade.tradeID]) + + trade.direction = order.direction + trade.price = order.price + trade.volume = newTradedVolume + trade.tradeTime = datetime.now().strftime('%H:%M:%S') + + self.gateway.onTrade(trade) + #---------------------------------------------------------------------- - def startQuery(self): - """启动连续查询""" - self.eventEngine.register(EVENT_TIMER, self.query) + def onQryAccount(self, data, reqid): + """""" + info = data['info'] + asset = info['asset'] + free = info['free'] + freeze = info['freeze'] + + for currency in asset.keys(): + account = VtAccountData() + account.gatewayName = self.gatewayName + + account.accountID = currency + account.vtAccountID = '.'.join([self.gatewayName, account.accountID]) + account.balance = float(asset[currency]) + account.available = float(free[currency]) + + self.gateway.onAccount(account) #---------------------------------------------------------------------- - def setQryEnabled(self, qryEnabled): - """设置是否要启动循环查询""" - self.qryEnabled = qryEnabled + def onQryContract(self, data, reqid): + """""" + for d in data: + contract = VtContractData() + contract.gatewayName = self.gatewayName + + contract.symbol = str(d['symbol']) + contract.exchange = EXCHANGE_LBANK + contract.vtSymbol = '.'.join([contract.symbol, contract.exchange]) + contract.name = contract.vtSymbol + contract.productClass = PRODUCT_SPOT + contract.priceTick = pow(10, -int(d['priceAccuracy'])) + contract.size = 1 + + self.gateway.onContract(contract) + + self.writeLog(u'合约信息查询完成') + #---------------------------------------------------------------------- + def onQryTicker(self, data, reqid): + """""" + ticker = data['ticker'] + + symbol = self.reqSymbolDict.pop(reqid) + tick = self.tickDict[symbol] + + tick.highPrice = float(ticker['high']) + tick.lowPrice = float(ticker['low']) + tick.lastPrice = float(ticker['latest']) + tick.volume = float(ticker['vol']) + + tick.datetime = datetime.fromtimestamp(int(data['timestamp']/1000)) + tick.date = tick.datetime.strftime('%Y%m%d') + tick.time = tick.datetime.strftime('%H:%M:%S') + + if tick.bidPrice1: + self.gateway.onTick(copy(tick)) + + #---------------------------------------------------------------------- + def onQryDepth(self, data, reqid): + """""" + symbol = self.reqSymbolDict.pop(reqid) + tick = self.tickDict[symbol] + + bids = data['bids'] + asks = data['asks'] + + tick.bidPrice1, tick.bidVolume1 = bids[0] + tick.bidPrice2, tick.bidVolume2 = bids[1] + tick.bidPrice3, tick.bidVolume3 = bids[2] + tick.bidPrice4, tick.bidVolume4 = bids[3] + tick.bidPrice5, tick.bidVolume5 = bids[4] + + tick.askPrice1, tick.askVolume1 = asks[0] + tick.askPrice2, tick.askVolume2 = asks[1] + tick.askPrice3, tick.askVolume3 = asks[2] + tick.askPrice4, tick.askVolume4 = asks[3] + tick.askPrice5, tick.askVolume5 = asks[4] + + if tick.lastPrice: + self.gateway.onTick(copy(tick)) + ######################################################################## -class LbankApi(LbankApi): +class WebsocketApi(LbankWebsocketApi): """""" #---------------------------------------------------------------------- def __init__(self, gateway): """Constructor""" - super(LbankApi, self).__init__() + super(WebsocketApi, self).__init__() self.gateway = gateway self.gatewayName = gateway.gatewayName - self.interval = 1 - - self.localID = 0 # 本地委托号 - self.localSystemDict = {} # key:localID, value:systemID - self.systemLocalDict = {} # key:systemID, value:localID - self.workingOrderDict = {} # key:localID, value:order - self.reqLocalDict = {} # key:reqID, value:localID - self.cancelDict = {} # key:localID, value:cancelOrderReq - - self.tradeID = 0 - - self.tickDict = {} # key:symbol, value:tick - + self.symbols = [] + self.channelTickDict = {} + #---------------------------------------------------------------------- - def onError(self, error, req, reqID): - """错误推送""" - err = VtErrorData() - err.gatewayName = self.gatewayName - err.errorMsg = str(error) - err.errorTime = datetime.now().strftime('%H:%M:%S.%f')[:-3] - self.gateway.onError(err) - + def connect(self, symbols): + """""" + self.symbols = symbols + self.start() + #---------------------------------------------------------------------- - def onGetTicker(self, data, req, reqID): - """查询行情回调""" - ticker = data['ticker'] - params = req['params'] - symbol = SYMBOL_MAP[params['symbol']] - - if symbol not in self.tickDict: + def onConnect(self): + """连接回调""" + self.writeLog(u'Websocket API连接成功') + self.subscribe() + + #---------------------------------------------------------------------- + def subscribe(self): + """""" + for symbol in self.symbols: tick = VtTickData() tick.gatewayName = self.gatewayName - tick.symbol = symbol tick.exchange = EXCHANGE_LBANK tick.vtSymbol = '.'.join([tick.symbol, tick.exchange]) - self.tickDict[symbol] = tick - else: - tick = self.tickDict[symbol] - - tick.highPrice = float(ticker['high']) - tick.lowPrice = float(ticker['low']) - tick.lastPrice = float(ticker['latest']) - tick.openPrice = tick.lastPrice - float(ticker['change']) - tick.volume = ticker['vol'] - - # ---------------------------------------------------------------------- - def onGetDepth(self, data, req, reqID): - """查询深度回调""" - params = req['params'] - symbol = SYMBOL_MAP[params['symbol']] - if symbol not in self.tickDict: - tick = VtTickData() - tick.gatewayName = self.gatewayName - - tick.symbol = symbol - tick.exchange = EXCHANGE_LBANK - tick.vtSymbol = '.'.join([tick.symbol, tick.exchange]) - self.tickDict[symbol] = tick - else: - tick = self.tickDict[symbol] - - tick.bidPrice1, tick.bidVolume1 = data['bids'][0] - tick.bidPrice2, tick.bidVolume2 = data['bids'][1] - tick.bidPrice3, tick.bidVolume3 = data['bids'][2] - tick.bidPrice4, tick.bidVolume4 = data['bids'][3] - tick.bidPrice5, tick.bidVolume5 = data['bids'][4] - - tick.askPrice1, tick.askVolume1 = data['asks'][0] - tick.askPrice2, tick.askVolume2 = data['asks'][1] - tick.askPrice3, tick.askVolume3 = data['asks'][2] - tick.askPrice4, tick.askVolume4 = data['asks'][3] - tick.askPrice5, tick.askVolume5 = data['asks'][4] - - now = datetime.now() - tick.time = now.strftime('%H:%M:%S.%f')[:-3] - tick.date = now.strftime('%Y%m%d') - - self.gateway.onTick(tick) - - # ---------------------------------------------------------------------- - def onGetTrades(self, data, req, reqID): - """查询历史成交""" - print data, reqID - - # ---------------------------------------------------------------------- - def onGetKline(self, data, req, reqID): - print data, reqID - - # ---------------------------------------------------------------------- - def onGetUserInfo(self, data, req, reqID): - """查询K线回报""" - d = data['info'] - account = VtAccountData() - account.gatewayName = self.gatewayName - account.accountID = self.gatewayName - account.vtAccountID = '.'.join([account.accountID, self.gatewayName]) - account.balance = d['asset']['net'] - self.gateway.onAccount(account) - - # 推送持仓数据 - posCny = VtPositionData() - posCny.gatewayName = self.gatewayName - posCny.symbol = 'CNY' - posCny.exchange = EXCHANGE_LBANK - posCny.vtSymbol = '.'.join([posCny.symbol, posCny.exchange]) - posCny.vtPositionName = posCny.vtSymbol - posCny.frozen = d['freeze']['cny'] - posCny.position = posCny.frozen + d['free']['cny'] - self.gateway.onPosition(posCny) - - posBtc = VtPositionData() - posBtc.gatewayName = self.gatewayName - posBtc.symbol = 'BTC' - posBtc.exchange = EXCHANGE_LBANK - posBtc.vtSymbol = '.'.join([posBtc.symbol, posBtc.exchange]) - posBtc.vtPositionName = posBtc.vtSymbol - posBtc.frozen = d['freeze']['btc'] - posBtc.position = posBtc.frozen + d['free']['btc'] - self.gateway.onPosition(posBtc) - - posZec = VtPositionData() - posZec.gatewayName = self.gatewayName - posZec.symbol = 'ZEC' - posZec.exchange = EXCHANGE_LBANK - posZec.vtSymbol = '.'.join([posZec.symbol, posZec.exchange]) - posZec.vtPositionName = posZec.vtSymbol - posZec.frozen = d['freeze']['zec'] - posZec.position = posZec.frozen + d['free']['zec'] - self.gateway.onPosition(posZec) - - # 查询历史委托 - self.queryOrders() - - # ---------------------------------------------------------------------- - def onCreateOrder(self, data, req, reqID): - """发单回调""" - localID = self.reqLocalDict[reqID] - systemID = data['id'] - self.localSystemDict[localID] = systemID - self.systemLocalDict[systemID] = localID - - # 撤单 - if localID in self.cancelDict: - req = self.cancelDict[localID] - self.cancel(req) - del self.cancelDict[localID] - - # 推送委托信息 - order = self.workingOrderDict[localID] - if data['result'] == 'success': - order.status = STATUS_NOTTRADED - self.gateway.onOrder(order) - - # ---------------------------------------------------------------------- - def onCancelOrder(self, data, req, reqID): - """撤单回调""" - if data['result'] == 'success': - systemID = req['params']['id'] - localID = self.systemLocalDict[systemID] - - order = self.workingOrderDict[localID] - order.status = STATUS_CANCELLED - - del self.workingOrderDict[localID] - self.gateway.onOrder(order) - - # ---------------------------------------------------------------------- - def onGetOrdersInfo(self, data, req, reqID): - """查询委托回报""" - if 'orders' in data: - for d in data['orders']: - systemID = d['order_id'] - localID = self.systemLocalDict[systemID] - order = self.workingOrderDict.get(localID, None) - if not order: - return - - # 记录最新成交的金额 - newTradeVolume = float(d['deal_amount']) - order.tradedVolume - if newTradeVolume: - trade = VtTradeData() - trade.gatewayName = self.gatewayName - trade.symbol = order.symbol - trade.vtSymbol = order.vtSymbol - - self.tradeID += 1 - trade.tradeID = str(self.tradeID) - trade.vtTradeID = '.'.join([trade.tradeID, trade.gatewayName]) - - trade.volume = newTradeVolume - trade.price = d['avg_price'] - trade.direction = order.direction - trade.offset = order.offset - trade.exchange = order.exchange - trade.tradeTime = datetime.now().strftime('%H:%M:%S.%f')[:-3] - - self.gateway.onTrade(trade) - - # 更新委托状态 - order.tradedVolume = float(d['deal_amount']) - order.status = STATUS_MAP.get(d['status'], STATUS_UNKNOWN) - - if newTradeVolume: - self.gateway.onOrder(order) - - if order.status == STATUS_ALLTRADED or order.status == STATUS_CANCELLED: - del self.workingOrderDict[order.orderID] - - # ---------------------------------------------------------------------- - def onGetOrdersInfoHistory(self, data, req, reqID): - """撤单回报""" - if 'orders' in data: - for d in data['orders']: - order = VtOrderData() - order.gatewayName = self.gatewayName - - order.symbol = SYMBOL_MAP[data['symbol']] - order.exchange = EXCHANGE_LBANK - order.vtSymbol = '.'.join([order.symbol, order.exchange]) - - systemID = d['order_id'] - self.localID += 1 - localID = str(self.localID) - self.systemLocalDict[systemID] = localID - self.localSystemDict[localID] = systemID - order.orderID = localID - order.vtOrderID = '.'.join([order.orderID, order.gatewayName]) - - order.totalVolume = float(d['amount']) - order.tradedVolume = float(d['deal_amount']) - order.price = float(d['price']) - order.direction = DIRECTION_MAP[d['type']] - order.offset = OFFSET_NONE - order.orderTime = datetime.fromtimestamp(d['create_time'], '%H:%M:%S') - - # 委托状态 - if order.tradedVolume == 0: - order.status = STATUS_NOTTRADED - else: - order.status = STATUS_PARTTRADED - - # 缓存病推送 - self.workingOrderDict[localID] = order - self.gateway.onOrder(order) - - #---------------------------------------------------------------------- - def connect(self, apiKey, secretKey, interval, debug): - """初始化""" - self.interval = interval - self.DEBUG = debug - - self.init(apiKey, secretKey, self.interval) - - # 推送合约信息 - contract = VtContractData() - contract.gatewayName = self.gatewayName - contract.symbol = SYMBOL_BTCCNY - contract.exchange = EXCHANGE_LBANK - contract.vtSymbol = '.'.join([contract.symbol, contract.exchange]) - contract.name = u'人民币现货BTC' - contract.size = 1 - contract.priceTick = 0.01 - contract.productClass = PRODUCT_SPOT - self.gateway.onContract(contract) - - contract = VtContractData() - contract.gatewayName = self.gatewayName - contract.symbol = SYMBOL_ZECCNY - contract.exchange = EXCHANGE_LBANK - contract.vtSymbol = '.'.join([contract.symbol, contract.exchange]) - contract.name = u'人民币现货ZEC' - contract.size = 1 - contract.priceTick = 0.01 - contract.productClass = PRODUCT_SPOT - self.gateway.onContract(contract) - - #---------------------------------------------------------------------- - def sendOrder(self, req): - """发单""" - # 检查是否填入了价格,禁止市价委托 - if req.priceType != PRICETYPE_LIMITPRICE: - err = VtErrorData() - err.gatewayName = self.gatewayName - err.errorMsg = u'LBANK接口仅支持限价单' - err.errorTime = datetime.now().strftime('%H:%M:%S.%f')[:-3] - self.gateway.onError(err) - return None - - # 发送限价委托 - s = SYMBOL_MAP_REVERSE[req.symbol] - - if req.direction == DIRECTION_LONG: - type_ = 'buy' - else: - type_ = 'sell' - - reqID = self.createOrder(s, type_, req.price, req.volume) - - self.localID += 1 - localID = str(self.localID) - self.reqLocalDict[reqID] = localID - - # 推送委托信息 - order = VtOrderData() - order.gatewayName = self.gatewayName - - order.symbol = req.symbol - order.exchange = EXCHANGE_LBANK - order.vtSymbol = '.'.join([order.symbol, order.exchange]) - - order.orderID = localID - order.vtOrderID = '.'.join([order.orderID, order.gatewayName]) - - order.direction = req.direction - order.offset = OFFSET_UNKNOWN - order.price = req.price - order.volume = req.volume - order.orderTime = datetime.now().strftime('%H:%M:%S.%f')[:-3] - order.status = STATUS_UNKNOWN - - self.workingOrderDict[localID] = order - self.gateway.onOrder(order) - - # 返回委托号 - return order.vtOrderID + + channelDepth = 'lh_sub_spot_%s_depth_20' %symbol + #channelTrades = 'lh_sub_spot_%s_ticker' %symbol + self.channelTickDict[channelDepth] = tick + # self.channelTickDict[channelTrades] = tick + + for channel in [channelDepth]: #, channelTrades]: + req = { + 'event': 'addChannel', + 'channel': channel + } + self.sendReq(req) #---------------------------------------------------------------------- - def cancel(self, req): - """撤单""" - localID = req.orderID - if localID in self.localSystemDict: - systemID = self.localSystemDict[localID] - s = SYMBOL_MAP_REVERSE[req.symbol] - self.cancelOrder(s, systemID) - else: - self.cancelDict[localID] = req + def onData(self, data): + """数据回调""" + channel = data['channel'] + print(data) + if 'trades' in channel: + self.onTick(data) + elif 'depth' in channel: + self.onDepth(data) + + #---------------------------------------------------------------------- + def onError(self, msg): + """错误回调""" + self.writeLog(msg) + + #---------------------------------------------------------------------- + def writeLog(self, content): + """发出日志""" + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = content + self.gateway.onLog(log) + + #---------------------------------------------------------------------- + def onTick(self, data): + """""" + if 'data' not in data: + return + + channel = data['channel'] + tick = self.channelTickDict[channel] + + fill = data['data'][0] + tick.lastPrice = float(fill[0]) + tick.datetime = datetime.fromtimestamp(int(fill[2])/1000) + tick.date = tick.datetime.strftime('%Y%m%d') + tick.time = tick.datetime.strftime('%H:%M:%S') + + self.gateway.onTick(copy(tick)) #---------------------------------------------------------------------- - def queryOrders(self): - """查询委托""" - for s in SYMBOL_MAP.keys(): - self.getOrdersInfoHistory(s, '0', '1', '200') + def onDepth(self, data): + """""" + if 'data' not in data: + return + + channel = data['channel'] + tick = self.channelTickDict[channel] - #---------------------------------------------------------------------- - def queryWorkingOrders(self): - """查询活动委托""" - for localID, order in self.workingOrderDict.items(): - if localID in self.localSystemDict: - systemID = self.localSystemDict[localID] - s = SYMBOL_MAP_REVERSE[order.symbol] - self.getOrdersInfo(s, systemID) + d = data['data'] + bids = d['bids'] + asks = d['asks'] + + tick.bidPrice1, tick.bidVolume1 = bids[0] + tick.bidPrice2, tick.bidVolume2 = bids[1] + tick.bidPrice3, tick.bidVolume3 = bids[2] + tick.bidPrice4, tick.bidVolume4 = bids[3] + tick.bidPrice5, tick.bidVolume5 = bids[4] + + tick.askPrice1, tick.askVolume1 = asks[0] + tick.askPrice2, tick.askVolume2 = asks[1] + tick.askPrice3, tick.askVolume3 = asks[2] + tick.askPrice4, tick.askVolume4 = asks[3] + tick.askPrice5, tick.askVolume5 = asks[4] + + tick.lastPrice = (tick.askPrice1 + tick.askPrice2) / 2 + + tick.datetime = datetime.fromtimestamp(d['timestamp']/1000) + tick.date = tick.datetime.strftime('%Y%m%d') + tick.time = tick.datetime.strftime('%H:%M:%S') + + self.gateway.onTick(copy(tick)) - #---------------------------------------------------------------------- - def queryPrice(self): - """查询行情""" - for s in SYMBOL_MAP.keys(): - self.getTicker(s) - self.getDepth(s, 5, 0) - - #---------------------------------------------------------------------- - def queryAccount(self): - """查询资金和资产""" - self.getUserInfo() \ No newline at end of file diff --git a/vnpy/trader/gateway/ltsGateway/__init__.py b/vnpy/trader/gateway/ltsGateway/__init__.py index cbb88696..72306493 100644 --- a/vnpy/trader/gateway/ltsGateway/__init__.py +++ b/vnpy/trader/gateway/ltsGateway/__init__.py @@ -1,7 +1,8 @@ # encoding: UTF-8 +from __future__ import absolute_import from vnpy.trader import vtConstant -from ltsGateway import LtsGateway +from .ltsGateway import LtsGateway gatewayClass = LtsGateway gatewayName = 'LTS' diff --git a/vnpy/trader/gateway/ltsGateway/ltsGateway.py b/vnpy/trader/gateway/ltsGateway/ltsGateway.py index 83610ac2..00942059 100644 --- a/vnpy/trader/gateway/ltsGateway/ltsGateway.py +++ b/vnpy/trader/gateway/ltsGateway/ltsGateway.py @@ -3,6 +3,7 @@ ''' vn.lts的gateway接入 ''' +from __future__ import print_function import os import json @@ -980,7 +981,7 @@ class LtsQryApi(QryApi): elif data['ProductClass'] == '8': contract.productClass = PRODUCT_EQUITY else: - print data['ProductClass'] + print(data['ProductClass']) # 期权类型 if data['InstrumentType'] == '1': diff --git a/vnpy/trader/gateway/oandaGateway/OANDA_connect.json b/vnpy/trader/gateway/oandaGateway/OANDA_connect.json deleted file mode 100644 index 21dd2cd9..00000000 --- a/vnpy/trader/gateway/oandaGateway/OANDA_connect.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "token": "请在OANDA网站申请", - "accountId": "请在OANDA网站申请", - "settingName": "practice" -} \ No newline at end of file diff --git a/vnpy/trader/gateway/oandaGateway/__init__.py b/vnpy/trader/gateway/oandaGateway/__init__.py deleted file mode 100644 index 0ce2fb0d..00000000 --- a/vnpy/trader/gateway/oandaGateway/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# encoding: UTF-8 - -from vnpy.trader import vtConstant -from oandaGateway import OandaGateway - -gatewayClass = OandaGateway -gatewayName = 'OANDA' -gatewayDisplayName = gatewayName -gatewayType = vtConstant.GATEWAYTYPE_INTERNATIONAL -gatewayQryEnabled = True diff --git a/vnpy/trader/gateway/oandaGateway/oandaGateway.py b/vnpy/trader/gateway/oandaGateway/oandaGateway.py deleted file mode 100644 index 9623f29d..00000000 --- a/vnpy/trader/gateway/oandaGateway/oandaGateway.py +++ /dev/null @@ -1,458 +0,0 @@ -# encoding: UTF-8 - -''' -vn.oanda的gateway接入 - -由于OANDA采用的是外汇做市商的交易模式,因此和国内接口方面有若干区别,具体如下: - -* 行情数据反映的是OANDA的报价变化,因此只有买卖价,而没有成交价 - -* OANDA的持仓管理分为单笔成交持仓(Trade数据,国内没有) - 和单一资产汇总持仓(Position数据) - -* OANDA系统中的所有时间都采用UTC时间(世界协调时,中国是UTC+8) - -* 由于采用的是外汇做市商的模式,用户的限价委托当价格被触及时就会立即全部成交, - 不会出现部分成交的情况,因此委托状态只有已报、成交、撤销三种 - -* 外汇市场采用24小时交易,因此OANDA的委托不像国内收盘后自动失效,需要用户指定 - 失效时间,本接口中默认设置为24个小时候失效 -''' - - -import os -import json -import datetime - -from vnpy.api.oanda import OandaApi -from vnpy.trader.vtGateway import * -from vnpy.trader.vtFunction import getJsonPath - -# 价格类型映射 -priceTypeMap = {} -priceTypeMap[PRICETYPE_LIMITPRICE] = 'limit' -priceTypeMap[PRICETYPE_MARKETPRICE] = 'market' -priceTypeMapReverse = {v: k for k, v in priceTypeMap.items()} - -# 方向类型映射 -directionMap = {} -directionMap[DIRECTION_LONG] = 'buy' -directionMap[DIRECTION_SHORT] = 'sell' -directionMapReverse = {v: k for k, v in directionMap.items()} - - -######################################################################## -class OandaGateway(VtGateway): - """OANDA接口""" - - #---------------------------------------------------------------------- - def __init__(self, eventEngine, gatewayName='OANDA'): - """Constructor""" - super(OandaGateway, self).__init__(eventEngine, gatewayName) - - self.api = Api(self) - - self.qryEnabled = False # 是否要启动循环查询 - - self.fileName = self.gatewayName + '_connect.json' - self.filePath = getJsonPath(self.fileName, __file__) - - #---------------------------------------------------------------------- - def connect(self): - """连接""" - # 载入json文件 - try: - f = file(self.filePath) - except IOError: - log = VtLogData() - log.gatewayName = self.gatewayName - log.logContent = u'读取连接配置出错,请检查' - self.onLog(log) - return - - # 解析json文件 - setting = json.load(f) - try: - token = str(setting['token']) - accountId = str(setting['accountId']) - settingName = str(setting['settingName']) - except KeyError: - log = VtLogData() - log.gatewayName = self.gatewayName - log.logContent = u'连接配置缺少字段,请检查' - self.onLog(log) - return - - # 初始化接口 - self.api.init(settingName, token, accountId) - log = VtLogData() - log.gatewayName = self.gatewayName - log.logContent = u'接口初始化成功' - self.onLog(log) - - # 查询信息 - self.api.qryInstruments() - self.api.qryOrders() - self.api.qryTrades() - - # 初始化并启动查询 - self.initQuery() - - #---------------------------------------------------------------------- - def subscribe(self, subscribeReq): - """订阅行情""" - pass - - #---------------------------------------------------------------------- - def sendOrder(self, orderReq): - """发单""" - return self.api.sendOrder_(orderReq) - - #---------------------------------------------------------------------- - def cancelOrder(self, cancelOrderReq): - """撤单""" - self.api.cancelOrder_(cancelOrderReq) - - #---------------------------------------------------------------------- - def qryAccount(self): - """查询账户资金""" - self.api.getAccountInfo() - - #---------------------------------------------------------------------- - def qryPosition(self): - """查询持仓""" - self.api.getPositions() - - #---------------------------------------------------------------------- - def close(self): - """关闭""" - self.api.exit() - - #---------------------------------------------------------------------- - def initQuery(self): - """初始化连续查询""" - if self.qryEnabled: - # 需要循环的查询函数列表 - self.qryFunctionList = [self.qryAccount, self.qryPosition] - - self.qryCount = 0 # 查询触发倒计时 - self.qryTrigger = 2 # 查询触发点 - self.qryNextFunction = 0 # 上次运行的查询函数索引 - - self.startQuery() - - #---------------------------------------------------------------------- - def query(self, event): - """注册到事件处理引擎上的查询函数""" - self.qryCount += 1 - - if self.qryCount > self.qryTrigger: - # 清空倒计时 - self.qryCount = 0 - - # 执行查询函数 - function = self.qryFunctionList[self.qryNextFunction] - function() - - # 计算下次查询函数的索引,如果超过了列表长度,则重新设为0 - self.qryNextFunction += 1 - if self.qryNextFunction == len(self.qryFunctionList): - self.qryNextFunction = 0 - - #---------------------------------------------------------------------- - def startQuery(self): - """启动连续查询""" - self.eventEngine.register(EVENT_TIMER, self.query) - - #---------------------------------------------------------------------- - def setQryEnabled(self, qryEnabled): - """设置是否要启动循环查询""" - self.qryEnabled = qryEnabled - - - -######################################################################## -class Api(OandaApi): - """OANDA的API实现""" - - #---------------------------------------------------------------------- - def __init__(self, gateway): - """Constructor""" - super(Api, self).__init__() - - self.gateway = gateway # gateway对象 - self.gatewayName = gateway.gatewayName # gateway对象名称 - - self.orderDict = {} # 缓存委托数据 - - #---------------------------------------------------------------------- - def onError(self, error, reqID): - """错误信息回调""" - err = VtErrorData() - err.gatewayName = self.gatewayName - err.errorMsg = error - self.gateway.onError(err) - - #---------------------------------------------------------------------- - def onGetInstruments(self, data, reqID): - """回调函数""" - if not 'instruments' in data: - return - l = data['instruments'] - for d in l: - contract = VtContractData() - contract.gatewayName = self.gatewayName - - contract.symbol = d['instrument'] - contract.name = d['displayName'] - contract.exchange = EXCHANGE_OANDA - contract.vtSymbol = '.'.join([contract.symbol, contract.exchange]) - contract.priceTick = float(d['pip']) - contract.size = 1 - contract.productClass = PRODUCT_FOREX - self.gateway.onContract(contract) - - self.writeLog(u'交易合约信息查询完成') - - #---------------------------------------------------------------------- - def onGetAccountInfo(self, data, reqID): - """回调函数""" - account = VtAccountData() - account.gatewayName = self.gatewayName - - account.accountID = str(data['accountId']) - account.vtAccountID = '.'.join([self.gatewayName, account.accountID]) - - account.available = data['marginAvail'] - account.margin = data['marginUsed'] - account.closeProfit = data['realizedPl'] - account.positionProfit = data['unrealizedPl'] - account.balance = data['balance'] - - self.gateway.onAccount(account) - - #---------------------------------------------------------------------- - def onGetOrders(self, data, reqID): - """回调函数""" - if not 'orders' in data: - return - l = data['orders'] - - for d in l: - order = VtOrderData() - order.gatewayName = self.gatewayName - - order.symbol = d['instrument'] - order.exchange = EXCHANGE_OANDA - order.vtSymbol = '.'.join([order.symbol, order.exchange]) - order.orderID = str(d['id']) - - order.direction = directionMapReverse.get(d['side'], DIRECTION_UNKNOWN) - order.offset = OFFSET_NONE - order.status = STATUS_NOTTRADED # OANDA查询到的订单都是活动委托 - - order.price = d['price'] - order.totalVolume = d['units'] - order.orderTime = getTime(d['time']) - - order.vtOrderID = '.'.join([self.gatewayName , order.orderID]) - - self.gateway.onOrder(order) - - self.orderDict[order.orderID] = order - - self.writeLog(u'委托信息查询完成') - - #---------------------------------------------------------------------- - def onGetPositions(self, data, reqID): - """回调函数""" - if not 'positions' in data: - return - l = data['positions'] - - for d in l: - pos = VtPositionData() - pos.gatewayName = self.gatewayName - - pos.symbol = d['instrument'] - pos.exchange = EXCHANGE_OANDA - pos.vtSymbol = '.'.join([pos.symbol, pos.exchange]) - pos.direction = directionMapReverse.get(d['side'], DIRECTION_UNKNOWN) - pos.position = d['units'] - pos.price = d['avgPrice'] - pos.vtPositionName = '.'.join([pos.vtSymbol, pos.direction]) - - self.gateway.onPosition(pos) - - #---------------------------------------------------------------------- - def onGetTransactions(self, data, reqID): - """回调函数""" - if not 'transactions' in data: - return - l = data['transactions'] - - for d in l: - # 这里我们只关心委托成交 - if d['type'] == 'ORDER_FILLED': - trade = VtTradeData() - trade.gatewayName = self.gatewayName - - trade.symbol = d['instrument'] - trade.exchange = EXCHANGE_OANDA - trade.vtSymbol = '.'.join([trade.symbol, trade.exchange]) - trade.tradeID = str(d['id']) - trade.vtTradeID = '.'.join([self.gatewayName, trade.tradeID]) - trade.orderID = str(d['orderId']) - trade.vtOrderID = '.'.join([self.gatewayName, trade.orderID]) - trade.direction = directionMapReverse.get(d['side'], DIRECTION_UNKNOWN) - trade.offset = OFFSET_NONE - trade.price = d['price'] - trade.volume = d['units'] - trade.tradeTime = getTime(d['time']) - - self.gateway.onTrade(trade) - - self.writeLog(u'成交信息查询完成') - - #---------------------------------------------------------------------- - def onPrice(self, data): - """行情推送""" - if 'tick' not in data: - return - d = data['tick'] - - tick = VtTickData() - tick.gatewayName = self.gatewayName - - tick.symbol = d['instrument'] - tick.exchange = EXCHANGE_OANDA - tick.vtSymbol = '.'.join([tick.symbol, tick.exchange]) - tick.bidPrice1 = d['bid'] - tick.askPrice1 = d['ask'] - tick.time = getTime(d['time']) + '.0' # 补齐毫秒部分 - tick.date = datetime.datetime.utcnow().strftime('%Y%m%d') # OANDA的时间是UTC标准时 - - # 做市商的TICK数据只有买卖的报价,因此最新价格选用中间价代替 - tick.lastPrice = (tick.bidPrice1 + tick.askPrice1)/2 - - self.gateway.onTick(tick) - - #---------------------------------------------------------------------- - def onEvent(self, data): - """事件推送(成交等)""" - if 'transaction' not in data: - return - - d = data['transaction'] - - # 委托成交 - if d['type'] == 'ORDER_FILLED': - # 推送成交事件 - trade = VtTradeData() - trade.gatewayName = self.gatewayName - - trade.symbol = d['instrument'] - trade.exchange = EXCHANGE_OANDA - trade.vtSymbol = '.'.join([trade.symbol, trade.exchange]) - - trade.tradeID = str(d['id']) - trade.vtTradeID = '.'.join([self.gatewayName, trade.tradeID]) - - trade.orderID = str(d['orderId']) - trade.vtOrderID = '.'.join([self.gatewayName, trade.orderID]) - - trade.direction = directionMapReverse.get(d['side'], DIRECTION_UNKNOWN) - trade.offset = OFFSET_NONE - - trade.price = d['price'] - trade.volume = d['units'] - trade.tradeTime = getTime(d['time']) - - self.gateway.onTrade(trade) - - # 推送委托事件 - order = self.orderDict.get(str(d['orderId']), None) - if not order: - return - order.status = STATUS_ALLTRADED - self.gateway.onOrder(order) - - # 委托下达 - elif d['type'] in ['MARKET_ORDER_CREATE', 'LIMIT_ORDER_CREATE']: - order = VtOrderData() - order.gatewayName = self.gatewayName - - order.symbol = d['instrument'] - order.exchange = EXCHANGE_OANDA - order.vtSymbol = '.'.join([order.symbol, order.exchange]) - order.orderID = str(d['id']) - order.direction = directionMapReverse.get(d['side'], DIRECTION_UNKNOWN) - order.offset = OFFSET_NONE - order.status = STATUS_NOTTRADED - order.price = d['price'] - order.totalVolume = d['units'] - order.orderTime = getTime(d['time']) - order.vtOrderID = '.'.join([self.gatewayName , order.orderID]) - - self.gateway.onOrder(order) - self.orderDict[order.orderID] = order - - # 委托撤销 - elif d['type'] == 'ORDER_CANCEL': - order = self.orderDict.get(str(d['orderId']), None) - if not order: - return - order.status = STATUS_CANCELLED - self.gateway.onOrder(order) - - #---------------------------------------------------------------------- - def writeLog(self, logContent): - """发出日志""" - log = VtLogData() - log.gatewayName = self.gatewayName - log.logContent = logContent - self.gateway.onLog(log) - - #---------------------------------------------------------------------- - def qryInstruments(self): - """查询合约""" - params = {'accountId': self.accountId} - self.getInstruments(params) - - #---------------------------------------------------------------------- - def qryOrders(self): - """查询委托""" - self.getOrders({}) - - #---------------------------------------------------------------------- - def qryTrades(self): - """查询成交""" - # 最多查询100条记录 - self.getTransactions({'count': 100}) - - #---------------------------------------------------------------------- - def sendOrder_(self, orderReq): - """发送委托""" - params = {} - params['instrument'] = orderReq.symbol - params['units'] = orderReq.volume - params['side'] = directionMap.get(orderReq.direction, '') - params['price'] = orderReq.price - params['type'] = priceTypeMap.get(orderReq.priceType, '') - - # 委托有效期24小时 - expire = datetime.datetime.now() + datetime.timedelta(days=1) - params['expiry'] = expire.isoformat('T') + 'Z' - - self.sendOrder(params) - - #---------------------------------------------------------------------- - def cancelOrder_(self, cancelOrderReq): - """撤销委托""" - self.cancelOrder(cancelOrderReq.orderID) - - -#---------------------------------------------------------------------- -def getTime(t): - """把OANDA返回的时间格式转化为简单的时间字符串""" - return t[11:19] \ No newline at end of file diff --git a/vnpy/trader/gateway/okexGateway/OKEX_connect.json b/vnpy/trader/gateway/okexGateway/OKEX_connect.json new file mode 100644 index 00000000..5c77f027 --- /dev/null +++ b/vnpy/trader/gateway/okexGateway/OKEX_connect.json @@ -0,0 +1,6 @@ +{ + "apiKey": "", + "secretKey": "", + "trace": false, + "symbols": ["eth_btc", "btc_usdt", "eth_usdt"] +} \ No newline at end of file diff --git a/beta/gateway/okexGateway/__init__.py b/vnpy/trader/gateway/okexGateway/__init__.py similarity index 76% rename from beta/gateway/okexGateway/__init__.py rename to vnpy/trader/gateway/okexGateway/__init__.py index bafb2366..5f0265a9 100644 --- a/beta/gateway/okexGateway/__init__.py +++ b/vnpy/trader/gateway/okexGateway/__init__.py @@ -2,9 +2,9 @@ from __future__ import absolute_import from vnpy.trader import vtConstant -from .okexGateway import okexGateway +from .okexGateway import OkexGateway -gatewayClass = okexGateway +gatewayClass = OkexGateway gatewayName = 'OKEX' gatewayDisplayName = u'OKEX' gatewayType = vtConstant.GATEWAYTYPE_BTC diff --git a/vnpy/trader/gateway/okexGateway/okexGateway.py b/vnpy/trader/gateway/okexGateway/okexGateway.py new file mode 100644 index 00000000..be924f81 --- /dev/null +++ b/vnpy/trader/gateway/okexGateway/okexGateway.py @@ -0,0 +1,631 @@ +# encoding: UTF-8 + +''' +vnpy.api.okex的gateway接入 + +Contributor:ipqhjjybj 大佳 +''' +from __future__ import print_function + + +import os +import json +from datetime import datetime +from time import sleep +from copy import copy +from threading import Condition +from queue import Queue, Empty +from threading import Thread +from time import sleep + +from vnpy.api.okex import OkexSpotApi, OKEX_SPOT_HOST +from vnpy.trader.vtGateway import * +from vnpy.trader.vtFunction import getJsonPath + +# 价格类型映射 +# 买卖类型: 限价单(buy/sell) 市价单(buy_market/sell_market) +priceTypeMap = {} +priceTypeMap['buy'] = (DIRECTION_LONG, PRICETYPE_LIMITPRICE) +priceTypeMap['buy_market'] = (DIRECTION_LONG, PRICETYPE_MARKETPRICE) +priceTypeMap['sell'] = (DIRECTION_SHORT, PRICETYPE_LIMITPRICE) +priceTypeMap['sell_market'] = (DIRECTION_SHORT, PRICETYPE_MARKETPRICE) +priceTypeMapReverse = {v: k for k, v in priceTypeMap.items()} + +# 委托状态印射 +statusMap = {} +statusMap[-1] = STATUS_CANCELLED +statusMap[0] = STATUS_NOTTRADED +statusMap[1] = STATUS_PARTTRADED +statusMap[2] = STATUS_ALLTRADED +statusMap[4] = STATUS_UNKNOWN + + +######################################################################## +class OkexGateway(VtGateway): + """OKEX交易接口""" + + #---------------------------------------------------------------------- + def __init__(self, eventEngine, gatewayName='OKEX'): + """Constructor""" + super(OkexGateway, self).__init__(eventEngine, gatewayName) + + self.spotApi = SpotApi(self) + # self.api_contract = Api_contract(self) + + self.leverage = 0 + self.connected = False + + self.fileName = self.gatewayName + '_connect.json' + self.filePath = getJsonPath(self.fileName, __file__) + + #---------------------------------------------------------------------- + def connect(self): + """连接""" + # 载入json文件 + try: + f = file(self.filePath) + except IOError: + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = u'读取连接配置出错,请检查' + self.onLog(log) + return + + # 解析json文件 + setting = json.load(f) + try: + apiKey = str(setting['apiKey']) + secretKey = str(setting['secretKey']) + trace = setting['trace'] + symbols = setting['symbols'] + except KeyError: + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = u'连接配置缺少字段,请检查' + self.onLog(log) + return + + # 初始化接口 + self.spotApi.init(apiKey, secretKey, trace, symbols) + + #---------------------------------------------------------------------- + def subscribe(self, subscribeReq): + """订阅行情""" + pass + + #---------------------------------------------------------------------- + def sendOrder(self, orderReq): + """发单""" + return self.spotApi.sendOrder(orderReq) + + #---------------------------------------------------------------------- + def cancelOrder(self, cancelOrderReq): + """撤单""" + self.spotApi.cancelOrder(cancelOrderReq) + + #---------------------------------------------------------------------- + def qryAccount(self): + """查询账户资金""" + pass + + #---------------------------------------------------------------------- + def qryPosition(self): + """查询持仓""" + self.spotApi.spotUserInfo() + + #---------------------------------------------------------------------- + def close(self): + """关闭""" + self.spotApi.close() + + #---------------------------------------------------------------------- + def initQuery(self): + """初始化连续查询""" + if self.qryEnabled: + # 需要循环的查询函数列表 + self.qryFunctionList = [self.qryPosition] + + self.qryCount = 0 # 查询触发倒计时 + self.qryTrigger = 2 # 查询触发点 + self.qryNextFunction = 0 # 上次运行的查询函数索引 + + self.startQuery() + + #---------------------------------------------------------------------- + def query(self, event): + """注册到事件处理引擎上的查询函数""" + self.qryCount += 1 + + if self.qryCount > self.qryTrigger: + # 清空倒计时 + self.qryCount = 0 + + # 执行查询函数 + function = self.qryFunctionList[self.qryNextFunction] + function() + + # 计算下次查询函数的索引,如果超过了列表长度,则重新设为0 + self.qryNextFunction += 1 + if self.qryNextFunction == len(self.qryFunctionList): + self.qryNextFunction = 0 + + #---------------------------------------------------------------------- + def startQuery(self): + """启动连续查询""" + self.eventEngine.register(EVENT_TIMER, self.query) + + #---------------------------------------------------------------------- + def setQryEnabled(self, qryEnabled): + """设置是否要启动循环查询""" + self.qryEnabled = qryEnabled + + +######################################################################## +class SpotApi(OkexSpotApi): + """OKEX的API实现""" + + #---------------------------------------------------------------------- + def __init__(self, gateway): + """Constructor""" + super(SpotApi, self).__init__() + + self.gateway = gateway # gateway对象 + self.gatewayName = gateway.gatewayName # gateway对象名称 + + self.cbDict = {} + self.tickDict = {} + self.orderDict = {} + + self.channelSymbolMap = {} + + self.localNo = 0 # 本地委托号 + self.localNoQueue = Queue() # 未收到系统委托号的本地委托号队列 + self.localNoDict = {} # key为本地委托号,value为系统委托号 + self.localOrderDict = {} # key为本地委托号, value为委托对象 + self.orderIdDict = {} # key为系统委托号,value为本地委托号 + self.cancelDict = {} # key为本地委托号,value为撤单请求 + + self.recordOrderId_BefVolume = {} # 记录的之前处理的量 + + self.cache_some_order = {} + self.tradeID = 0 + + self.registerSymbolPairArray = set([]) + + #---------------------------------------------------------------------- + def onMessage(self, data): + """信息推送""" + channel = data.get('channel', '') + if not channel: + return + + if channel in self.cbDict: + callback = self.cbDict[channel] + callback(data) + + #---------------------------------------------------------------------- + def onError(self, data): + """错误推送""" + error = VtErrorData() + error.gatewayName = self.gatewayName + error.errorMsg = str(data) + self.gateway.onError(error) + + #---------------------------------------------------------------------- + def onClose(self): + """接口断开""" + self.gateway.connected = False + self.writeLog(u'服务器连接断开') + + #---------------------------------------------------------------------- + def onOpen(self): + """连接成功""" + self.gateway.connected = True + self.writeLog(u'服务器连接成功') + + self.login() + + # 推送合约数据 + for symbol in self.symbols: + contract = VtContractData() + contract.gatewayName = self.gatewayName + contract.symbol = symbol + contract.exchange = EXCHANGE_OKEX + contract.vtSymbol = '.'.join([contract.symbol, contract.exchange]) + contract.name = symbol + contract.size = 0.00001 + contract.priceTick = 0.00001 + contract.productClass = PRODUCT_SPOT + self.gateway.onContract(contract) + + #---------------------------------------------------------------------- + def initCallback(self): + """初始化回调函数""" + for symbol in self.symbols: + # channel和symbol映射 + self.channelSymbolMap["ok_sub_spot_%s_ticker" % symbol] = symbol + self.channelSymbolMap["ok_sub_spot_%s_depth_5" % symbol] = symbol + + # channel和callback映射 + self.cbDict["ok_sub_spot_%s_ticker" % symbol] = self.onTicker + self.cbDict["ok_sub_spot_%s_depth_5" % symbol] = self.onDepth + self.cbDict["ok_sub_spot_%s_order" % symbol] = self.onSubSpotOrder + self.cbDict["ok_sub_spot_%s_balance" % symbol] = self.onSubSpotBalance + + self.cbDict['ok_spot_userinfo'] = self.onSpotUserInfo + self.cbDict['ok_spot_orderinfo'] = self.onSpotOrderInfo + self.cbDict['ok_spot_order'] = self.onSpotOrder + self.cbDict['ok_spot_cancel_order'] = self.onSpotCancelOrder + self.cbDict['login'] = self.onLogin + + #---------------------------------------------------------------------- + def onLogin(self, data): + """""" + self.writeLog(u'服务器登录成功') + + # 查询持仓 + self.spotUserInfo() + + # 订阅推送 + for symbol in self.symbols: + self.subscribe(symbol) + + #---------------------------------------------------------------------- + def onTicker(self, data): + """""" + channel = data['channel'] + symbol = self.channelSymbolMap[channel] + + if symbol not in self.tickDict: + tick = VtTickData() + tick.symbol = symbol + tick.exchange = EXCHANGE_OKEX + tick.vtSymbol = '.'.join([tick.symbol, tick.exchange]) + tick.gatewayName = self.gatewayName + + self.tickDict[symbol] = tick + else: + tick = self.tickDict[symbol] + + d = data['data'] + tick.highPrice = float(d['high']) + tick.lowPrice = float(d['low']) + tick.lastPrice = float(d['last']) + tick.volume = float(d['vol'].replace(',', '')) + tick.date, tick.time = self.generateDateTime(d['timestamp']) + + if tick.bidPrice1: + newtick = copy(tick) + self.gateway.onTick(newtick) + + #---------------------------------------------------------------------- + def onDepth(self, data): + """""" + channel = data['channel'] + symbol = self.channelSymbolMap[channel] + + if symbol not in self.tickDict: + tick = VtTickData() + tick.symbol = symbol + tick.exchange = EXCHANGE_OKEX + tick.vtSymbol = '.'.join([tick.symbol, tick.exchange]) + tick.gatewayName = self.gatewayName + + self.tickDict[symbol] = tick + else: + tick = self.tickDict[symbol] + + d = data['data'] + + tick.bidPrice1, tick.bidVolume1 = d['bids'][0] + tick.bidPrice2, tick.bidVolume2 = d['bids'][1] + tick.bidPrice3, tick.bidVolume3 = d['bids'][2] + tick.bidPrice4, tick.bidVolume4 = d['bids'][3] + tick.bidPrice5, tick.bidVolume5 = d['bids'][4] + + tick.askPrice1, tick.askVolume1 = d['asks'][-1] + tick.askPrice2, tick.askVolume2 = d['asks'][-2] + tick.askPrice3, tick.askVolume3 = d['asks'][-3] + tick.askPrice4, tick.askVolume4 = d['asks'][-4] + tick.askPrice5, tick.askVolume5 = d['asks'][-5] + + tick.bidPrice1 = float(tick.bidPrice1) + tick.bidPrice2 = float(tick.bidPrice2) + tick.bidPrice3 = float(tick.bidPrice3) + tick.bidPrice4 = float(tick.bidPrice4) + tick.bidPrice5 = float(tick.bidPrice5) + tick.askPrice1 = float(tick.askPrice1) + tick.askPrice2 = float(tick.askPrice2) + tick.askPrice3 = float(tick.askPrice3) + tick.askPrice4 = float(tick.askPrice4) + tick.askPrice5 = float(tick.askPrice5) + + tick.bidVolume1 = float(tick.bidVolume1) + tick.bidVolume2 = float(tick.bidVolume2) + tick.bidVolume3 = float(tick.bidVolume3) + tick.bidVolume4 = float(tick.bidVolume4) + tick.bidVolume5 = float(tick.bidVolume5) + tick.askVolume1 = float(tick.askVolume1) + tick.askVolume2 = float(tick.askVolume2) + tick.askVolume3 = float(tick.askVolume3) + tick.askVolume4 = float(tick.askVolume4) + tick.askVolume5 = float(tick.askVolume5) + + tick.date, tick.time = self.generateDateTime(d['timestamp']) + + if tick.lastPrice: + newtick = copy(tick) + self.gateway.onTick(newtick) + + #---------------------------------------------------------------------- + def onSpotOrder(self, data): + """""" + # 如果委托失败,则通知委托被拒单的信息 + if self.checkDataError(data): + try: + localNo = self.localNoQueue.get_nowait() + except Empty: + return + + order = self.localOrderDict[localNo] + order.status = STATUS_REJECTED + self.gateway.onOrder(order) + + #---------------------------------------------------------------------- + def onSpotCancelOrder(self, data): + """""" + self.checkDataError(data) + + #---------------------------------------------------------------------- + def onSpotUserInfo(self, data): + """现货账户资金推送""" + if self.checkDataError(data): + return + + funds = data['data']['info']['funds'] + free = funds['free'] + freezed = funds['freezed'] + + # 持仓信息 + for symbol in free.keys(): + frozen = float(freezed[symbol]) + available = float(free[symbol]) + + if frozen or available: + account = VtAccountData() + account.gatewayName = self.gatewayName + + account.accountID = symbol + account.vtAccountID = '.'.join([account.gatewayName, account.accountID]) + account.balance = frozen + available + account.available = available + + self.gateway.onAccount(account) + + self.writeLog(u'持仓信息查询成功') + + # 查询委托 + for symbol in self.symbols: + self.spotOrderInfo(symbol, '-1') + + #---------------------------------------------------------------------- + def onSpotOrderInfo(self, data): + """委托信息查询回调""" + if self.checkDataError(data): + return + + rawData = data['data'] + + for d in rawData['orders']: + self.localNo += 1 + localNo = str(self.localNo) + orderId = str(d['order_id']) + + self.localNoDict[localNo] = orderId + self.orderIdDict[orderId] = localNo + + if orderId not in self.orderDict: + order = VtOrderData() + order.gatewayName = self.gatewayName + + order.symbol = d['symbol'] + order.exchange = EXCHANGE_OKEX + order.vtSymbol = '.'.join([order.symbol, order.exchange]) + + order.orderID = localNo + order.vtOrderID = '.'.join([self.gatewayName, order.orderID]) + + order.price = d['price'] + order.totalVolume = d['amount'] + order.direction, priceType = priceTypeMap[d['type']] + date, order.orderTime = self.generateDateTime(d['create_date']) + + self.orderDict[orderId] = order + else: + order = self.orderDict[orderId] + + order.tradedVolume = d['deal_amount'] + order.status = statusMap[d['status']] + + self.gateway.onOrder(copy(order)) + + #---------------------------------------------------------------------- + def onSubSpotOrder(self, data): + """交易数据""" + rawData = data["data"] + orderId = str(rawData['orderId']) + + # 获取本地委托号 + if orderId in self.orderIdDict: + localNo = self.orderIdDict[orderId] + else: + try: + localNo = self.localNoQueue.get_nowait() + except Empty: + self.localNo += 1 + localNo = str(self.localNo) + + self.localNoDict[localNo] = orderId + self.orderIdDict[orderId] = localNo + + # 获取委托对象 + if orderId in self.orderDict: + order = self.orderDict[orderId] + else: + order = VtOrderData() + order.gatewayName = self.gatewayName + order.symbol = rawData['symbol'] + order.exchange = EXCHANGE_OKEX + order.vtSymbol = '.'.join([order.symbol, order.exchange]) + order.orderID = localNo + order.vtOrderID = '.'.join([self.gatewayName, localNo]) + order.direction, priceType = priceTypeMap[rawData['tradeType']] + order.price = float(rawData['tradeUnitPrice']) + order.totalVolume = float(rawData['tradeAmount']) + date, order.orderTime = self.generateDateTime(rawData['createdDate']) + + lastTradedVolume = order.tradedVolume + + order.status = statusMap[rawData['status']] + order.tradedVolume = float(rawData['completedTradeAmount']) + self.gateway.onOrder(copy(order)) + + # 成交信息 + if order.tradedVolume > lastTradedVolume: + trade = VtTradeData() + trade.gatewayName = self.gatewayName + + trade.symbol = order.symbol + trade.exchange = order.exchange + trade.vtSymbol = order.vtSymbol + + self.tradeID += 1 + trade.tradeID = str(self.tradeID) + trade.vtTradeID = '.'.join([self.gatewayName, trade.tradeID]) + + trade.orderID = order.orderID + trade.vtOrderID = order.vtOrderID + + trade.direction = order.direction + trade.price = float(rawData['averagePrice']) + trade.volume = order.tradedVolume - lastTradedVolume + + trade.tradeTime = datetime.now().strftime('%H:%M:%S') + self.gateway.onTrade(trade) + + # 撤单 + if localNo in self.cancelDict: + req = self.cancelDict[localNo] + self.spotCancel(req) + del self.cancelDict[localNo] + + #---------------------------------------------------------------------- + def onSubSpotBalance(self, data): + """""" + rawData = data['data'] + free = rawData['info']['free'] + freezed = rawData['info']['freezed'] + + for symbol in free.keys(): + account = VtAccountData() + account.gatewayName = self.gatewayName + + account.accountID = symbol + account.vtAccountID = '.'.join([account.gatewayName, account.accountID]) + account.available = float(free[symbol]) + account.balance = account.available + float(freezed[symbol]) + + self.gateway.onAccount(account) + + #---------------------------------------------------------------------- + def init(self, apiKey, secretKey, trace, symbols): + """初始化接口""" + self.symbols = symbols + self.initCallback() + self.connect(OKEX_SPOT_HOST, apiKey, secretKey, trace) + self.writeLog(u'接口初始化成功') + + #---------------------------------------------------------------------- + def sendOrder(self, req): + """发单""" + type_ = priceTypeMapReverse[(req.direction, req.priceType)] + result = self.spotOrder(req.symbol, type_, str(req.price), str(req.volume)) + + # 若请求失败,则返回空字符串委托号 + if not result: + return '' + + # 本地委托号加1,并将对应字符串保存到队列中,返回基于本地委托号的vtOrderID + self.localNo += 1 + self.localNoQueue.put(str(self.localNo)) + vtOrderID = '.'.join([self.gatewayName, str(self.localNo)]) + + # 缓存委托信息 + order = VtOrderData() + order.gatewayName = self.gatewayName + + order.symbol = req.symbol + order.exchange = EXCHANGE_OKEX + order.vtSymbol = '.'.join([order.symbol, order.exchange]) + order.orderID= str(self.localNo) + order.vtOrderID = vtOrderID + order.direction = req.direction + order.price = req.price + order.totalVolume = req.volume + + self.localOrderDict[str(self.localNo)] = order + + return vtOrderID + + #---------------------------------------------------------------------- + def cancelOrder(self, req): + """撤单""" + localNo = req.orderID + if localNo in self.localNoDict: + orderID = self.localNoDict[localNo] + self.spotCancelOrder(req.symbol, orderID) + else: + # 如果在系统委托号返回前客户就发送了撤单请求,则保存 + # 在cancelDict字典中,等待返回后执行撤单任务 + self.cancelDict[localNo] = req + + #---------------------------------------------------------------------- + def generateDateTime(self, s): + """生成时间""" + dt = datetime.fromtimestamp(float(s)/1e3) + time = dt.strftime("%H:%M:%S.%f") + date = dt.strftime("%Y%m%d") + return date, time + + #---------------------------------------------------------------------- + def writeLog(self, content): + """快速记录日志""" + log = VtLogData() + log.gatewayName = self.gatewayName + log.logContent = content + self.gateway.onLog(log) + + #---------------------------------------------------------------------- + def checkDataError(self, data): + """检查回报是否存在错误""" + rawData = data['data'] + if 'error_code' not in rawData: + return False + else: + error = VtErrorData() + error.gatewayName = self.gatewayName + error.errorID = rawData['error_code'] + error.errorMsg = u'请求失败,功能:%s' %data['channel'] + self.gateway.onError(error) + return True + + #---------------------------------------------------------------------- + def subscribe(self, symbol): + """订阅行情""" + symbol = symbol + + self.subscribeSpotTicker(symbol) + self.subscribeSpotDepth(symbol, 5) + self.subSpotOrder(symbol) + self.subSpotBalance(symbol) + \ No newline at end of file diff --git a/vnpy/trader/gateway/qdpGateway/__init__.py b/vnpy/trader/gateway/qdpGateway/__init__.py index 4fb85e9b..2c8b6685 100644 --- a/vnpy/trader/gateway/qdpGateway/__init__.py +++ b/vnpy/trader/gateway/qdpGateway/__init__.py @@ -1,7 +1,8 @@ # encoding: UTF-8 +from __future__ import absolute_import from vnpy.trader import vtConstant -from qdpGateway import QdpGateway +from .qdpGateway import QdpGateway gatewayClass = QdpGateway gatewayName = 'QDP' diff --git a/vnpy/trader/gateway/secGateway/__init__.py b/vnpy/trader/gateway/secGateway/__init__.py index c919bb29..ba50b638 100644 --- a/vnpy/trader/gateway/secGateway/__init__.py +++ b/vnpy/trader/gateway/secGateway/__init__.py @@ -1,7 +1,8 @@ # encoding: UTF-8 +from __future__ import absolute_import from vnpy.trader import vtConstant -from secGateway import SecGateway +from .secGateway import SecGateway gatewayClass = SecGateway gatewayName = 'SEC' diff --git a/vnpy/trader/gateway/secGateway/secGateway.py b/vnpy/trader/gateway/secGateway/secGateway.py index 095341b8..a5ff3d01 100644 --- a/vnpy/trader/gateway/secGateway/secGateway.py +++ b/vnpy/trader/gateway/secGateway/secGateway.py @@ -3,6 +3,7 @@ ''' vn.sec的gateway接入 ''' +from __future__ import print_function import os import json @@ -50,11 +51,11 @@ exchangeMapReverse = {v:k for k,v in exchangeMap.items()} #---------------------------------------------------------------------- def print_dict(d): """""" - print '-' * 30 + print('-' * 30) l = d.keys() l.sort() for k in l: - print '%s:%s' %(k, d[k]) + print('%s:%s' %(k, d[k])) ######################################################################## diff --git a/vnpy/trader/gateway/sgitGateway/__init__.py b/vnpy/trader/gateway/sgitGateway/__init__.py index 2be6983b..52c55412 100644 --- a/vnpy/trader/gateway/sgitGateway/__init__.py +++ b/vnpy/trader/gateway/sgitGateway/__init__.py @@ -1,7 +1,8 @@ # encoding: UTF-8 +from __future__ import absolute_import from vnpy.trader import vtConstant -from sgitGateway import SgitGateway +from .sgitGateway import SgitGateway gatewayClass = SgitGateway gatewayName = 'SGIT' diff --git a/vnpy/trader/gateway/shzdGateway/__init__.py b/vnpy/trader/gateway/shzdGateway/__init__.py index 7f5d3497..fd10bb58 100644 --- a/vnpy/trader/gateway/shzdGateway/__init__.py +++ b/vnpy/trader/gateway/shzdGateway/__init__.py @@ -1,7 +1,8 @@ # encoding: UTF-8 +from __future__ import absolute_import from vnpy.trader import vtConstant -from shzdGateway import ShzdGateway +from .shzdGateway import ShzdGateway gatewayClass = ShzdGateway gatewayName = 'SHZD' diff --git a/vnpy/trader/gateway/shzdGateway/shzdGateway.py b/vnpy/trader/gateway/shzdGateway/shzdGateway.py index 3ed9f305..d49ba6b0 100644 --- a/vnpy/trader/gateway/shzdGateway/shzdGateway.py +++ b/vnpy/trader/gateway/shzdGateway/shzdGateway.py @@ -8,6 +8,7 @@ vn.shzd的gateway接入 3. 持仓全部平光后,再次查询时会没有该合约的推送(和CTP不同),为了避免最后平仓 不更新的情况,使用缓存机制来处理 ''' +from __future__ import print_function import os @@ -721,9 +722,9 @@ class ShzdGatewayApi(ShzdApi): #---------------------------------------------------------------------- def printDict(d): """打印字典""" - print '-' * 50 + print('-' * 50) l = d.keys() l.sort() for k in l: - print k, ':', d[k] + print(k, ':', d[k]) \ No newline at end of file diff --git a/vnpy/trader/gateway/tkproGateway/TKPRO_connect.json b/vnpy/trader/gateway/tkproGateway/TKPRO_connect.json deleted file mode 100644 index 38b5a354..00000000 --- a/vnpy/trader/gateway/tkproGateway/TKPRO_connect.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "username": "请在quantos.org申请", - "token": "请在quantos.org申请", - "strategy": 625, - "tradeAddress": "tcp://gw.quantos.org:8901", - "dataAddress": "tcp://data.tushare.org:8910" -} \ No newline at end of file diff --git a/vnpy/trader/gateway/windGateway/__init__.py b/vnpy/trader/gateway/windGateway/__init__.py index f2508aee..fc9949b4 100644 --- a/vnpy/trader/gateway/windGateway/__init__.py +++ b/vnpy/trader/gateway/windGateway/__init__.py @@ -1,7 +1,8 @@ # encoding: UTF-8 +from __future__ import absolute_import from vnpy.trader import vtConstant -from windGateway import WindGateway +from .windGateway import WindGateway gatewayClass = WindGateway gatewayName = 'WIND' diff --git a/vnpy/trader/gateway/windGateway/windGateway.py b/vnpy/trader/gateway/windGateway/windGateway.py index 1a872cdf..e244b6c5 100644 --- a/vnpy/trader/gateway/windGateway/windGateway.py +++ b/vnpy/trader/gateway/windGateway/windGateway.py @@ -3,6 +3,7 @@ ''' Wind Python API的gateway接入 ''' +from __future__ import print_function from copy import copy @@ -11,7 +12,7 @@ w = None try: from WindPy import w except ImportError: - print u'请先安装WindPy接口' + print(u'请先安装WindPy接口') from vnpy.trader.vtGateway import * diff --git a/vnpy/trader/gateway/xspeedGateway/__init__.py b/vnpy/trader/gateway/xspeedGateway/__init__.py index 50d26714..ee1f848b 100644 --- a/vnpy/trader/gateway/xspeedGateway/__init__.py +++ b/vnpy/trader/gateway/xspeedGateway/__init__.py @@ -1,7 +1,8 @@ # encoding: UTF-8 +from __future__ import absolute_import from vnpy.trader import vtConstant -from xspeedGateway import XspeedGateway +from .xspeedGateway import XspeedGateway gatewayClass = XspeedGateway gatewayName = 'XSPEED' diff --git a/vnpy/trader/gateway/xtpGateway/__init__.py b/vnpy/trader/gateway/xtpGateway/__init__.py index bd4dfe6d..b9ea0af4 100644 --- a/vnpy/trader/gateway/xtpGateway/__init__.py +++ b/vnpy/trader/gateway/xtpGateway/__init__.py @@ -1,7 +1,8 @@ # encoding: UTF-8 +from __future__ import absolute_import from vnpy.trader import vtConstant -from xtpGateway import XtpGateway +from .xtpGateway import XtpGateway gatewayClass = XtpGateway gatewayName = 'XTP' diff --git a/vnpy/trader/language/chinese/constant.py b/vnpy/trader/language/chinese/constant.py index ef49b3dd..08743346 100644 --- a/vnpy/trader/language/chinese/constant.py +++ b/vnpy/trader/language/chinese/constant.py @@ -80,17 +80,20 @@ EXCHANGE_CME = 'CME' # CME交易所 EXCHANGE_ICE = 'ICE' # ICE交易所 EXCHANGE_LME = 'LME' # LME交易所 -EXCHANGE_OANDA = 'OANDA' # OANDA外汇做市商 EXCHANGE_FXCM = 'FXCM' # FXCM外汇做市商 EXCHANGE_OKCOIN = 'OKCOIN' # OKCOIN比特币交易所 EXCHANGE_HUOBI = 'HUOBI' # 火币比特币交易所 EXCHANGE_LBANK = 'LBANK' # LBANK比特币交易所 -EXCHANGE_KORBIT = 'KORBIT' # KORBIT韩国交易所 EXCHANGE_ZB = 'ZB' # 比特币中国比特币交易所 EXCHANGE_OKEX = 'OKEX' # OKEX比特币交易所 -EXCHANGE_ZAIF = "ZAIF" # ZAIF日本比特币交易所 -EXCHANGE_COINCHECK = "COINCHECK" # COINCHECK日本比特币交易所 +EXCHANGE_BINANCE = "BINANCE" # 币安比特币交易所 +EXCHANGE_BITFINEX = "BITFINEX" # Bitfinex比特币交易所 +EXCHANGE_BITMEX = 'BITMEX' # BitMEX比特币交易所 +EXCHANGE_FCOIN = 'FCOIN' # FCoin比特币交易所 +EXCHANGE_BIGONE = 'BIGONE' # BigOne比特币交易所 +EXCHANGE_COINBASE = 'COINBASE' # Coinbase交易所 +EXCHANGE_BITHUMB = 'BITHUMB' # Bithumb比特币交易所 # 货币类型 CURRENCY_USD = 'USD' # 美元 diff --git a/vnpy/trader/language/chinese/text.py b/vnpy/trader/language/chinese/text.py index 61850491..f9729083 100644 --- a/vnpy/trader/language/chinese/text.py +++ b/vnpy/trader/language/chinese/text.py @@ -137,3 +137,4 @@ DATABASE_CONNECTING_FAILED = u'MongoDB连接失败' DATA_INSERT_FAILED = u'数据插入失败,MongoDB没有连接' DATA_QUERY_FAILED = u'数据查询失败,MongoDB没有连接' DATA_UPDATE_FAILED = u'数据更新失败,MongoDB没有连接' +DATA_DELETE_FAILED = u'数据删除失败,MongoDB没有连接' \ No newline at end of file diff --git a/vnpy/trader/language/english/constant.py b/vnpy/trader/language/english/constant.py index 276cabee..b5caeabe 100644 --- a/vnpy/trader/language/english/constant.py +++ b/vnpy/trader/language/english/constant.py @@ -76,17 +76,20 @@ EXCHANGE_CME = 'CME' # CME交易所 EXCHANGE_ICE = 'ICE' # ICE交易所 EXCHANGE_LME = 'LME' # LME交易所 -EXCHANGE_OANDA = 'OANDA' # OANDA外汇做市商 EXCHANGE_FXCM = 'FXCM' # FXCM外汇做市商 EXCHANGE_OKCOIN = 'OKCOIN' # OKCOIN比特币交易所 EXCHANGE_HUOBI = 'HUOBI' # 火币比特币交易所 EXCHANGE_LBANK = 'LBANK' # LBANK比特币交易所 -EXCHANGE_KORBIT = 'KORBIT' # KORBIT韩国交易所 EXCHANGE_ZB = 'ZB' # 比特币中国比特币交易所 EXCHANGE_OKEX = 'OKEX' # OKEX比特币交易所 -EXCHANGE_ZAIF = "ZAIF" # ZAIF日本比特币交易所 -EXCHANGE_COINCHECK = "COINCHECK" # COINCHECK日本比特币交易所 +EXCHANGE_BINANCE = "BINANCE" # 币安比特币交易所 +EXCHANGE_BITFINEX = "BITFINEX" # Bitfinex比特币交易所 +EXCHANGE_BITMEX = 'BITMEX' # BitMEX比特币交易所 +EXCHANGE_FCOIN = 'FCOIN' # FCoin比特币交易所 +EXCHANGE_BIGONE = 'BIGONE' # BigOne比特币交易所 +EXCHANGE_COINBASE = 'COINBASE' # Coinbase交易所 +EXCHANGE_BITHUMB = 'BITHUMB' # Bithumb比特币交易所 # 货币类型 CURRENCY_USD = 'USD' # 美元 diff --git a/vnpy/trader/language/english/text.py b/vnpy/trader/language/english/text.py index 6268d9c8..68ed2985 100644 --- a/vnpy/trader/language/english/text.py +++ b/vnpy/trader/language/english/text.py @@ -137,4 +137,5 @@ DATABASE_CONNECTING_COMPLETED = u'MongoDB is connected.' DATABASE_CONNECTING_FAILED = u'Failed to connect to MongoDB.' DATA_INSERT_FAILED = u'Data insert failed,please connect MongoDB first.' DATA_QUERY_FAILED = u'Data query failed, please connect MongoDB first.' -DATA_UPDATE_FAILED = u'Data update failed, please connect MongoDB first.' \ No newline at end of file +DATA_UPDATE_FAILED = u'Data update failed, please connect MongoDB first.' +DATA_DELETE_FAILED = u'Data delete failed, please connect MongoDB first.' \ No newline at end of file diff --git a/vnpy/trader/uiBasicWidget.py b/vnpy/trader/uiBasicWidget.py index 6ef2dcee..282e7762 100644 --- a/vnpy/trader/uiBasicWidget.py +++ b/vnpy/trader/uiBasicWidget.py @@ -6,6 +6,8 @@ import os import platform from collections import OrderedDict +from six import text_type + from vnpy.event import * from .vtEvent import * from .vtFunction import * @@ -375,7 +377,7 @@ class BasicMonitor(QtWidgets.QTableWidget): self.menu.close() # 获取想要保存的文件名 - path = QtWidgets.QFileDialog.getSaveFileName(self, vtText.SAVE_DATA, '', 'CSV(*.csv)') + path, fileType = QtWidgets.QFileDialog.getSaveFileName(self, vtText.SAVE_DATA, '', 'CSV(*.csv)') try: #if not path.isEmpty(): @@ -394,7 +396,7 @@ class BasicMonitor(QtWidgets.QTableWidget): item = self.item(row, column) if item is not None: rowdata.append( - unicode(item.text()).encode('gbk')) + text_type(item.text()).encode('gbk')) else: rowdata.append('') writer.writerow(rowdata) @@ -727,7 +729,7 @@ class TradingWidget(QtWidgets.QFrame): def initUi(self): """初始化界面""" self.setWindowTitle(vtText.TRADING) - self.setMaximumWidth(400) + self.setFixedWidth(500) self.setFrameShape(self.Box) # 设置边框 self.setLineWidth(1) @@ -754,14 +756,14 @@ class TradingWidget(QtWidgets.QFrame): self.comboOffset = QtWidgets.QComboBox() self.comboOffset.addItems(self.offsetList) - self.spinPrice = QtWidgets.QDoubleSpinBox() - self.spinPrice.setDecimals(globalSetting.get('maxDecimal', 4)) - self.spinPrice.setMinimum(0) - self.spinPrice.setMaximum(1000000) + validator = QtGui.QDoubleValidator() + validator.setBottom(0) - self.spinVolume = QtWidgets.QSpinBox() - self.spinVolume.setMinimum(0) - self.spinVolume.setMaximum(1000000) + self.linePrice = QtWidgets.QLineEdit() + self.linePrice.setValidator(validator) + + self.lineVolume = QtWidgets.QLineEdit() + self.lineVolume.setValidator(validator) self.comboPriceType = QtWidgets.QComboBox() self.comboPriceType.addItems(self.priceTypeList) @@ -796,8 +798,8 @@ class TradingWidget(QtWidgets.QFrame): gridleft.addWidget(self.comboDirection, 2, 1, 1, -1) gridleft.addWidget(self.comboOffset, 3, 1, 1, -1) gridleft.addWidget(self.checkFixed, 4, 1) - gridleft.addWidget(self.spinPrice, 4, 2) - gridleft.addWidget(self.spinVolume, 5, 1, 1, -1) + gridleft.addWidget(self.linePrice, 4, 2) + gridleft.addWidget(self.lineVolume, 5, 1, 1, -1) gridleft.addWidget(self.comboPriceType, 6, 1, 1, -1) gridleft.addWidget(self.comboExchange, 7, 1, 1, -1) gridleft.addWidget(self.comboCurrency, 8, 1, 1, -1) @@ -882,6 +884,8 @@ class TradingWidget(QtWidgets.QFrame): gridRight.addWidget(self.labelBidVolume3, 8, 2) gridRight.addWidget(self.labelBidVolume4, 9, 2) gridRight.addWidget(self.labelBidVolume5, 10, 2) + + self.labelBidVolume5.setFixedWidth(100) # 发单按钮 buttonSendOrder = QtWidgets.QPushButton(vtText.SEND_ORDER) @@ -914,10 +918,10 @@ class TradingWidget(QtWidgets.QFrame): """合约变化""" # 读取组件数据 symbol = str(self.lineSymbol.text()) - exchange = unicode(self.comboExchange.currentText()) - currency = unicode(self.comboCurrency.currentText()) - productClass = unicode(self.comboProductClass.currentText()) - gatewayName = unicode(self.comboGateway.currentText()) + exchange = text_type(self.comboExchange.currentText()) + currency = text_type(self.comboCurrency.currentText()) + productClass = text_type(self.comboProductClass.currentText()) + gatewayName = text_type(self.comboGateway.currentText()) # 查询合约 if exchange: @@ -934,8 +938,8 @@ class TradingWidget(QtWidgets.QFrame): exchange = contract.exchange # 保证有交易所代码 # 清空价格数量 - self.spinPrice.setValue(0) - self.spinVolume.setValue(0) + self.linePrice.clear() + self.lineVolume.clear() # 清空行情显示 self.labelBidPrice1.setText('') @@ -984,7 +988,7 @@ class TradingWidget(QtWidgets.QFrame): return if not self.checkFixed.isChecked(): - self.spinPrice.setValue(tick.lastPrice) + self.linePrice.setText(str(tick.lastPrice)) self.labelBidPrice1.setText(str(tick.bidPrice1)) self.labelAskPrice1.setText(str(tick.askPrice1)) @@ -1031,10 +1035,10 @@ class TradingWidget(QtWidgets.QFrame): """发单""" symbol = str(self.lineSymbol.text()) vtSymbol = symbol - exchange = unicode(self.comboExchange.currentText()) - currency = unicode(self.comboCurrency.currentText()) - productClass = unicode(self.comboProductClass.currentText()) - gatewayName = unicode(self.comboGateway.currentText()) + exchange = text_type(self.comboExchange.currentText()) + currency = text_type(self.comboCurrency.currentText()) + productClass = text_type(self.comboProductClass.currentText()) + gatewayName = text_type(self.comboGateway.currentText()) # 查询合约 if exchange: @@ -1048,16 +1052,33 @@ class TradingWidget(QtWidgets.QFrame): gatewayName = contract.gatewayName exchange = contract.exchange # 保证有交易所代码 vtSymbol = contract.vtSymbol - + + # 获取价格 + priceText = self.linePrice.text() + if not priceText: + return + price = float(priceText) + + # 获取数量 + volumeText = self.lineVolume.text() + if not volumeText: + return + + if '.' in volumeText: + volume = float(volumeText) + else: + volume = int(volumeText) + + # 委托 req = VtOrderReq() req.symbol = symbol req.exchange = exchange req.vtSymbol = vtSymbol - req.price = self.spinPrice.value() - req.volume = self.spinVolume.value() - req.direction = unicode(self.comboDirection.currentText()) - req.priceType = unicode(self.comboPriceType.currentText()) - req.offset = unicode(self.comboOffset.currentText()) + req.price = price + req.volume = volume + req.direction = text_type(self.comboDirection.currentText()) + req.priceType = text_type(self.comboPriceType.currentText()) + req.offset = text_type(self.comboOffset.currentText()) req.currency = currency req.productClass = productClass @@ -1090,7 +1111,7 @@ class TradingWidget(QtWidgets.QFrame): # 自动填写信息 self.comboPriceType.setCurrentIndex(self.priceTypeList.index(PRICETYPE_LIMITPRICE)) self.comboOffset.setCurrentIndex(self.offsetList.index(OFFSET_CLOSE)) - self.spinVolume.setValue(pos.position) + self.lineVolume.setText(str(pos.position)) if pos.direction == DIRECTION_LONG or pos.direction == DIRECTION_NET: self.comboDirection.setCurrentIndex(self.directionList.index(DIRECTION_SHORT)) @@ -1144,7 +1165,7 @@ class ContractMonitor(BasicMonitor): """显示所有合约数据""" l = self.mainEngine.getAllContracts() d = {'.'.join([contract.exchange, contract.symbol]):contract for contract in l} - l2 = d.keys() + l2 = list(d.keys()) l2.sort(reverse=True) self.setRowCount(len(l2)) diff --git a/vnpy/trader/uiQt.py b/vnpy/trader/uiQt.py index c31e2bce..6001457e 100644 --- a/vnpy/trader/uiQt.py +++ b/vnpy/trader/uiQt.py @@ -30,7 +30,7 @@ def createQApp(): if globalSetting['darkStyle']: try: import qdarkstyle - qApp.setStyleSheet(qdarkstyle.load_stylesheet(pyside=False)) + qApp.setStyleSheet(qdarkstyle.load_stylesheet_from_environment()) except ImportError: pass diff --git a/vnpy/trader/vtEngine.py b/vnpy/trader/vtEngine.py index cfd1c5f1..e86d2c8f 100644 --- a/vnpy/trader/vtEngine.py +++ b/vnpy/trader/vtEngine.py @@ -251,7 +251,17 @@ class MainEngine(object): collection = db[collectionName] collection.replace_one(flt, d, upsert) else: - self.writeLog(text.DATA_UPDATE_FAILED) + self.writeLog(text.DATA_UPDATE_FAILED) + + #---------------------------------------------------------------------- + def dbDelete(self, dbName, collectionName, flt): + """从数据库中删除数据,flt是过滤条件""" + if self.dbClient: + db = self.dbClient[dbName] + collection = db[collectionName] + collection.delete_one(flt) + else: + self.writeLog(text.DATA_DELETE_FAILED) #---------------------------------------------------------------------- def dbLogging(self, event): @@ -735,7 +745,7 @@ class LogEngine(object): function = self.levelFunctionDict[log.logLevel] # 获取日志级别对应的处理函数 msg = '\t'.join([log.gatewayName, log.logContent]) function(msg) - + ######################################################################## class PositionDetail(object): @@ -872,8 +882,6 @@ class PositionDetail(object): self.shortPnl = pos.positionProfit self.shortPrice = pos.price - #self.output() - #---------------------------------------------------------------------- def updateOrderReq(self, req, vtOrderID): """发单更新""" @@ -986,15 +994,6 @@ class PositionDetail(object): self.longPosFrozen = self.longYdFrozen + self.longTdFrozen self.shortPosFrozen = self.shortYdFrozen + self.shortTdFrozen - #---------------------------------------------------------------------- - def output(self): - """""" - print self.vtSymbol, '-'*30 - print 'long, total:%s, td:%s, yd:%s' %(self.longPos, self.longTd, self.longYd) - print 'long frozen, total:%s, td:%s, yd:%s' %(self.longPosFrozen, self.longTdFrozen, self.longYdFrozen) - print 'short, total:%s, td:%s, yd:%s' %(self.shortPos, self.shortTd, self.shortYd) - print 'short frozen, total:%s, td:%s, yd:%s' %(self.shortPosFrozen, self.shortTdFrozen, self.shortYdFrozen) - #---------------------------------------------------------------------- def convertOrderReq(self, req): """转换委托请求""" diff --git a/vnpy/trader/vtFunction.py b/vnpy/trader/vtFunction.py index 1fc55c9a..7673177a 100644 --- a/vnpy/trader/vtFunction.py +++ b/vnpy/trader/vtFunction.py @@ -11,6 +11,8 @@ import traceback from datetime import datetime from math import isnan +from six import text_type + #---------------------------------------------------------------------- def safeUnicode(value): @@ -26,7 +28,7 @@ def safeUnicode(value): if abs(d.as_tuple().exponent) > MAX_DECIMAL: value = round(value, ndigits=MAX_DECIMAL) - return unicode(value) + return text_type(value) #----------------------------------------------------------------------