├── .gitignore ├── LICENSE ├── README.md ├── doc ├── img │ ├── tradesim.png │ ├── vnTrader_cancel.png │ ├── vnTrader_login.png │ ├── vnTrader_main.png │ ├── vnTrader_order.png │ ├── vnTrader_start.png │ └── vnTrader_strategy.png ├── vnTrader.md └── webClient.md ├── tradeapi ├── .gitignore ├── LICENSE ├── README.md ├── __init__.py ├── jrpc_py.py ├── trade_api.py └── utils.py └── vnTrader ├── __init__.py ├── eventEngine.py ├── eventType.py ├── gateway ├── __init__.py ├── quantosGateway.py └── quantosLoginWidget.py ├── qdarkstyle ├── __init__.py ├── compile_qrc.py ├── pyqt5_style_rc.py ├── pyqt_style_rc.py ├── rc │ ├── Hmovetoolbar.png │ ├── Hsepartoolbar.png │ ├── Vmovetoolbar.png │ ├── Vsepartoolbar.png │ ├── branch_closed-on.png │ ├── branch_closed.png │ ├── branch_open-on.png │ ├── branch_open.png │ ├── checkbox_checked.png │ ├── checkbox_checked_disabled.png │ ├── checkbox_checked_focus.png │ ├── checkbox_indeterminate.png │ ├── checkbox_indeterminate_disabled.png │ ├── checkbox_indeterminate_focus.png │ ├── checkbox_unchecked.png │ ├── checkbox_unchecked_disabled.png │ ├── checkbox_unchecked_focus.png │ ├── close-hover.png │ ├── close-pressed.png │ ├── close.png │ ├── down_arrow.png │ ├── down_arrow_disabled.png │ ├── left_arrow.png │ ├── left_arrow_disabled.png │ ├── radio_checked.png │ ├── radio_checked_disabled.png │ ├── radio_checked_focus.png │ ├── radio_unchecked.png │ ├── radio_unchecked_disabled.png │ ├── radio_unchecked_focus.png │ ├── right_arrow.png │ ├── right_arrow_disabled.png │ ├── sizegrip.png │ ├── stylesheet-branch-end.png │ ├── stylesheet-branch-more.png │ ├── stylesheet-vline.png │ ├── transparent.png │ ├── undock.png │ ├── up_arrow.png │ └── up_arrow_disabled.png ├── style.qrc └── style.qss ├── setting ├── VT_setting.json └── vnpy.ico ├── start.bat ├── uiBasicWidget.py ├── uiMainWindow.py ├── vtConstant.py ├── vtEngine.py ├── vtFunction.py ├── vtGateway.py └── vtMain.py /.gitignore: -------------------------------------------------------------------------------- 1 | vnTrader/setting/VT_setting.json 2 | # cProfile results 3 | *.prof 4 | 5 | # Personal Configs 6 | # etc/*.json 7 | 8 | # IDE configs 9 | .idea/ 10 | .eclipse/ 11 | 12 | # Output Results 13 | output/ 14 | 15 | # Byte-compiled / optimized / DLL files 16 | __pycache__/ 17 | *.py[cod] 18 | *$py.class 19 | 20 | # C extensions 21 | *.so 22 | 23 | # Distribution / packaging 24 | .Python 25 | build/ 26 | develop-eggs/ 27 | dist/ 28 | downloads/ 29 | eggs/ 30 | .eggs/ 31 | lib/ 32 | lib64/ 33 | parts/ 34 | sdist/ 35 | var/ 36 | wheels/ 37 | *.egg-info/ 38 | .installed.cfg 39 | *.egg 40 | 41 | # PyInstaller 42 | # Usually these files are written by a python script from a template 43 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 44 | *.manifest 45 | *.spec 46 | 47 | # Installer logs 48 | pip-log.txt 49 | pip-delete-this-directory.txt 50 | 51 | # Unit test / coverage reports 52 | htmlcov/ 53 | .tox/ 54 | .coverage 55 | .coverage.* 56 | .cache 57 | nosetests.xml 58 | coverage.xml 59 | *.cover 60 | .hypothesis/ 61 | 62 | # Translations 63 | *.mo 64 | *.pot 65 | 66 | # Django stuff: 67 | *.log 68 | local_settings.py 69 | 70 | # Flask stuff: 71 | instance/ 72 | .webassets-cache 73 | 74 | # Scrapy stuff: 75 | .scrapy 76 | 77 | # Sphinx documentation 78 | docs/_build/ 79 | 80 | # PyBuilder 81 | target/ 82 | 83 | # Jupyter Notebook 84 | .ipynb_checkpoints 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # celery beat schedule file 90 | celerybeat-schedule 91 | 92 | # SageMath parsed files 93 | *.sage.py 94 | 95 | # Environments 96 | .env 97 | .venv 98 | env/ 99 | venv/ 100 | ENV/ 101 | 102 | # Spyder project settings 103 | .spyderproject 104 | .spyproject 105 | 106 | # Rope project settings 107 | .ropeproject 108 | 109 | # mkdocs documentation 110 | /site 111 | 112 | # mypy 113 | .mypy_cache/ 114 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 项目简介 2 | TradeSim是一个在线仿真交易平台(未开源),提供账户管理、在线交易、模拟成交等服务,支持股票、期货等品种的交易。 3 | TradeSim中的交易系统模块支持多账户管理、多通道交易、实时风控,提供包括VWAP、TWAP、配对交易、篮子下单在内的算法交易,是一款企业级应用。 4 | 5 | ![](https://github.com/quantOS-org/TradeSim/blob/master/doc/img/tradesim.png) 6 | 7 | ## 功能特色 8 | 9 | - 通过TradeAPI进行程序化下单。 10 | - 支持股票、ETF、股指期货、国债期货、商品期货等品种的交易业务。 11 | - 支持VWAP、TWAP等算法交易下单。 12 | - 支持根据实时行情进行模拟撮合。 13 | - 永久保存用户的历史交易、持仓信息。 14 | - 支持对历史交易进行PNL分析、绩效归因、数据查询。 15 | - VN.PY已实现与TradeSim的集成,用户可以通过VN.PY界面进行模拟交易。 16 | 17 | ## 客户端使用 18 | 19 | TradeSim提供Web客户端、专用Python客户端等访问方式。支持Python 2.7/3.6,支持PyQt 4/5. 20 | 21 | 1. Web客户端:请登录[仿真交易](http://www.quantos.org/tradesim/trade.html)后使用。使用帮助参见[https://github.com/quantOS-org/TradeSim/tree/master/doc/webClient.md](https://github.com/quantOS-org/TradeSim/tree/master/doc/webClient.md) 22 | 2. 专用Python客户端:提供vnTrader客户端,请从[这里](https://github.com/quantOS-org/TradeSim/tree/master/vnTrader)下载。使用帮助参见[https://github.com/quantOS-org/TradeSim/tree/master/doc/vnTrader.md](https://github.com/quantOS-org/TradeSim/tree/master/doc/vnTrader.md) 23 | 24 | ## 交易API使用说明 25 | 26 | 请参见[TradeApi说明文档](https://github.com/quantOS-org/TradeAPI)。 27 | -------------------------------------------------------------------------------- /doc/img/tradesim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/doc/img/tradesim.png -------------------------------------------------------------------------------- /doc/img/vnTrader_cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/doc/img/vnTrader_cancel.png -------------------------------------------------------------------------------- /doc/img/vnTrader_login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/doc/img/vnTrader_login.png -------------------------------------------------------------------------------- /doc/img/vnTrader_main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/doc/img/vnTrader_main.png -------------------------------------------------------------------------------- /doc/img/vnTrader_order.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/doc/img/vnTrader_order.png -------------------------------------------------------------------------------- /doc/img/vnTrader_start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/doc/img/vnTrader_start.png -------------------------------------------------------------------------------- /doc/img/vnTrader_strategy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/doc/img/vnTrader_strategy.png -------------------------------------------------------------------------------- /doc/vnTrader.md: -------------------------------------------------------------------------------- 1 | # vnTrader使用帮助 2 | 3 | ## 0. 软件下载。 4 | + 请首先安装[JAQS](https://github.com/quantOS-org/JAQS),如已安装,请忽略。 5 | + 请从[这里](https://github.com/quantOS-org/TradeSim/tree/master/vnTrader)下载vnTrader, 如已下载,请忽略. 6 | 7 | ## 1. 请在vnTrader程序目录,通过如下命令启动vnTrader: 8 | ```shell 9 | python vtMain.py 10 | ``` 11 | 在Windows上,也可以直接双击`start.bat`运行,如下图所示: 12 | 13 | ![](https://github.com/quantOS-org/TradeSim/blob/master/doc/img/vnTrader_start.png) 14 | 15 | ## 2. 系统提示登录,在登录框输入手机号和token,如下图 16 | 17 | ![](https://github.com/quantOS-org/TradeSim/blob/master/doc/img/vnTrader_login.png) 18 | **此时的策略号无法选择**,直接点击确定,系统会加载出策略号,如下图 19 | 20 | ![](https://github.com/quantOS-org/TradeSim/blob/master/doc/img/vnTrader_strategy.png) 21 | 22 | 选择你要操作的策略号,再次点击确定,进入系统主界面,如下图: 23 | 24 | ![](https://github.com/quantOS-org/TradeSim/blob/master/doc/img/vnTrader_main.png) 25 | 26 | 主界面主要包括:交易,行情,持仓,委托,成交,资金,日志,合约等几个大的面板,用于展示用户的详细交易信息。 27 | 28 | *注:如想避免每次打开重复输入手机号和token,可在`vnTrader\setting\VT_setting.json`文件中修改`username`和`token`的值。* 29 | 30 | ## 3. 发起委托 31 | 32 | 在交易界面,用户可以输入需要交易的标的代码、价格、数量,选择适当的算法,点击发单,即可进行委托。如下图 33 | 34 | ![](https://github.com/quantOS-org/TradeSim/blob/master/doc/img/vnTrader_order.png) 35 | 36 | ## 4. 发起撤单 37 | 38 | 有两种方式可以撤单: 39 | 40 | 1. 点击交易模块的“全撤”按钮。 41 | 2. 双击委托模块的指定委托记录。 42 | 43 | ![](https://github.com/quantOS-org/TradeSim/blob/master/doc/img/vnTrader_cancel.png) 44 | 45 | -------------------------------------------------------------------------------- /doc/webClient.md: -------------------------------------------------------------------------------- 1 | ## webClient使用帮助 2 | 3 | 请登录[仿真交易](http://www.quantos.org/tradesim/trade.html)后使用。 4 | 5 | -------------------------------------------------------------------------------- /tradeapi/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | -------------------------------------------------------------------------------- /tradeapi/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /tradeapi/README.md: -------------------------------------------------------------------------------- 1 | # TradeApi 2 | 3 | 标准交易API定义 4 | 5 | # 安装步骤 6 | 7 | 1、安装Python环境 8 | ------ 9 | 10 | 如果本地还没有安装Python环境,强烈建议安装 [Anaconda](http://www.continuum.io/downloads "Anaconda")。 11 | 12 | 打开上面的网址,选择相应的操作系统,确定要按照的Python版本,一般建议用Python 2.7。 13 | 14 | 下载完成以后,按照图形界面步骤完成安装。在默认情况下,Anaconda会自动设置PATH环境。 15 | 16 | 2、安装依赖包 17 | ---------------- 18 | 19 | 如果Python环境不是类似Anaconda的集成开发环境,我们需要单独安装依赖包,在已经有pandas/numpy包前提下,还需要有以下几个包: 20 | 21 | pyzmq 22 | msgpack_python 23 | python-snappy 24 | 25 | 可以通过单个安装完成,例如: pip install pyzmq 26 | 27 | 需要注意的是,python-snappy和msgpack-python这两个包在Windows上的安装需要比较多的编译依赖,建议从[这个网页](http://www.lfd.uci.edu/~gohlke/pythonlibs)下载编译好的包,然后安装: 28 | 29 | pip install msgpack_python-0.4.8-cp27-cp27m-win_amd64.whl 30 | 31 | pip install python_snappy-0.5.1-cp27-cp27m-win_amd64.whl 32 | 33 | 34 | 35 | 3、使用TradeApi 36 | -------- 37 | 38 | 在项目目录,验证TradeApi是否正常使用。 39 | 40 | ```python 41 | from TradeApi import TradeApi 42 | 43 | api = TradeApi(addr="tcp://gw.quantos.org:8901") 44 | result, msg = api.login("username", "token") # 示例账户,用户需要改为自己在www.quantos.org上注册的账户 45 | print result 46 | print msg 47 | 48 | ``` 49 | -------------------------------------------------------------------------------- /tradeapi/__init__.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | """ 3 | Core trade api for simulated and live trading. 4 | """ 5 | from __future__ import absolute_import 6 | from __future__ import division 7 | from __future__ import print_function 8 | from __future__ import unicode_literals 9 | 10 | from .trade_api import TradeApi 11 | 12 | __all__ = ['TradeApi'] 13 | -------------------------------------------------------------------------------- /tradeapi/jrpc_py.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from __future__ import division 3 | from __future__ import print_function 4 | from __future__ import unicode_literals 5 | 6 | import json 7 | import random 8 | import time 9 | from builtins import * 10 | 11 | import zmq 12 | 13 | try: 14 | import queue 15 | except ImportError: 16 | import queue as queue 17 | import threading 18 | import msgpack 19 | import snappy 20 | import copy 21 | 22 | qEmpty = copy.copy(queue.Empty) 23 | 24 | 25 | def _unpack_msgpack_snappy(str): 26 | if str.startswith(b'S'): 27 | tmp = snappy.uncompress(str[1:]) 28 | # print "SNAPPY: ", len(str), len(tmp) 29 | obj = msgpack.loads(tmp, encoding='utf-8') 30 | elif str.startswith(b'\0'): 31 | obj = msgpack.loads(str[1:], encoding='utf-8') 32 | else: 33 | return None 34 | 35 | return obj 36 | 37 | 38 | def _pack_msgpack_snappy(obj): 39 | # print "pack", obj 40 | tmp = msgpack.dumps(obj, encoding='utf-8') 41 | if len(tmp) > 1000: 42 | return b'S' + snappy.compress(tmp) 43 | else: 44 | return b'\0' + tmp 45 | 46 | 47 | def _unpack_msgpack(str): 48 | return msgpack.loads(str, encoding='utf-8') 49 | 50 | 51 | def _pack_msgpack(obj): 52 | return msgpack.dumps(obj, encoding='utf-8') 53 | 54 | 55 | def _unpack_json(str): 56 | return json.loads(str, encoding='utf-8') 57 | 58 | 59 | def _pack_json(obj): 60 | return json.dumps(obj, encoding='utf-8') 61 | 62 | 63 | class JRpcClient(object): 64 | def __init__(self, data_format="msgpack_snappy"): 65 | self._waiter_lock = threading.Lock() 66 | self._waiter_map = {} 67 | 68 | self._should_close = False 69 | self._next_callid = 0 70 | self._send_lock = threading.Lock() 71 | self._callid_lock = threading.Lock() 72 | 73 | self._last_heartbeat_rsp_time = 0 74 | self._connected = False 75 | 76 | self.on_disconnected = None 77 | self.on_rpc_callback = None 78 | self._callback_queue = queue.Queue() 79 | self._call_wait_queue = queue.Queue() 80 | 81 | self._ctx = zmq.Context() 82 | self._pull_sock = self._ctx.socket(zmq.PULL) 83 | self._pull_sock.bind("inproc://pull_sock") 84 | self._push_sock = self._ctx.socket(zmq.PUSH) 85 | self._push_sock.connect("inproc://pull_sock") 86 | 87 | self._heartbeat_interval = 1 88 | self._heartbeat_timeout = 3 89 | 90 | self._addr = None 91 | 92 | if data_format == "msgpack_snappy": 93 | self._pack = _pack_msgpack_snappy 94 | self._unpack = _unpack_msgpack_snappy 95 | 96 | elif data_format == "msgpack": 97 | self._pack = _pack_msgpack 98 | self._unpack = _unpack_msgpack 99 | 100 | elif data_format == "json": 101 | self._pack = _pack_json 102 | self._unpack = _unpack_json 103 | 104 | else: 105 | assert False, "unknown data_format " + data_format 106 | 107 | t = threading.Thread(target=self._recv_run) 108 | t.setDaemon(True) 109 | t.start() 110 | self._recv_thread = t 111 | 112 | t = threading.Thread(target=self._callback_run) 113 | t.setDaemon(True) 114 | t.start() 115 | self._callback_thread = t 116 | 117 | def __del__(self): 118 | self.close() 119 | 120 | def next_callid(self): 121 | self._callid_lock.acquire() 122 | self._next_callid += 1 123 | callid = self._next_callid 124 | self._callid_lock.release() 125 | return callid 126 | 127 | def set_heartbeat_options(self, interval, timeout): 128 | self._heartbeat_interval = interval 129 | self._heartbeat_timeout = timeout 130 | 131 | def _recv_run(self): 132 | 133 | heartbeat_time = 0 134 | 135 | poller = zmq.Poller() 136 | poller.register(self._pull_sock, zmq.POLLIN) 137 | 138 | remote_sock = None 139 | 140 | while not self._should_close: 141 | 142 | try: 143 | if self._connected and time.time() - self._last_heartbeat_rsp_time > self._heartbeat_timeout: 144 | self._connected = False 145 | if self.on_disconnected: self._async_call(self.on_disconnected) 146 | 147 | if remote_sock and time.time() - heartbeat_time > self._heartbeat_interval: 148 | self._send_hearbeat() 149 | heartbeat_time = time.time() 150 | 151 | socks = dict(poller.poll(500)) 152 | if self._pull_sock in socks and socks[self._pull_sock] == zmq.POLLIN: 153 | cmd = self._pull_sock.recv() 154 | if cmd == b"CONNECT": 155 | # print time.ctime(), "CONNECT " + self._addr 156 | if remote_sock: 157 | poller.unregister(remote_sock) 158 | remote_sock.close() 159 | remote_sock = None 160 | 161 | remote_sock = self._do_connect() 162 | 163 | if remote_sock: 164 | poller.register(remote_sock, zmq.POLLIN) 165 | 166 | elif cmd.startswith(b"SEND:") and remote_sock: 167 | # print time.ctime(), "SEND " + cmd[5:] 168 | remote_sock.send(cmd[5:]) 169 | 170 | if remote_sock and remote_sock in socks and socks[remote_sock] == zmq.POLLIN: 171 | data = remote_sock.recv() 172 | if data: 173 | # if not data.find("heartbeat"): 174 | # print time.ctime(), "RECV", data 175 | self._on_data_arrived(data) 176 | 177 | except zmq.error.Again as e: 178 | # print "RECV timeout: ", e 179 | pass 180 | except Exception as e: 181 | print("_recv_run:", e) 182 | 183 | def _callback_run(self): 184 | while not self._should_close: 185 | try: 186 | r = self._callback_queue.get(timeout=1) 187 | if r: 188 | r() 189 | except qEmpty as e: 190 | pass 191 | except TypeError as e: 192 | if str(e) == "'NoneType' object is not callable": 193 | pass 194 | else: 195 | print("_callback_run {}".format(r), type(e), e) 196 | except Exception as e: 197 | print("_callback_run {}".format(r), type(e), e) 198 | 199 | def _async_call(self, func): 200 | self._callback_queue.put(func) 201 | 202 | def _send_request(self, json): 203 | 204 | try: 205 | self._send_lock.acquire() 206 | self._push_sock.send(b"SEND:" + json) 207 | 208 | finally: 209 | self._send_lock.release() 210 | 211 | def connect(self, addr): 212 | self._addr = addr 213 | self._push_sock.send_string('CONNECT', encoding='utf-8') 214 | 215 | def _do_connect(self): 216 | 217 | client_id = str(random.randint(1000000, 100000000)) 218 | 219 | socket = self._ctx.socket(zmq.DEALER) 220 | identity = (client_id) + '$' + str(random.randint(1000000, 1000000000)) 221 | identity = identity.encode('utf-8') 222 | socket.setsockopt(zmq.IDENTITY, identity) 223 | socket.setsockopt(zmq.RCVTIMEO, 500) 224 | socket.setsockopt(zmq.SNDTIMEO, 500) 225 | socket.setsockopt(zmq.LINGER, 0) 226 | socket.connect(self._addr) 227 | 228 | return socket 229 | 230 | def close(self): 231 | self._should_close = True 232 | self._callback_thread.join() 233 | self._recv_thread.join() 234 | 235 | def _on_data_arrived(self, str): 236 | try: 237 | msg = self._unpack(str) 238 | # print "RECV", msg 239 | 240 | if not msg: 241 | print("wrong message format") 242 | return 243 | 244 | if 'method' in msg and msg['method'] == '.sys.heartbeat': 245 | self._last_heartbeat_rsp_time = time.time() 246 | if not self._connected: 247 | self._connected = True 248 | if self.on_connected: 249 | self._async_call(self.on_connected) 250 | 251 | # Let user has a chance to check message in .sys.heartbeat 252 | if 'result' in msg and self.on_rpc_callback: 253 | self._async_call(lambda: self.on_rpc_callback(msg['method'], msg['result'])) 254 | 255 | elif 'id' in msg and msg['id']: 256 | 257 | # Call result 258 | id = int(msg['id']) 259 | 260 | if self._waiter_lock.acquire(): 261 | if id in self._waiter_map: 262 | q = self._waiter_map[id] 263 | if q: q.put(msg) 264 | self._waiter_lock.release() 265 | else: 266 | # Notification message 267 | if 'method' in msg and 'result' in msg and self.on_rpc_callback: 268 | self._async_call(lambda: self.on_rpc_callback(msg['method'], msg['result'])) 269 | 270 | except Exception as e: 271 | print("_on_data_arrived:", e) 272 | pass 273 | 274 | def _send_hearbeat(self): 275 | msg = {'jsonrpc' : '2.0', 276 | 'method' : '.sys.heartbeat', 277 | 'params' : { 'time': time.time()}, 278 | 'id' : str(self.next_callid())} 279 | json_str = self._pack(msg) 280 | self._send_request(json_str) 281 | 282 | def _alloc_wait_queue(self): 283 | self._waiter_lock.acquire() 284 | if self._call_wait_queue: 285 | q = self._call_wait_queue 286 | self._call_wait_queue = None 287 | else: 288 | q = queue.Queue() 289 | self._waiter_lock.release() 290 | return q 291 | 292 | def _free_wait_queue(self, q): 293 | self._waiter_lock.acquire() 294 | if not self._call_wait_queue: 295 | self._call_wait_queue = q 296 | else: 297 | del q 298 | self._waiter_lock.release() 299 | 300 | def call(self, method, params, timeout=6): 301 | # print "call", method, params, timeout 302 | callid = self.next_callid() 303 | if timeout: 304 | q = self._alloc_wait_queue() 305 | 306 | self._waiter_lock.acquire() 307 | self._waiter_map[callid] = q 308 | self._waiter_lock.release() 309 | 310 | msg = {'jsonrpc' : '2.0', 311 | 'method' : method, 312 | 'params' : params, 313 | 'id' : str(callid) } 314 | 315 | # print "SEND", msg 316 | json_str = self._pack(msg) 317 | self._send_request(json_str) 318 | 319 | if timeout: 320 | ret = {} 321 | try: 322 | r = q.get(timeout=timeout) 323 | q.task_done() 324 | except qEmpty: 325 | r = None 326 | 327 | self._waiter_lock.acquire() 328 | self._waiter_map[callid] = None 329 | self._waiter_lock.release() 330 | self._free_wait_queue(q) 331 | 332 | if r: 333 | if 'result' in r: 334 | ret['result'] = r['result'] 335 | 336 | if 'error' in r: 337 | ret['error'] = r['error'] 338 | 339 | return ret if ret else {'error': {'error': -1, 'message': "timeout"}} 340 | else: 341 | return {'result': True} 342 | -------------------------------------------------------------------------------- /tradeapi/trade_api.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from __future__ import division 3 | from __future__ import print_function 4 | from __future__ import unicode_literals 5 | 6 | import json 7 | from builtins import * 8 | import pandas as pd 9 | from . import utils 10 | 11 | 12 | class EntrustOrder(object): 13 | def __init__(self, security, action, price, size): 14 | self.security = security 15 | self.action = action 16 | self.price = price 17 | self.size = size 18 | 19 | def set_log_dir(log_dir): 20 | try: 21 | import jrpc 22 | if log_dir: 23 | jrpc.set_log_dir(log_dir) 24 | else: 25 | jrpc.set_log_dir("") 26 | except Exception as e: 27 | print("Exception", e) 28 | 29 | class TradeApi(object): 30 | def __init__(self, addr, use_jrpc=True, prod_type="jzts"): 31 | """ 32 | use_jrpc: 33 | True -- Use jrcp_client of C version, for jzts only 34 | False -- Use pure python version 35 | prod_type: 36 | "jaqs" -- jrpc_msgpack_wth_snappy 37 | "jzts" -- jrpc_msgpack 38 | """ 39 | 40 | self._remote = None 41 | if prod_type == "jzts": 42 | try: 43 | if use_jrpc: 44 | import jrpc 45 | self._remote = jrpc.JsonRpcClient() 46 | else: 47 | from . import jrpc_py 48 | self._remote = jrpc_py.JRpcClient(data_format="msgpack") 49 | except Exception as e: 50 | print("Exception", e) 51 | 52 | if not self._remote: 53 | from . import jrpc_py 54 | self._remote = jrpc_py.JRpcClient(data_format="msgpack") 55 | 56 | else: 57 | from . import jrpc_py 58 | self._remote = jrpc_py.JRpcClient(data_format="msgpack_snappy") 59 | 60 | self._remote.on_rpc_callback = self._on_rpc_callback 61 | self._remote.on_disconnected = self._on_disconnected 62 | self._remote.on_connected = self._on_connected 63 | self._remote.connect(addr) 64 | 65 | self._ordstatus_callback = None 66 | self._taskstatus_callback = None 67 | self._internal_order_callback = None 68 | self._trade_callback = None 69 | self._on_connection_callback = None 70 | self._connected = False 71 | self._username = "" 72 | self._password = "" 73 | self._strategy_id = 0 74 | self._strategy_selected = False 75 | self._data_format = "default" 76 | 77 | def __del__(self): 78 | self._remote.close() 79 | 80 | def _on_rpc_callback(self, method, data): 81 | #print "_on_rpc_callback:", method, data 82 | 83 | if method == "oms.orderstatus_ind": 84 | if self._data_format == "obj": 85 | data = utils.to_obj("Order", data) 86 | 87 | if self._ordstatus_callback: 88 | self._ordstatus_callback(data) 89 | 90 | elif method == "oms.taskstatus_ind": 91 | if self._data_format == "obj": 92 | data = utils.to_obj("TaskStatus", data) 93 | 94 | if self._taskstatus_callback: 95 | self._taskstatus_callback(data) 96 | 97 | elif method == "oms.trade_ind": 98 | if self._data_format == "obj": 99 | data = utils.to_obj("Trade", data) 100 | 101 | if self._trade_callback: 102 | self._trade_callback(data) 103 | 104 | elif method == "oms.internal_order_ind": 105 | if self._data_format == "obj": 106 | data = utils.to_obj("QuoteOrder", data) 107 | 108 | if self._internal_order_callback: 109 | self._internal_order_callback(data) 110 | 111 | def _on_disconnected(self): 112 | print("TradeApi: _on_disconnected") 113 | self._connected = False 114 | self._strategy_selected = False 115 | if self._on_connection_callback: 116 | self._on_connection_callback(False) 117 | 118 | def _on_connected(self): 119 | print("TradeApi: _on_connected") 120 | self._connected = True 121 | self._do_login() 122 | self._do_use_strategy() 123 | if self._on_connection_callback: 124 | self._on_connection_callback(True) 125 | 126 | def _check_session(self): 127 | if not self._connected: 128 | return (False, "no connection") 129 | 130 | if self._strategy_selected: 131 | return (True, "") 132 | 133 | r, msg = self._do_login() 134 | if not r: return (r, msg) 135 | if self._strategy_id: 136 | return self._do_use_strategy() 137 | else: 138 | return (r, msg) 139 | 140 | def set_data_format(self, format): 141 | self._data_format = format 142 | 143 | def set_connection_callback(self, callback): 144 | self._on_connection_callback = callback 145 | 146 | def set_ordstatus_callback(self, callback): 147 | self._ordstatus_callback = callback 148 | 149 | def set_trade_callback(self, callback): 150 | self._trade_callback = callback 151 | 152 | def set_task_callback(self, callback): 153 | self._taskstatus_callback = callback 154 | 155 | def set_quoteorder_callback(self, callback): 156 | self._internal_order_callback = callback 157 | 158 | def _get_format(self, format, default_format): 159 | if format: 160 | return format 161 | elif self._data_format != "default": 162 | return self._data_format 163 | else: 164 | return default_format 165 | 166 | def login(self, username, password, format=""): 167 | self._username = username 168 | self._password = password 169 | return self._do_login(format=format) 170 | 171 | def _do_login(self, format=""): 172 | # Shouldn't check connected flag here. ZMQ is a mesageq queue! 173 | # if !self._connected : 174 | # return (False, "-1,no connection") 175 | 176 | if self._username and self._password: 177 | rpc_params = {"username": self._username, 178 | "password": self._password} 179 | 180 | cr = self._remote.call("auth.login", rpc_params) 181 | f = self._get_format(format, "") 182 | if f != "obj" and f != "": 183 | f = "" 184 | return utils.extract_result(cr, data_format=f, class_name="UserInfo") 185 | else: 186 | return (False, "-1,empty username or password") 187 | 188 | def logout(self): 189 | rpc_params = {} 190 | 191 | cr = self._remote.call("auth.logout", rpc_params) 192 | return utils.extract_result(cr) 193 | 194 | def close(self): 195 | self._remote.close() 196 | 197 | def use_strategy(self, strategy_id): 198 | if strategy_id: 199 | self._strategy_id = strategy_id 200 | return self._do_use_strategy() 201 | else: 202 | # Query 203 | rpc_params = {"account_id": 0} 204 | 205 | cr = self._remote.call("auth.use_strategy", rpc_params) 206 | r, msg = utils.extract_result(cr) 207 | self._strategy_selected = r 208 | 209 | return (r, msg) 210 | 211 | def _do_use_strategy(self): 212 | if self._strategy_id: 213 | rpc_params = {"account_id": self._strategy_id} 214 | 215 | cr = self._remote.call("auth.use_strategy", rpc_params) 216 | r, msg = utils.extract_result(cr) 217 | self._strategy_selected = r 218 | 219 | return (r, msg) 220 | else: 221 | return (False, "-1,no strategy_id was specified") 222 | 223 | def confirm_internal_order(self, task_id, confirmed): 224 | """ 225 | return (result, message) 226 | if result is None, message contains error information 227 | """ 228 | 229 | r, msg = self._check_session() 230 | if not r: return (None, msg) 231 | 232 | rpc_params = {"task_id" : task_id, 233 | "confirmed": confirmed} 234 | 235 | cr = self._remote.call("oms.confirm_internal_order", rpc_params) 236 | return utils.extract_result(cr) 237 | 238 | def order(self, security, price, size, algo="", algo_param={}, userdata=""): 239 | """ 240 | return (result, message) 241 | if result is None, message contains error information 242 | """ 243 | 244 | r, msg = self._check_session() 245 | if not r: return (None, msg) 246 | 247 | rpc_params = {"security" : security, 248 | "price" : price, 249 | "size" : int(size), 250 | "algo" : algo, 251 | "algo_param" : json.dumps(algo_param), 252 | "user" : self._username, 253 | "userdata" : userdata} 254 | 255 | cr = self._remote.call("oms.order", rpc_params) 256 | return utils.extract_result(cr) 257 | 258 | def place_order(self, security, action, price, size, algo="", algo_param={}, userdata=""): 259 | """ 260 | return (result, message) 261 | if result is None, message contains error information 262 | """ 263 | 264 | r, msg = self._check_session() 265 | if not r: return (None, msg) 266 | 267 | rpc_params = { "security" : security, 268 | "action" : action, 269 | "price" : price, 270 | "size" : int(size), 271 | "algo" : algo, 272 | "algo_param" : json.dumps(algo_param), 273 | "user" : self._username, 274 | "userdata" : userdata} 275 | 276 | cr = self._remote.call("oms.place_order", rpc_params) 277 | return utils.extract_result(cr) 278 | 279 | def batch_order(self, orders, algo="", algo_param={}, userdata=""): 280 | """ 281 | orders format: 282 | [ {"security": "000001.SZ", "action": "Buy", "price": 10.0, "size" : 100}, ... ] 283 | return (result, message) 284 | if result is None, message contains error information 285 | """ 286 | 287 | if not orders or not isinstance(orders, (list, tuple)): 288 | return (None, "empty order") 289 | 290 | if isinstance(orders[0], EntrustOrder): 291 | tmp = [] 292 | for o in orders: 293 | tmp.append({"security": o.security, 294 | "price" : o.price, 295 | "size" : int(o.size)}) 296 | 297 | orders = tmp 298 | 299 | r, msg = self._check_session() 300 | if not r: return (None, msg) 301 | 302 | rpc_params = {"orders" : orders, 303 | "algo" : algo, 304 | "algo_param" : json.dumps(algo_param), 305 | "user" : self._username, 306 | "userdata" : userdata} 307 | 308 | cr = self._remote.call("oms.batch_order", rpc_params) 309 | return utils.extract_result(cr) 310 | 311 | def place_batch_order(self, orders, algo="", algo_param={}, userdata=""): 312 | """ 313 | orders format: 314 | [ {"security": "000001.SZ", "action": "Buy", "price": 10.0, "size" : 100}, ... ] 315 | return (result, message) 316 | if result is None, message contains error information 317 | """ 318 | 319 | if not orders or not isinstance(orders, (list, tuple)): 320 | return (None, "empty order") 321 | 322 | if isinstance(orders[0], EntrustOrder): 323 | tmp = [] 324 | for o in orders: 325 | tmp.append({"security": o.security, 326 | "action" : o.action, 327 | "price" : o.price, 328 | "size" : int(o.size)}) 329 | 330 | orders = tmp 331 | 332 | r, msg = self._check_session() 333 | if not r: return (None, msg) 334 | 335 | rpc_params = {"orders" : orders, 336 | "algo" : algo, 337 | "algo_param" : json.dumps(algo_param), 338 | "user" : self._username, 339 | "userdata" : userdata} 340 | 341 | cr = self._remote.call("oms.place_batch_order", rpc_params) 342 | return utils.extract_result(cr) 343 | 344 | def cancel_order(self, task_id): 345 | """ 346 | return (result, message) 347 | if result is None, message contains error information 348 | """ 349 | 350 | r, msg = self._check_session() 351 | if not r: return (None, msg) 352 | 353 | rpc_params = {"task_id": task_id} 354 | 355 | cr = self._remote.call("oms.cancel_order", rpc_params) 356 | return utils.extract_result(cr) 357 | 358 | def query_account(self, format=""): 359 | """ 360 | return pd.dataframe 361 | """ 362 | r, msg = self._check_session() 363 | if not r: return (None, msg) 364 | 365 | rpc_params = {} 366 | 367 | data_format = self._get_format(format, "pandas") 368 | if data_format == "pandas": 369 | rpc_params["format"] = "columnset" 370 | 371 | cr = self._remote.call("oms.query_account", rpc_params) 372 | 373 | return utils.extract_result(cr, data_format=data_format, class_name="Account") 374 | 375 | def query_position(self, mode="all", securities="", format=""): 376 | """ 377 | securities: seperate by "," 378 | return pd.dataframe 379 | """ 380 | 381 | r, msg = self._check_session() 382 | if not r: return (None, msg) 383 | 384 | rpc_params = {"mode" : mode, 385 | "security" : securities} 386 | 387 | data_format = self._get_format(format, "pandas") 388 | if data_format == "pandas": 389 | rpc_params["format"] = "columnset" 390 | 391 | cr = self._remote.call("oms.query_position", rpc_params) 392 | 393 | return utils.extract_result(cr, data_format=data_format, class_name="Position") 394 | 395 | def query_net_position(self, mode="all", securities="", format=""): 396 | """ 397 | securities: seperate by "," 398 | return pd.dataframe 399 | """ 400 | 401 | r, msg = self._check_session() 402 | if not r: return (None, msg) 403 | 404 | rpc_params = {"mode" : mode, 405 | "security" : securities} 406 | 407 | data_format = self._get_format(format, "pandas") 408 | if data_format == "pandas": 409 | rpc_params["format"] = "columnset" 410 | 411 | cr = self._remote.call("oms.query_net_position", rpc_params) 412 | 413 | return utils.extract_result(cr, data_format=data_format, class_name="NetPosition") 414 | 415 | def query_repo_contract(self, format=""): 416 | """ 417 | securities: seperate by "," 418 | return pd.dataframe 419 | """ 420 | 421 | r, msg = self._check_session() 422 | if not r: return (None, msg) 423 | 424 | rpc_params = {} 425 | 426 | cr = self._remote.call("oms.query_repo_contract", rpc_params) 427 | 428 | return utils.extract_result(cr, data_format=self._get_format(format, "pandas"), class_name="RepoContract") 429 | 430 | def query_task(self, task_id=-1, format=""): 431 | """ 432 | task_id: -1 -- all 433 | return pd.dataframe 434 | """ 435 | 436 | r, msg = self._check_session() 437 | if not r: return (None, msg) 438 | 439 | rpc_params = {"task_id": task_id} 440 | 441 | data_format = self._get_format(format, "pandas") 442 | if data_format == "pandas": 443 | rpc_params["format"] = "columnset" 444 | 445 | cr = self._remote.call("oms.query_task", rpc_params) 446 | 447 | return utils.extract_result(cr, data_format=data_format, class_name="Task") 448 | 449 | def query_order(self, task_id=-1, format=""): 450 | """ 451 | task_id: -1 -- all 452 | return pd.dataframe 453 | """ 454 | 455 | r, msg = self._check_session() 456 | if not r: return (None, msg) 457 | 458 | rpc_params = {"task_id": task_id} 459 | 460 | data_format = self._get_format(format, "pandas") 461 | if data_format == "pandas": 462 | rpc_params["format"] = "columnset" 463 | 464 | cr = self._remote.call("oms.query_order", rpc_params) 465 | 466 | return utils.extract_result(cr, data_format=data_format, class_name="Order") 467 | 468 | def query_trade(self, task_id=-1, format=""): 469 | """ 470 | task_id: -1 -- all 471 | return pd.dataframe 472 | """ 473 | 474 | r, msg = self._check_session() 475 | if not r: return (None, msg) 476 | 477 | rpc_params = {"task_id": task_id} 478 | 479 | data_format = self._get_format(format, "pandas") 480 | if data_format == "pandas": 481 | rpc_params["format"] = "columnset" 482 | 483 | cr = self._remote.call("oms.query_trade", rpc_params) 484 | 485 | return utils.extract_result(cr, data_format=data_format, class_name="Trade") 486 | 487 | def query_portfolio(self, format=""): 488 | """ 489 | return pd.dataframe 490 | """ 491 | 492 | r, msg = self._check_session() 493 | if not r: return (None, msg) 494 | 495 | rpc_params = {} 496 | 497 | data_format = self._get_format(format, "pandas") 498 | if data_format == "pandas": 499 | rpc_params["format"] = "columnset" 500 | 501 | cr = self._remote.call("pms.query_portfolio", rpc_params) 502 | 503 | return utils.extract_result(cr, index_column="security", data_format=data_format, class_name="NetPosition") 504 | 505 | def goal_portfolio(self, positions, algo="", algo_param={}, userdata=""): 506 | """ 507 | positions format: 508 | [ {"security": "000001.SZ", "ref_price": 10.0, "size" : 100}, ...] 509 | return (result, message) 510 | if result is None, message contains error information 511 | """ 512 | 513 | r, msg = self._check_session() 514 | if not r: return (False, msg) 515 | 516 | if type(positions) is pd.core.frame.DataFrame : 517 | tmp = [] 518 | for i in range(0, len(positions)): 519 | tmp.append ({'security': positions.index[i], 520 | 'ref_price': float(positions['ref_price'][i]), 521 | "size" : int(positions['size'][i])}) 522 | positions = tmp 523 | 524 | rpc_params = {"positions" : positions, 525 | "algo" : algo, 526 | "algo_param" : json.dumps(algo_param), 527 | "user" : self._username, 528 | "userdata" : userdata} 529 | 530 | cr = self._remote.call("pms.goal_portfolio", rpc_params) 531 | return utils.extract_result(cr) 532 | 533 | def basket_order(self, orders, algo="", algo_param={}, userdata=""): 534 | """ 535 | orders format: 536 | [ {"security": "000001.SZ", "ref_price": 10.0, "inc_size" : 100}, ...] 537 | return (result, message) 538 | if result is None, message contains error information 539 | """ 540 | 541 | r, msg = self._check_session() 542 | if not r: return (False, msg) 543 | 544 | if type(orders) is pd.core.frame.DataFrame : 545 | tmp = [] 546 | for i in range(0, len(orders)): 547 | tmp.append ({'security': orders.index[i], 548 | 'ref_price': float(orders['ref_price'][i]), 549 | 'inc_size' : int(orders['inc_size'][i])}) 550 | orders = tmp 551 | 552 | rpc_params = {"orders": orders, 553 | "algo": algo, 554 | "algo_param": json.dumps(algo_param), 555 | "user": self._username, 556 | "userdata": userdata} 557 | 558 | cr = self._remote.call("pms.basket_order", rpc_params) 559 | return utils.extract_result(cr) 560 | 561 | def stop_portfolio(self): 562 | """ 563 | return (result, message) 564 | if result is None, message contains error information 565 | """ 566 | 567 | r, msg = self._check_session() 568 | if not r: return (False, msg) 569 | 570 | rpc_params = {} 571 | 572 | cr = self._remote.call("pms.stop_portfolio", rpc_params) 573 | return utils.extract_result(cr) 574 | 575 | def query_universe(self, format=""): 576 | 577 | r, msg = self._check_session() 578 | if not r: return (None, msg) 579 | 580 | rpc_params = {} 581 | data_format = self._get_format(format, "pandas") 582 | if data_format == "pandas": 583 | rpc_params["format"] = "columnset" 584 | 585 | cr = self._remote.call("oms.query_universe", rpc_params) 586 | 587 | return utils.extract_result(cr, data_format=data_format, class_name="UniverseItem") 588 | 589 | def set_heartbeat(self, interval, timeout): 590 | self._remote.set_hearbeat_options(interval, timeout) 591 | print("heartbeat_interval =", self._remote._heartbeat_interval, ", heartbeat_timeout =", 592 | self._remote._heartbeat_timeout) 593 | -------------------------------------------------------------------------------- /tradeapi/utils.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from __future__ import division 3 | from __future__ import print_function 4 | from __future__ import unicode_literals 5 | 6 | from builtins import * 7 | from collections import namedtuple 8 | import datetime as dt 9 | import pandas as pd 10 | import numpy as np 11 | 12 | long_nan = 9223372036854775807 13 | 14 | def is_long_nan(v): 15 | if v == long_nan: 16 | return True 17 | else: 18 | return False 19 | 20 | 21 | def to_nan(x): 22 | if is_long_nan(x): 23 | return np.nan 24 | else: 25 | return x 26 | 27 | 28 | def _to_date(row): 29 | date = int(row['DATE']) 30 | return pd.datetime(year=date // 10000, month=date // 100 % 100, day=date % 100) 31 | 32 | 33 | def _to_datetime(row): 34 | date = int(row['DATE']) 35 | time = int(row['TIME']) // 1000 36 | return pd.datetime(year=date // 10000, month=date // 100 % 100, day=date % 100, 37 | hour=time // 10000, minute=time // 100 % 100, second=time % 100) 38 | 39 | 40 | def _to_dataframe(cloumset, index_func=None, index_column=None): 41 | df = pd.DataFrame(cloumset) 42 | for col in df.columns: 43 | if df.dtypes.loc[col] == np.int64: 44 | df.loc[:, col] = df.loc[:, col].apply(to_nan) 45 | if index_func: 46 | df.index = df.apply(index_func, axis=1) 47 | elif index_column: 48 | df.index = df[index_column] 49 | del df.index.name 50 | 51 | return df 52 | 53 | 54 | def _error_to_str(error): 55 | if error: 56 | if 'message' in error: 57 | return str(error['error']) + "," + error['message'] 58 | else: 59 | return str(error['error']) + "," 60 | else: 61 | return "," 62 | 63 | 64 | def to_obj(class_name, data): 65 | try: 66 | if isinstance(data, (list, tuple)): 67 | result = [] 68 | for d in data: 69 | result.append(namedtuple(class_name, list(d.keys()))(*list(d.values()))) 70 | return result 71 | 72 | elif type(data) == dict: 73 | result = namedtuple(class_name, list(data.keys()))(*list(data.values())) 74 | return result 75 | else: 76 | return data 77 | except Exception as e: 78 | print(class_name, data, e) 79 | return data 80 | 81 | 82 | def to_date_int(date): 83 | if isinstance(date, str): 84 | t = dt.datetime.strptime(date, "%Y-%m-%d") 85 | date_int = t.year * 10000 + t.month * 100 + t.day 86 | return date_int 87 | elif isinstance(date, (int, np.integer)): 88 | return date 89 | else: 90 | return -1 91 | 92 | 93 | def to_time_int(time): 94 | if isinstance(time, str): 95 | t = dt.datetime.strptime(time, "%H:%M:%S") 96 | time_int = t.hour * 10000 + t.minute * 100 + t.second 97 | return time_int 98 | elif isinstance(time, (int, np.integer)): 99 | return time 100 | else: 101 | return -1 102 | 103 | 104 | def extract_result(cr, data_format="", index_column=None, class_name=""): 105 | """ 106 | format supports pandas, obj. 107 | """ 108 | 109 | err = _error_to_str(cr['error']) if 'error' in cr else None 110 | if 'result' in cr: 111 | if data_format == "pandas": 112 | if index_column: 113 | return (_to_dataframe(cr['result'], None, index_column), err) 114 | # if 'TIME' in cr['result']: 115 | # return (_to_dataframe(cr['result'], _to_datetime), err) 116 | # elif 'DATE' in cr['result']: 117 | # return (_to_dataframe(cr['result'], _to_date), err) 118 | else: 119 | return (_to_dataframe(cr['result']), err) 120 | 121 | elif data_format == "obj" and cr['result'] and class_name: 122 | r = cr['result'] 123 | if isinstance(r, (list, tuple)): 124 | result = [] 125 | for d in r: 126 | result.append(namedtuple(class_name, list(d.keys()))(*list(d.values()))) 127 | elif isinstance(r, dict): 128 | result = namedtuple(class_name, list(r.keys()))(*list(r.values())) 129 | else: 130 | result = r 131 | 132 | return (result, err) 133 | else: 134 | return (cr['result'], err) 135 | else: 136 | return (None, err) 137 | -------------------------------------------------------------------------------- /vnTrader/__init__.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | __version__ = '1.0' 4 | -------------------------------------------------------------------------------- /vnTrader/eventEngine.py: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | # 系统模块 4 | from __future__ import absolute_import, division, print_function, unicode_literals 5 | 6 | from builtins import * 7 | 8 | try: 9 | from queue import Queue, Empty 10 | except ImportError: 11 | from queue import Queue, Empty 12 | 13 | from threading import Thread 14 | from time import sleep 15 | from collections import defaultdict 16 | 17 | # 第三方模块 18 | # PyQt 4/5 compatibility 19 | try: 20 | from PyQt4.QtCore import QTimer 21 | except ImportError: 22 | from PyQt5.QtCore import QTimer 23 | 24 | # 自己开发的模块 25 | from eventType import * 26 | 27 | 28 | ######################################################################## 29 | class EventEngine(object): 30 | """ 31 | 事件驱动引擎 32 | 事件驱动引擎中所有的变量都设置为了私有,这是为了防止不小心 33 | 从外部修改了这些变量的值或状态,导致bug。 34 | 35 | 变量说明 36 | __queue:私有变量,事件队列 37 | __active:私有变量,事件引擎开关 38 | __thread:私有变量,事件处理线程 39 | __timer:私有变量,计时器 40 | __handlers:私有变量,事件处理函数字典 41 | 42 | 43 | 方法说明 44 | __run: 私有方法,事件处理线程连续运行用 45 | __process: 私有方法,处理事件,调用注册在引擎中的监听函数 46 | __onTimer:私有方法,计时器固定事件间隔触发后,向事件队列中存入计时器事件 47 | start: 公共方法,启动引擎 48 | stop:公共方法,停止引擎 49 | register:公共方法,向引擎中注册监听函数 50 | unregister:公共方法,向引擎中注销监听函数 51 | put:公共方法,向事件队列中存入新的事件 52 | 53 | 事件监听函数必须定义为输入参数仅为一个event对象,即: 54 | 55 | 函数 56 | def func(event) 57 | ... 58 | 59 | 对象方法 60 | def method(self, event) 61 | ... 62 | 63 | """ 64 | 65 | # ---------------------------------------------------------------------- 66 | def __init__(self): 67 | """初始化事件引擎""" 68 | # 事件队列 69 | self.__queue = Queue() 70 | 71 | # 事件引擎开关 72 | self.__active = False 73 | 74 | # 事件处理线程 75 | self.__thread = Thread(target=self.__run) 76 | 77 | # 计时器,用于触发计时器事件 78 | self.__timer = QTimer() 79 | self.__timer.timeout.connect(self.__onTimer) 80 | 81 | # 这里的__handlers是一个字典,用来保存对应的事件调用关系 82 | # 其中每个键对应的值是一个列表,列表中保存了对该事件进行监听的函数功能 83 | self.__handlers = defaultdict(list) 84 | 85 | # ---------------------------------------------------------------------- 86 | def __run(self): 87 | """引擎运行""" 88 | while self.__active == True: 89 | try: 90 | event = self.__queue.get(block=True, timeout=1) # 获取事件的阻塞时间设为1秒 91 | self.__process(event) 92 | except Empty: 93 | pass 94 | 95 | # ---------------------------------------------------------------------- 96 | def __process(self, event): 97 | """处理事件""" 98 | # 检查是否存在对该事件进行监听的处理函数 99 | if event.type_ in self.__handlers: 100 | # 若存在,则按顺序将事件传递给处理函数执行 101 | [handler(event) for handler in self.__handlers[event.type_]] 102 | 103 | # 以上语句为Python列表解析方式的写法,对应的常规循环写法为: 104 | # for handler in self.__handlers[event.type_]: 105 | # handler(event) 106 | 107 | # ---------------------------------------------------------------------- 108 | def __onTimer(self): 109 | """向事件队列中存入计时器事件""" 110 | # 创建计时器事件 111 | event = Event(type_=EVENT_TIMER) 112 | 113 | # 向队列中存入计时器事件 114 | self.put(event) 115 | 116 | # ---------------------------------------------------------------------- 117 | 118 | def start(self): 119 | """引擎启动""" 120 | # 将引擎设为启动 121 | self.__active = True 122 | 123 | # 启动事件处理线程 124 | self.__thread.start() 125 | 126 | # 启动计时器,计时器事件间隔默认设定为1秒 127 | self.__timer.start(1000) 128 | 129 | # ---------------------------------------------------------------------- 130 | def stop(self): 131 | """停止引擎""" 132 | # 将引擎设为停止 133 | self.__active = False 134 | 135 | # 停止计时器 136 | self.__timer.stop() 137 | 138 | # 等待事件处理线程退出 139 | self.__thread.join() 140 | 141 | # ---------------------------------------------------------------------- 142 | def register(self, type_, handler): 143 | """注册事件处理函数监听""" 144 | # 尝试获取该事件类型对应的处理函数列表,若无defaultDict会自动创建新的list 145 | handlerList = self.__handlers[type_] 146 | 147 | # 若要注册的处理器不在该事件的处理器列表中,则注册该事件 148 | if handler not in handlerList: 149 | handlerList.append(handler) 150 | 151 | # ---------------------------------------------------------------------- 152 | def unregister(self, type_, handler): 153 | """注销事件处理函数监听""" 154 | # 尝试获取该事件类型对应的处理函数列表,若无则忽略该次注销请求 155 | handlerList = self.__handlers[type_] 156 | 157 | # 如果该函数存在于列表中,则移除 158 | if handler in handlerList: 159 | handlerList.remove(handler) 160 | 161 | # 如果函数列表为空,则从引擎中移除该事件类型 162 | if not handlerList: 163 | del self.__handlers[type_] 164 | 165 | # ---------------------------------------------------------------------- 166 | def put(self, event): 167 | """向事件队列中存入事件""" 168 | self.__queue.put(event) 169 | 170 | 171 | ######################################################################## 172 | class EventEngine2(object): 173 | """ 174 | 计时器使用python线程的事件驱动引擎 175 | """ 176 | 177 | # ---------------------------------------------------------------------- 178 | def __init__(self): 179 | """初始化事件引擎""" 180 | # 事件队列 181 | self.__queue = Queue() 182 | 183 | # 事件引擎开关 184 | self.__active = False 185 | 186 | # 事件处理线程 187 | self.__thread = Thread(target=self.__run) 188 | 189 | # 计时器,用于触发计时器事件 190 | self.__timer = Thread(target=self.__runTimer) 191 | self.__timerActive = False # 计时器工作状态 192 | self.__timerSleep = 1 # 计时器触发间隔(默认1秒) 193 | 194 | # 这里的__handlers是一个字典,用来保存对应的事件调用关系 195 | # 其中每个键对应的值是一个列表,列表中保存了对该事件进行监听的函数功能 196 | self.__handlers = defaultdict(list) 197 | 198 | # ---------------------------------------------------------------------- 199 | def __run(self): 200 | """引擎运行""" 201 | while self.__active == True: 202 | try: 203 | event = self.__queue.get(block=True, timeout=1) # 获取事件的阻塞时间设为1秒 204 | self.__process(event) 205 | except Empty: 206 | pass 207 | 208 | # ---------------------------------------------------------------------- 209 | def __process(self, event): 210 | """处理事件""" 211 | # 检查是否存在对该事件进行监听的处理函数 212 | if event.type_ in self.__handlers: 213 | # 若存在,则按顺序将事件传递给处理函数执行 214 | [handler(event) for handler in self.__handlers[event.type_]] 215 | 216 | # 以上语句为Python列表解析方式的写法,对应的常规循环写法为: 217 | # for handler in self.__handlers[event.type_]: 218 | # handler(event) 219 | 220 | # ---------------------------------------------------------------------- 221 | def __runTimer(self): 222 | """运行在计时器线程中的循环函数""" 223 | while self.__timerActive: 224 | # 创建计时器事件 225 | event = Event(type_=EVENT_TIMER) 226 | 227 | # 向队列中存入计时器事件 228 | self.put(event) 229 | 230 | # 等待 231 | sleep(self.__timerSleep) 232 | 233 | # ---------------------------------------------------------------------- 234 | def start(self): 235 | """引擎启动""" 236 | # 将引擎设为启动 237 | self.__active = True 238 | 239 | # 启动事件处理线程 240 | self.__thread.start() 241 | 242 | # 启动计时器,计时器事件间隔默认设定为1秒 243 | self.__timerActive = True 244 | self.__timer.start() 245 | 246 | # ---------------------------------------------------------------------- 247 | def stop(self): 248 | """停止引擎""" 249 | # 将引擎设为停止 250 | self.__active = False 251 | 252 | # 停止计时器 253 | self.__timerActive = False 254 | self.__timer.join() 255 | 256 | # 等待事件处理线程退出 257 | self.__thread.join() 258 | 259 | # ---------------------------------------------------------------------- 260 | def register(self, type_, handler): 261 | """注册事件处理函数监听""" 262 | # 尝试获取该事件类型对应的处理函数列表,若无defaultDict会自动创建新的list 263 | handlerList = self.__handlers[type_] 264 | 265 | # 若要注册的处理器不在该事件的处理器列表中,则注册该事件 266 | if handler not in handlerList: 267 | handlerList.append(handler) 268 | 269 | # ---------------------------------------------------------------------- 270 | def unregister(self, type_, handler): 271 | """注销事件处理函数监听""" 272 | # 尝试获取该事件类型对应的处理函数列表,若无则忽略该次注销请求 273 | handlerList = self.__handlers[type_] 274 | 275 | # 如果该函数存在于列表中,则移除 276 | if handler in handlerList: 277 | handlerList.remove(handler) 278 | 279 | # 如果函数列表为空,则从引擎中移除该事件类型 280 | if not handlerList: 281 | del self.__handlers[type_] 282 | 283 | # ---------------------------------------------------------------------- 284 | 285 | def put(self, event): 286 | """向事件队列中存入事件""" 287 | self.__queue.put(event) 288 | 289 | 290 | ######################################################################## 291 | class Event(object): 292 | """事件对象""" 293 | 294 | # ---------------------------------------------------------------------- 295 | def __init__(self, type_=None): 296 | """Constructor""" 297 | self.type_ = type_ # 事件类型 298 | self.dict_ = {} # 字典用于保存具体的事件数据 299 | 300 | 301 | # ---------------------------------------------------------------------- 302 | def test(): 303 | """测试函数""" 304 | from datetime import datetime 305 | try: 306 | from PyQt5.QtCore import QCoreApplication 307 | except ImportError: 308 | from PyQt4.QtCore import QCoreApplication 309 | 310 | def simpletest(event): 311 | print(u'处理每秒触发的计时器事件:%s' % str(datetime.now())) 312 | 313 | app = QCoreApplication('VnTrader') 314 | 315 | ee = EventEngine2() 316 | ee.register(EVENT_TIMER, simpletest) 317 | ee.start() 318 | 319 | app.exec_() 320 | 321 | 322 | # 直接运行脚本可以进行测试 323 | if __name__ == '__main__': 324 | test() 325 | -------------------------------------------------------------------------------- /vnTrader/eventType.py: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | ''' 4 | 本文件仅用于存放对于事件类型常量的定义。 5 | 6 | 由于python中不存在真正的常量概念,因此选择使用全大写的变量名来代替常量。 7 | 这里设计的命名规则以EVENT_前缀开头。 8 | 9 | 常量的内容通常选择一个能够代表真实意义的字符串(便于理解)。 10 | 11 | 建议将所有的常量定义放在该文件中,便于检查是否存在重复的现象。 12 | ''' 13 | from __future__ import print_function 14 | from builtins import str 15 | 16 | # 系统相关 17 | EVENT_TIMER = 'eTimer' # 计时器事件,每隔1秒发送一次 18 | EVENT_LOG = 'eLog' # 日志事件,全局通用 19 | EVENT_TITLE = 'eTitle' 20 | 21 | # Gateway相关 22 | EVENT_TICK = 'eTick.' # TICK行情事件,可后接具体的symbol 23 | EVENT_TRADE = 'eTrade.' # 成交回报事件 24 | EVENT_ORDER = 'eOrder.' # 报单回报事件 25 | EVENT_POSITION = 'ePosition.' # 持仓回报事件 26 | EVENT_ACCOUNT = 'eAccount.' # 账户回报事件 27 | EVENT_CONTRACT = 'eContract.' # 合约基础信息回报事件 28 | EVENT_ERROR = 'eError.' # 错误回报事件 29 | 30 | EVENT_CLEAR = 'eClear.' # 成交回报事件 31 | EVENT_CONTRACT_CLEAR = 'eContractClear.' # 合约基础信息回报事件 32 | 33 | # CTA模块相关 34 | #EVENT_CTA_LOG = 'eCtaLog' # CTA相关的日志事件 35 | #EVENT_CTA_STRATEGY = 'eCtaStrategy.' # CTA策略状态变化事件 36 | 37 | # 行情记录模块相关 38 | EVENT_DATARECORDER_LOG = 'eDataRecorderLog' # 行情记录日志更新事件 39 | 40 | # Wind接口相关 41 | EVENT_WIND_CONNECTREQ = 'eWindConnectReq' # Wind接口请求连接事件 42 | 43 | EVENT_TASK = 'eTask' 44 | 45 | #---------------------------------------------------------------------- 46 | def test(): 47 | """检查是否存在内容重复的常量定义""" 48 | check_dict = {} 49 | 50 | global_dict = globals() 51 | 52 | for key, value in list(global_dict.items()): 53 | if '__' not in key: # 不检查python内置对象 54 | if value in check_dict: 55 | check_dict[value].append(key) 56 | else: 57 | check_dict[value] = [key] 58 | 59 | for key, value in list(check_dict.items()): 60 | if len(value)>1: 61 | print(u'存在重复的常量定义:' + str(key)) 62 | for name in value: 63 | print(name) 64 | print('') 65 | 66 | print(u'测试完毕') 67 | 68 | 69 | # 直接运行脚本可以进行测试 70 | if __name__ == '__main__': 71 | test() -------------------------------------------------------------------------------- /vnTrader/gateway/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/gateway/__init__.py -------------------------------------------------------------------------------- /vnTrader/gateway/quantosGateway.py: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | from __future__ import absolute_import 4 | from builtins import str 5 | from builtins import range 6 | from builtins import object 7 | 8 | import time 9 | import json 10 | import traceback 11 | 12 | from vtGateway import * 13 | from jaqs.trade.tradeapi import TradeApi 14 | from jaqs.data import DataApi 15 | from .quantosLoginWidget import QuantOSLoginEngine 16 | 17 | 18 | def check_return_error(res, err_msg): 19 | if res is None: 20 | return False 21 | else: 22 | return True 23 | 24 | # functions for generating algo parameters 25 | 26 | #---------------------------------------------------------------------- 27 | def generateEmptyParams(security, urgency): 28 | """generate empty dict""" 29 | return {} 30 | 31 | #---------------------------------------------------------------------- 32 | def generateVwapParams(security, urgency): 33 | """generate params for vwap algo""" 34 | params = {} 35 | params['urgency'] = {security: urgency} # bid + 0.25 * bid_ask_spread 36 | params['participate_rate'] = {security: 0.1} 37 | params['price_range_factor'] = 0.1 38 | params['lifetime'] = 600000 # 10 minutes 39 | return params 40 | 41 | #---------------------------------------------------------------------- 42 | def generateTwapParams(security, urgency): 43 | """generate params for twap algo""" 44 | params = {} 45 | params['urgency'] = {security: urgency} # bid + 0.25 * bid_ask_spread 46 | params['price_range_factor'] = 0.1 47 | params['cycle'] = 1000 48 | params['lifetime'] = 60000 # 10 minutes 49 | return params 50 | 51 | # 以下为一些VT类型和quantos类型的映射字典 52 | # 价格类型映射 53 | priceTypeMap = {} 54 | priceTypeMap[PRICETYPE_LIMITPRICE] = ('', generateEmptyParams) 55 | priceTypeMap[PRICETYPE_VWAP] = ('vwap', generateVwapParams) 56 | priceTypeMap[PRICETYPE_TWAP] = ('twap', generateTwapParams) 57 | 58 | # 动作印射 59 | actionMap = {} 60 | actionMap[(DIRECTION_LONG, OFFSET_OPEN)] = "Buy" 61 | actionMap[(DIRECTION_SHORT, OFFSET_OPEN)] = "Short" 62 | actionMap[(DIRECTION_LONG, OFFSET_CLOSE)] = "Cover" 63 | actionMap[(DIRECTION_SHORT, OFFSET_CLOSE)] = "Sell" 64 | actionMap[(DIRECTION_LONG, OFFSET_CLOSEYESTERDAY)] = "CoverYesterday" 65 | actionMap[(DIRECTION_SHORT, OFFSET_CLOSEYESTERDAY)] = "SellYesterday" 66 | actionMap[(DIRECTION_LONG, OFFSET_CLOSETODAY)] = "CoverToday" 67 | actionMap[(DIRECTION_SHORT, OFFSET_CLOSETODAY)] = "SellToday" 68 | actionMap[(DIRECTION_LONG, OFFSET_UNKNOWN)] = "AutoLong" 69 | actionMap[(DIRECTION_SHORT, OFFSET_UNKNOWN)] = "AutoShort" 70 | actionMapReverse = {v: k for k, v in list(actionMap.items())} 71 | 72 | # 交易所类型映射 73 | exchangeMap = {} 74 | exchangeMap[EXCHANGE_CFFEX] = 'CFE' 75 | exchangeMap[EXCHANGE_SHFE] = 'SHF' 76 | exchangeMap[EXCHANGE_CZCE] = 'CZC' 77 | exchangeMap[EXCHANGE_DCE] = 'DCE' 78 | exchangeMap[EXCHANGE_SSE] = 'SH' 79 | exchangeMap[EXCHANGE_SZSE] = 'SZ' 80 | exchangeMap[EXCHANGE_SGE] = 'SGE' 81 | exchangeMap[EXCHANGE_CSI] = 'CSI' 82 | exchangeMap[EXCHANGE_HKS] = 'HKS' 83 | exchangeMap[EXCHANGE_HKH] = 'HKH' 84 | exchangeMap[EXCHANGE_JZ] = 'JZ' 85 | exchangeMap[EXCHANGE_SPOT] = 'SPOT' 86 | exchangeMap[EXCHANGE_IB] = 'IB' 87 | exchangeMap[EXCHANGE_FX] = 'FX' 88 | exchangeMap[EXCHANGE_INE] = 'INE' 89 | 90 | exchangeMapReverse = {v:k for k,v in list(exchangeMap.items())} 91 | 92 | # 持仓类型映射 93 | sideMap = {} 94 | sideMap[DIRECTION_LONG] = 'Long' 95 | sideMap[DIRECTION_SHORT] = 'Short' 96 | sideMapReverse = {v:k for k,v in list(sideMap.items())} 97 | 98 | # 产品类型映射 99 | productClassMapReverse = {} 100 | productClassMapReverse[1] = PRODUCT_EQUITY 101 | productClassMapReverse[3] = PRODUCT_EQUITY 102 | productClassMapReverse[4] = PRODUCT_EQUITY 103 | productClassMapReverse[5] = PRODUCT_EQUITY 104 | productClassMapReverse[8] = PRODUCT_BOND 105 | productClassMapReverse[17] = PRODUCT_BOND 106 | productClassMapReverse[101] = PRODUCT_FUTURES 107 | productClassMapReverse[102] = PRODUCT_FUTURES 108 | productClassMapReverse[103] = PRODUCT_FUTURES 109 | 110 | # 委托状态映射 111 | statusMapReverse = {} 112 | statusMapReverse['New'] = STATUS_UNKNOWN 113 | statusMapReverse['Accepted'] = STATUS_NOTTRADED 114 | statusMapReverse['Cancelled'] = STATUS_CANCELLED 115 | statusMapReverse['Filled'] = STATUS_ALLTRADED 116 | statusMapReverse['Rejected'] = STATUS_REJECTED 117 | 118 | 119 | 120 | ######################################################################## 121 | class QuantOSGateway(VtGateway): 122 | #---------------------------------------------------------------------- 123 | def __init__(self, eventengine, dataengine, gatewayName='quantos'): 124 | """Constructor""" 125 | super(QuantOSGateway, self).__init__(eventengine, gatewayName) 126 | 127 | f = open("setting/VT_setting.json", "r") 128 | setting = json.load(f) 129 | 130 | self.mdApi = QuantOSMdApi(self, setting) # 行情 131 | self.tdApi = QuantOSTdApi(self, setting) # 交易 132 | 133 | self.qryEnabled = False # 是否要启动循环查询 134 | self.loginWindow = QuantOSLoginEngine(self, setting) 135 | 136 | self.dataengine = dataengine 137 | 138 | def connect(self): 139 | self.loginWindow.show() 140 | 141 | def getStrategyList(self, userName, password): 142 | return self.tdApi.getStrategyList(userName, password) 143 | 144 | #---------------------------------------------------------------------- 145 | def login(self, username, token, selectedStrat): 146 | """连接""" 147 | try: 148 | # 创建行情和交易接口对象 149 | self.mdApi.connect(username, token) 150 | self.tdApi.connect(username, token, selectedStrat) 151 | 152 | # 初始化并启动查询 153 | self.initQuery() 154 | except: 155 | traceback.print_exc() 156 | 157 | #---------------------------------------------------------------------- 158 | def subscribe(self, symbols): 159 | """订阅行情""" 160 | self.mdApi.subscribe(symbols) 161 | 162 | def unsubscribeAll(self): 163 | """订阅行情""" 164 | pass 165 | #self.mdApi.unsubscribeAll() 166 | 167 | #---------------------------------------------------------------------- 168 | def sendOrder(self, orderReq): 169 | """发单""" 170 | self.tdApi.sendOrder(orderReq) 171 | 172 | #---------------------------------------------------------------------- 173 | def sendBasketOrder(self, basketOrderReq): 174 | """""" 175 | return self.tdApi.sendBasketOrder(basketOrderReq) 176 | 177 | #---------------------------------------------------------------------- 178 | def cancelOrder(self, cancelOrderReq): 179 | """撤单""" 180 | self.tdApi.cancelOrder(cancelOrderReq) 181 | 182 | #---------------------------------------------------------------------- 183 | def qryAccount(self): 184 | """查询账户资金""" 185 | self.tdApi.qryAccount() 186 | 187 | #---------------------------------------------------------------------- 188 | def qryPosition(self): 189 | """查询持仓""" 190 | self.tdApi.qryPosition() 191 | 192 | #---------------------------------------------------------------------- 193 | def close(self): 194 | """关闭""" 195 | pass 196 | #self.tdApi.close() 197 | 198 | #---------------------------------------------------------------------- 199 | def initQuery(self): 200 | """初始化连续查询""" 201 | if self.qryEnabled: 202 | # 需要循环的查询函数列表 203 | self.qryFunctionList = [self.qryPosition, self.qryAccount] 204 | 205 | self.qryCount = 0 # 查询触发倒计时 206 | self.qryTrigger = 2 # 查询触发点 207 | self.qryNextFunction = 0 # 上次运行的查询函数索引 208 | 209 | self.startQuery() 210 | 211 | #---------------------------------------------------------------------- 212 | def query(self, event): 213 | """注册到事件处理引擎上的查询函数""" 214 | self.qryCount += 1 215 | 216 | if self.qryCount > self.qryTrigger: 217 | # 清空倒计时 218 | self.qryCount = 0 219 | 220 | # 执行查询函数 221 | function = self.qryFunctionList[self.qryNextFunction] 222 | function() 223 | 224 | # 计算下次查询函数的索引,如果超过了列表长度,则重新设为0 225 | self.qryNextFunction += 1 226 | if self.qryNextFunction == len(self.qryFunctionList): 227 | self.qryNextFunction = 0 228 | 229 | #---------------------------------------------------------------------- 230 | def startQuery(self): 231 | """启动连续查询""" 232 | self.eventEngine.register(EVENT_TIMER, self.query) 233 | 234 | #---------------------------------------------------------------------- 235 | def setQryEnabled(self, qryEnabled): 236 | """设置是否要启动循环查询""" 237 | self.qryEnabled = qryEnabled 238 | 239 | 240 | ######################################################################## 241 | class QuantOSTdApi(object): 242 | """交易API实现""" 243 | 244 | #---------------------------------------------------------------------- 245 | def __init__(self, gateway, setting): 246 | """Constructor""" 247 | super(QuantOSTdApi, self).__init__() 248 | 249 | self.gateway = gateway # gateway对象 250 | self.gatewayName = gateway.gatewayName # gateway对象名称 251 | 252 | self.api = None 253 | self.current_stratid = None 254 | self.current_user = None 255 | self.current_strats = None 256 | 257 | self.TradeApi = TradeApi 258 | 259 | self.setting = setting 260 | 261 | self.orderPricetypeDict = {} # key:vtOrderID, value:algoType 262 | 263 | def changeTitle(self): 264 | self.gateway.changeTitle(self.current_user, self.current_stratid) 265 | #---------------------------------------------------------------------- 266 | 267 | def clearAll(self): 268 | self.gateway.clearPosition() 269 | self.gateway.clearContract() 270 | self.gateway.unsubscribeAll() 271 | 272 | def loadContracts(self): 273 | """""" 274 | pf, msg = self.api.query_universe() 275 | 276 | if not check_return_error(pf, msg): 277 | self.writeLog(u'查询合约失败,错误信息:{}'.format(msg)) 278 | return False 279 | 280 | symbols = '' 281 | for instcode in pf['security']: 282 | if len(instcode) > 0: 283 | symbols += str(instcode) 284 | symbols += "," 285 | 286 | instruments = self.gateway.mdApi.queryInstruments(symbols) 287 | 288 | for k, d in instruments.items(): 289 | contract = VtContractData() 290 | contract.gatewayName = self.gatewayName 291 | 292 | instcode = d['symbol'] 293 | 294 | code, jzExchange = instcode.split('.') 295 | contract.symbol = instcode 296 | contract.exchange = exchangeMapReverse[jzExchange] 297 | contract.name = d['name'] 298 | contract.priceTick = d['pricetick'] 299 | contract.size = d['multiplier'] 300 | contract.lotsize = d['buylot'] 301 | contract.productClass = productClassMapReverse.get(int(d['inst_type']), PRODUCT_UNKNOWN) 302 | 303 | self.gateway.onContract(contract) 304 | 305 | # do not subscribe 306 | #self.gateway.subscribe(symbols) 307 | 308 | self.writeLog(u'查询合约信息成功') 309 | return True 310 | 311 | def subscribePositionSymbols(self): 312 | """""" 313 | pf, msg = self.api.query_position() 314 | 315 | if not check_return_error(pf, msg): 316 | self.writeLog(u'查询持仓失败,错误信息:{}'.format(msg)) 317 | return False 318 | 319 | symbols = '' 320 | for instcode in pf['security']: 321 | symbols += str(instcode) 322 | symbols += "," 323 | 324 | quotes = self.gateway.mdApi.queryQuotes(symbols) 325 | for k, d in quotes.items(): 326 | tick = VtTickData() 327 | tick.gatewayName = self.gatewayName 328 | 329 | symbol = d['symbol'] 330 | code, jzExchange = instcode.split('.') 331 | tick.symbol = symbol 332 | tick.exchange = exchangeMapReverse[jzExchange] 333 | tick.name = symbol 334 | 335 | tick.openPrice = d['open'] 336 | tick.highPrice = d['high'] 337 | tick.lowPrice = d['low'] 338 | tick.volume = d['volume'] 339 | tick.volchg = 0 340 | tick.turnover = d['turnover'] if 'turnover' in d else 0 341 | tick.lastPrice = d['last'] 342 | 343 | tick.openInterest = d['oi'] if 'oi' in d else 0 344 | tick.preClosePrice = d['preclose'] if 'preclose' in d else 0 345 | tick.date = str(d['date']) 346 | 347 | t = str(d['time']) 348 | t = t.rjust(9, '0') 349 | tick.time = '%s:%s:%s.%s' %(t[0:2],t[2:4],t[4:6],t[6:]) 350 | 351 | tick.bidPrice1 = d['bidprice1'] 352 | tick.bidPrice2 = d['bidprice2'] 353 | tick.bidPrice3 = d['bidprice3'] 354 | tick.bidPrice4 = d['bidprice4'] 355 | tick.bidPrice5 = d['bidprice5'] 356 | 357 | tick.askPrice1 = d['askprice1'] 358 | tick.askPrice2 = d['askprice2'] 359 | tick.askPrice3 = d['askprice3'] 360 | tick.askPrice4 = d['askprice4'] 361 | tick.askPrice5 = d['askprice5'] 362 | 363 | tick.bidVolume1 = d['bidvolume1'] 364 | tick.bidVolume2 = d['bidvolume2'] 365 | tick.bidVolume3 = d['bidvolume3'] 366 | tick.bidVolume4 = d['bidvolume4'] 367 | tick.bidVolume5 = d['bidvolume5'] 368 | 369 | tick.askVolume1 = d['askvolume1'] 370 | tick.askVolume2 = d['askvolume2'] 371 | tick.askVolume3 = d['askvolume3'] 372 | tick.askVolume4 = d['askvolume4'] 373 | tick.askVolume5 = d['askvolume5'] 374 | 375 | tick.upperLimit = d['limit_up'] if 'limit_up' in d else 0 376 | tick.lowerLimit = d['limit_down'] if 'limit_down' in d else 0 377 | 378 | self.gateway.onTick(tick) 379 | 380 | self.gateway.subscribe(symbols) 381 | 382 | self.writeLog(u'订阅合约信息成功') 383 | return True 384 | 385 | #---------------------------------------------------------------------- 386 | def onOrderStatus(self, data): 387 | """委托信息推送""" 388 | order = VtOrderData() 389 | order.gatewayName = self.gatewayName 390 | 391 | code, exchange = data['security'].split('.') 392 | order.symbol = data['security'] 393 | order.name = data['security'] 394 | order.exchange = exchangeMapReverse.get(exchange, EXCHANGE_UNKNOWN) 395 | 396 | order.orderID = str(data['entrust_no']) 397 | order.taskID = str(data['task_id']) 398 | order.vtOrderID = order.orderID 399 | 400 | order.direction, order.offset = actionMapReverse.get(data['entrust_action'], (DIRECTION_UNKNOWN, OFFSET_UNKNOWN)) 401 | order.totalVolume = data['entrust_size'] 402 | order.tradedVolume = data['fill_size'] 403 | order.price = data['entrust_price'] 404 | order.status = statusMapReverse.get(data['order_status']) 405 | 406 | # addtional info 407 | order.tradePrice = data['fill_price'] 408 | 409 | t = str(data['entrust_time']) 410 | t = t.rjust(6, '0') 411 | order.orderTime = '%s:%s:%s' %(t[0:2],t[2:4],t[4:]) 412 | 413 | #if order.vtOrderID in self.orderPricetypeDict: 414 | #order.priceType = self.orderPricetypeDict[order.vtOrderID] 415 | order.priceType = data['algo'] 416 | 417 | self.gateway.onOrder(order) 418 | 419 | #---------------------------------------------------------------------- 420 | def onTaskStatus(self, data): 421 | """""" 422 | task = TaskData() 423 | task.taskId = str(data['task_id']) 424 | task.taskStatus = data['status'] 425 | 426 | event = Event(EVENT_TASK) 427 | event.dict_['data'] = task 428 | 429 | self.gateway.eventEngine.put(event) 430 | 431 | #---------------------------------------------------------------------- 432 | def onTrade(self, data): 433 | """成交信息推送""" 434 | trade = VtTradeData() 435 | trade.gatewayName = self.gatewayName 436 | 437 | code, jzExchange = data['security'].split('.') 438 | trade.symbol = data['security'] 439 | trade.name = data['security'] 440 | trade.exchange = exchangeMapReverse.get(jzExchange, EXCHANGE_UNKNOWN) 441 | 442 | trade.direction, trade.offset = actionMapReverse.get(data['entrust_action'], (DIRECTION_UNKNOWN, OFFSET_UNKNOWN)) 443 | 444 | trade.tradeID = str(data['fill_no']) 445 | trade.vtTradeID = str(data['fill_no']) 446 | 447 | trade.orderID = str(data['entrust_no']) 448 | trade.vtOrderID = trade.orderID 449 | trade.taskID = str(data['task_id']) 450 | 451 | trade.price = data['fill_price'] 452 | trade.volume = data['fill_size'] 453 | 454 | t = str(data['fill_time']) 455 | t = t.rjust(6, '0') 456 | trade.tradeTime = '%s:%s:%s' %(t[0:2],t[2:4],t[4:]) 457 | 458 | self.gateway.onTrade(trade) 459 | 460 | #---------------------------------------------------------------------- 461 | def onConnection(self, data): 462 | """""" 463 | self.writeLog(u'连接状态更新:%s' %data) 464 | 465 | def getStrategyList(self, username, token): 466 | 467 | if self.api is None: 468 | tdAddress = self.setting['tdAddress'] 469 | self.api = self.TradeApi(tdAddress) 470 | 471 | # 登录 472 | info, msg = self.api.login(username, token) 473 | 474 | if check_return_error(info, msg): 475 | self.writeLog(u'登录成功') 476 | self.current_strats = info['strategies'] 477 | self.current_user = info['username'] 478 | return info['strategies'] 479 | # if info is None: 480 | else: 481 | self.writeLog(u'登录失败,错误信息:%s' % msg) 482 | self.api = None 483 | return None 484 | else: 485 | self.writeLog(u'已经登录') 486 | return self.current_strats 487 | 488 | #---------------------------------------------------------------------- 489 | def connect(self, username, password, strategyid): 490 | """初始化连接""" 491 | # 创建API对象并绑定回调函数 492 | if self.api is None: 493 | tdAddress = self.setting['tdAddress'] 494 | self.api = self.TradeApi(tdAddress) 495 | # 登录 496 | info, msg = self.api.login(username, password) 497 | 498 | else: 499 | info = 0 500 | 501 | if info is None: 502 | self.writeLog(u'登录失败,错误信息:%s' %msg) 503 | self.api = None 504 | else: 505 | if info == 0: 506 | self.writeLog(u'已经登录') 507 | else: 508 | self.writeLog(u'登录成功') 509 | self.current_user = username 510 | 511 | if self.current_stratid is None: 512 | self.api.set_ordstatus_callback(self.onOrderStatus) 513 | self.api.set_trade_callback(self.onTrade) 514 | self.api.set_task_callback(self.onTaskStatus) 515 | self.api.set_connection_callback(self.onConnection) 516 | # 使用某个策略号 517 | 518 | if self.current_stratid != strategyid: 519 | result, msg = self.api.use_strategy(int(strategyid)) 520 | self.current_stratid = strategyid 521 | 522 | if check_return_error(result, msg): 523 | self.writeLog(u'选定策略账户%s成功' %result) 524 | # sleep for 1 second and then query data 525 | time.sleep(1) 526 | self.changeTitle() 527 | self.clearAll() 528 | self.loadContracts() 529 | self.subscribePositionSymbols() 530 | self.qryOrder() 531 | self.qryTrade() 532 | else: 533 | self.writeLog(u'选定策略账户失败,错误信息:%s' %msg) 534 | else: 535 | time.sleep(1) 536 | self.clearAll() 537 | self.loadContracts() 538 | self.subscribePositionSymbols() 539 | self.qryOrder() 540 | self.qryTrade() 541 | 542 | #---------------------------------------------------------------------- 543 | def close(self): 544 | """关闭""" 545 | pass 546 | 547 | #---------------------------------------------------------------------- 548 | def writeLog(self, logContent): 549 | """记录日志""" 550 | log = VtLogData() 551 | log.gatewayName = self.gatewayName 552 | log.logContent = logContent 553 | self.gateway.onLog(log) 554 | 555 | #---------------------------------------------------------------------- 556 | def sendOrder(self, orderReq): 557 | """发单""" 558 | security = '.'.join([orderReq.symbol, exchangeMap.get(orderReq.exchange, '')]) 559 | urgency = orderReq.urgency 560 | algo, paramsFunction = priceTypeMap[orderReq.priceType] 561 | 562 | if len(orderReq.offset) > 0: 563 | action = actionMap.get((orderReq.direction, orderReq.offset), '') 564 | if len(algo) > 0: 565 | taskid, msg = self.api.place_order(security, action, orderReq.price, int(orderReq.volume), algo, paramsFunction(security, urgency)) 566 | else: 567 | taskid, msg = self.api.place_order(security, action, orderReq.price, int(orderReq.volume)) 568 | 569 | if not check_return_error(taskid, msg): 570 | self.writeLog(u'委托失败,错误信息:%s' %msg) 571 | else: 572 | inc_size = int(orderReq.volume) if orderReq.direction == DIRECTION_LONG else int(orderReq.volume) * -1 573 | 574 | taskid, msg = self.api.batch_order([{"security": security, "price":orderReq.price, "size":inc_size}], algo, paramsFunction(security, urgency)) 575 | if not check_return_error(taskid, msg): 576 | self.writeLog(u'篮子委托失败,错误信息:%s' %msg) 577 | #---------------------------------------------------------------------- 578 | def sendBasketOrder(self, req): 579 | """ 580 | when sending basket order, taskid is returned instead of vtOrderID 581 | """ 582 | taskid, msg = self.api.basket_order(req.positions, req.algo, req.params) 583 | 584 | # return result 585 | if not check_return_error(taskid, msg): 586 | self.writeLog(u'篮子委托失败,错误信息:%s' %msg) 587 | return None 588 | else: 589 | return str(taskid) 590 | 591 | #---------------------------------------------------------------------- 592 | def cancelOrder(self, cancelOrderReq): 593 | """撤单""" 594 | if not self.api: 595 | return 596 | 597 | result, msg = self.api.cancel_order(cancelOrderReq.orderID) 598 | 599 | if not check_return_error(result, msg): 600 | self.writeLog(u'撤单失败,错误信息:%s' %msg) 601 | 602 | #---------------------------------------------------------------------- 603 | def qryPosition(self): 604 | """查询持仓""" 605 | df, msg = self.api.query_position() 606 | 607 | if not check_return_error(df, msg): 608 | self.writeLog(u'查询持仓失败,错误信息:%s' %msg) 609 | return False 610 | 611 | for index, data in df.iterrows(): 612 | position = VtPositionData() 613 | position.gatewayName = self.gatewayName 614 | 615 | code, jzExchange = data['security'].split('.') 616 | position.symbol = data['security'] 617 | position.name = data['security'] 618 | position.exchange = exchangeMapReverse.get(jzExchange, EXCHANGE_UNKNOWN) 619 | 620 | position.direction = sideMapReverse.get(data['side'], DIRECTION_UNKNOWN) 621 | position.vtPositionName = '.'.join([data['security'], position.direction]) 622 | 623 | position.price = data['cost_price'] 624 | position.ydPosition = data['pre_size'] 625 | position.tdPosition = data['today_size'] 626 | position.position = data['current_size'] 627 | position.frozen = data['frozen_size'] 628 | 629 | position.commission = data['commission'] 630 | position.enable = data['enable_size'] 631 | position.want = data['want_size'] 632 | position.initPosition = data['init_size'] 633 | position.trading = data['trading_pnl'] 634 | position.holding = data['holding_pnl'] 635 | position.last = data['last_price'] 636 | 637 | if (position.position > 0): 638 | contract = self.gateway.dataengine.getContract(position.symbol) 639 | if (contract != None): 640 | position.mktval = data['last_price'] * position.position * contract.size 641 | else: 642 | position.mktval = 0.0 643 | 644 | self.gateway.onPosition(position) 645 | 646 | return True 647 | 648 | def qryAccount(self): 649 | 650 | df, msg = self.api.query_account() 651 | 652 | if not check_return_error(df, msg): 653 | self.writeLog(u'查询资金失败,错误信息:%s' %msg) 654 | return False 655 | 656 | for index, data in df.iterrows(): 657 | account = VtAccountData() 658 | 659 | account.accountID = data['id'] 660 | account.type = data['type'] 661 | account.vtAccountID = str(data['id']) + "_" + data['type'] 662 | 663 | account.frozen_balance = data['frozen_balance'] 664 | account.enable_balance = data['enable_balance'] 665 | account.float_pnl = data['float_pnl'] 666 | account.init_balance = data['init_balance'] 667 | account.deposit_balance = data['deposit_balance'] 668 | account.holding_pnl = data['holding_pnl'] 669 | account.close_pnl = data['close_pnl'] 670 | account.margin = data['margin'] 671 | account.trading_pnl = data['trading_pnl'] 672 | 673 | self.gateway.onAccount(account) 674 | 675 | #---------------------------------------------------------------------- 676 | def qryOrder(self): 677 | """查询委托""" 678 | df, msg = self.api.query_order() 679 | 680 | if not check_return_error(df, msg): 681 | self.writeLog(u'查询委托失败,错误信息:%s' %msg) 682 | return False 683 | 684 | for index, data in df.iterrows(): 685 | self.onOrderStatus(data) 686 | 687 | self.writeLog(u'查询委托完成') 688 | return True 689 | 690 | #---------------------------------------------------------------------- 691 | def qryTrade(self): 692 | """查询成交""" 693 | df, msg = self.api.query_trade() 694 | 695 | if not check_return_error(df, msg): 696 | self.writeLog(u'查询成交失败,错误信息:%s' %msg) 697 | return False 698 | 699 | for index, data in df.iterrows(): 700 | self.onTrade(data) 701 | 702 | self.writeLog(u'查询成交完成') 703 | return True 704 | 705 | 706 | 707 | ######################################################################## 708 | class QuantOSMdApi(object): 709 | #---------------------------------------------------------------------- 710 | def __init__(self, gateway, setting): 711 | """Constructor""" 712 | super(QuantOSMdApi, self).__init__() 713 | 714 | self.gateway = gateway 715 | self.gatewayName = gateway.gatewayName 716 | 717 | self.api = None 718 | 719 | self.fields = "OPEN,CLOSE,HIGH,LOW,LAST,\ 720 | VOLUME,TURNOVER,OI,PRECLOSE,TIME,DATE,\ 721 | ASKPRICE1,ASKPRICE2,ASKPRICE3,ASKPRICE4,ASKPRICE5,\ 722 | BIDPRICE1,BIDPRICE2,BIDPRICE3,BIDPRICE4,BIDPRICE5,\ 723 | ASKVOLUME1,ASKVOLUME2,ASKVOLUME3,ASKVOLUME4,ASKVOLUME5,\ 724 | BIDVOLUME1,BIDVOLUME2,BIDVOLUME3,BIDVOLUME4,BIDVOLUME5,\ 725 | LIMIT_UP,LIMIT_DOWN" 726 | 727 | self.fields = self.fields.replace(' ', '').lower() 728 | 729 | self.DataApi = DataApi 730 | 731 | self.setting = setting 732 | 733 | #---------------------------------------------------------------------- 734 | def onMarketData(self, key, data): 735 | """行情推送""" 736 | tick = VtTickData() 737 | tick.gatewayName = self.gatewayName 738 | 739 | try: 740 | l = data['symbol'].split('.') 741 | tick.symbol = data['symbol'] 742 | tick.name = data['symbol'] 743 | tick.exchange = exchangeMapReverse.get(l[1], EXCHANGE_UNKNOWN) 744 | 745 | tick.openPrice = data['open'] 746 | tick.highPrice = data['high'] 747 | tick.lowPrice = data['low'] 748 | tick.volume = data['volume'] 749 | tick.volchg = 0 750 | tick.turnover = data['turnover'] if 'turnover' in data else 0 751 | tick.lastPrice = data['last'] 752 | 753 | tick.openInterest = data['oi'] if 'oi' in data else 0 754 | tick.preClosePrice = data['preclose'] if 'preclose' in data else 0 755 | tick.date = str(data['date']) 756 | 757 | t = str(data['time']) 758 | t = t.rjust(9, '0') 759 | tick.time = '%s:%s:%s.%s' %(t[0:2],t[2:4],t[4:6],t[6:]) 760 | 761 | tick.bidPrice1 = data['bidprice1'] 762 | tick.bidPrice2 = data['bidprice2'] 763 | tick.bidPrice3 = data['bidprice3'] 764 | tick.bidPrice4 = data['bidprice4'] 765 | tick.bidPrice5 = data['bidprice5'] 766 | 767 | tick.askPrice1 = data['askprice1'] 768 | tick.askPrice2 = data['askprice2'] 769 | tick.askPrice3 = data['askprice3'] 770 | tick.askPrice4 = data['askprice4'] 771 | tick.askPrice5 = data['askprice5'] 772 | 773 | tick.bidVolume1 = data['bidvolume1'] 774 | tick.bidVolume2 = data['bidvolume2'] 775 | tick.bidVolume3 = data['bidvolume3'] 776 | tick.bidVolume4 = data['bidvolume4'] 777 | tick.bidVolume5 = data['bidvolume5'] 778 | 779 | tick.askVolume1 = data['askvolume1'] 780 | tick.askVolume2 = data['askvolume2'] 781 | tick.askVolume3 = data['askvolume3'] 782 | tick.askVolume4 = data['askvolume4'] 783 | tick.askVolume5 = data['askvolume5'] 784 | 785 | tick.upperLimit = data['limit_up'] if 'limit_up' in data else 0 786 | tick.lowerLimit = data['limit_down'] if 'limit_down' in data else 0 787 | 788 | self.gateway.onTick(tick) 789 | except Exception as e: 790 | self.writeLog(u'行情更新失败,错误信息:%s' % str(e)) 791 | 792 | #---------------------------------------------------------------------- 793 | def connect(self, username, token): 794 | """ todo """ 795 | """连接""" 796 | if self.api is None: 797 | 798 | self.api = self.DataApi(self.setting['mdAddress']) 799 | 800 | #登录 801 | info, msg = self.api.login(username, token) 802 | if check_return_error(info, msg): 803 | self.writeLog(u'行情连接成功') 804 | else: 805 | self.writeLog(u'行情连接失败,错误信息:%s' % msg) 806 | else: 807 | self.writeLog(u'行情已经连接') 808 | 809 | def unsubscribeAll(self): 810 | subscribed, msg = self.api.unsubscribe() 811 | 812 | #---------------------------------------------------------------------- 813 | def subscribe(self, symbols): 814 | """订阅""" 815 | subscribed, msg = self.api.subscribe(symbols, fields=self.fields, func=self.onMarketData) 816 | if not check_return_error(subscribed, msg): 817 | self.writeLog(u'行情订阅失败,错误信息:%s' % msg) 818 | 819 | #---------------------------------------------------------------------- 820 | def writeLog(self, logContent): 821 | """记录日志""" 822 | log = VtLogData() 823 | log.gatewayName = self.gatewayName 824 | log.logContent = logContent 825 | self.gateway.onLog(log) 826 | 827 | #---------------------------------------------------------------------- 828 | def queryInstruments(self, instcodes): 829 | 830 | if instcodes == "": 831 | df, msg = self.api.query("jz.instrumentInfo", fields="symbol, name, buylot, selllot, pricetick, multiplier, inst_type", filter="market=SH,SZ,SHF,CZC,DCE,CFE&status=1&inst_type=1,2,3,4,5,101,102,103") 832 | else: 833 | p = "symbol=%s" %instcodes 834 | df, msg = self.api.query("jz.instrumentInfo", fields="symbol, name, buylot, selllot, pricetick, multiplier, inst_type", filter=p) 835 | 836 | d = {} 837 | if df is None: 838 | return {} 839 | 840 | for index, row in df.iterrows(): 841 | k = row['symbol'] 842 | v = row 843 | d[k] = v 844 | return d 845 | 846 | #---------------------------------------------------------------------- 847 | def queryQuotes(self, instcodes): 848 | 849 | item_count = 50 850 | 851 | codelist = instcodes.split(",") 852 | 853 | d = {} 854 | 855 | symbol = '' 856 | for idx in range(len(codelist)): 857 | symbol += codelist[idx] 858 | symbol += ',' 859 | 860 | if (idx == item_count): 861 | df, msg = self.api.quote(fields=self.fields, symbol=symbol) 862 | 863 | for index, row in df.iterrows(): 864 | k = row['symbol'] 865 | v = row 866 | d[k] = v 867 | 868 | idx = 0 869 | 870 | if idx>0: 871 | df, msg = self.api.quote(fields=self.fields, symbol=symbol) 872 | 873 | for index, row in df.iterrows(): 874 | k = row['symbol'] 875 | v = row 876 | d[k] = v 877 | 878 | return d 879 | 880 | 881 | ######################################################################## 882 | class TaskData(object): 883 | """""" 884 | 885 | #---------------------------------------------------------------------- 886 | def __init__(self): 887 | """Constructor""" 888 | self.taskId = EMPTY_STRING 889 | self.taskStatus = EMPTY_UNICODE 890 | 891 | 892 | 893 | 894 | -------------------------------------------------------------------------------- /vnTrader/gateway/quantosLoginWidget.py: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | from builtins import str 4 | 5 | # PyQt 4/5 compatibility 6 | try: 7 | from PyQt4.QtGui import QFrame, QWidget, QLineEdit, QTextEdit, QComboBox, QGridLayout,QLabel,QMessageBox,QPushButton,QVBoxLayout,QHBoxLayout 8 | except ImportError: 9 | from PyQt5.QtWidgets import QFrame, QWidget, QLineEdit, QTextEdit, QComboBox, QGridLayout,QLabel,QMessageBox,QPushButton,QHBoxLayout,QVBoxLayout 10 | 11 | 12 | class LoginLine(QFrame): 13 | """水平分割线""" 14 | 15 | #---------------------------------------------------------------------- 16 | def __init__(self): 17 | """Constructor""" 18 | super(LoginLine, self).__init__() 19 | self.setFrameShape(self.HLine) 20 | self.setFrameShadow(self.Sunken) 21 | 22 | ######################################################################## 23 | class QuantOSLoginEngine(QWidget): 24 | """风控引擎的管理组件""" 25 | 26 | #---------------------------------------------------------------------- 27 | def __init__(self, gateway, setting, parent=None): 28 | """Constructor""" 29 | super(QuantOSLoginEngine, self).__init__(parent) 30 | 31 | self.setting = setting 32 | self.gateway = gateway 33 | self.connectionMap = {} 34 | 35 | self.initUi() 36 | 37 | #---------------------------------------------------------------------- 38 | def initUi(self): 39 | """初始化界面""" 40 | self.setWindowTitle(u'登录') 41 | 42 | # 设置界面 43 | self.userName = QLineEdit() 44 | self.password = QTextEdit() 45 | self.comboStrategy = QComboBox() 46 | 47 | grid = QGridLayout() 48 | grid.addWidget(LoginLine(), 1, 0, 1, 2) 49 | grid.addWidget(QLabel(u'用户名'), 2, 0) 50 | grid.addWidget(self.userName, 2, 1) 51 | grid.addWidget(QLabel(u'令牌'), 3, 0) 52 | grid.addWidget(self.password, 3, 1) 53 | grid.addWidget(LoginLine(), 4, 0, 1, 2) 54 | grid.addWidget(QLabel(u'策略'), 5, 0) 55 | grid.addWidget(self.comboStrategy, 5, 1) 56 | grid.addWidget(LoginLine(), 6, 0, 1, 2) 57 | 58 | self.buttonCancel = QPushButton(u'取消') 59 | self.buttonConfirm = QPushButton(u'确认') 60 | hbox = QHBoxLayout() 61 | hbox.addWidget(self.buttonConfirm) 62 | hbox.addWidget(self.buttonCancel) 63 | self.buttonConfirm.setDefault(True) 64 | 65 | vbox = QVBoxLayout() 66 | vbox.addLayout(grid) 67 | vbox.addLayout(hbox) 68 | self.setLayout(vbox) 69 | 70 | # 设为固定大小 71 | self.setFixedSize(self.sizeHint()) 72 | 73 | self.buttonCancel.clicked.connect(self.close) 74 | self.buttonConfirm.clicked.connect(self.login) 75 | self.userName.returnPressed.connect(self.password.setFocus) 76 | 77 | # init username & token 78 | username = self.setting['username'] 79 | token = self.setting['token'] 80 | 81 | self.userName.setText(username) 82 | self.password.setText(token) 83 | 84 | def login(self): 85 | selectedStrat = self.comboStrategy.currentText() 86 | if selectedStrat is not None and len(selectedStrat) > 0: 87 | username = str(self.userName.text()).strip() 88 | password = str(self.password.toPlainText()).strip() 89 | if len(username) <= 0 or len(password) <= 0: 90 | QMessageBox.warning(self, u'登录', u'输入用户名和密码') 91 | else: 92 | self.close() 93 | self.gateway.login(username, password, selectedStrat) 94 | else: 95 | self.connect() 96 | 97 | def connect(self): 98 | userName = str(self.userName.text()).strip() 99 | password = str(self.password.toPlainText()).strip() 100 | if len(userName) <= 0 or len(password) <= 0: 101 | QMessageBox.warning(self, u'获取策略', u'输入用户名和密码') 102 | else: 103 | strategyList = self.gateway.getStrategyList(userName, password) 104 | if strategyList is not None and len(strategyList) > 0: 105 | self.comboStrategy.clear() 106 | strategyList_sl = [] 107 | for strategy in strategyList: 108 | strategyList_sl.append(str(strategy)) 109 | strategyList_sl.sort() 110 | self.comboStrategy.addItems(strategyList_sl) 111 | self.userName.setEnabled(False) 112 | self.password.setEnabled(False) 113 | 114 | else: 115 | QMessageBox.warning(self, u'获取策略', u'无法获取相关策略') 116 | self.comboStrategy.clear() 117 | 118 | self.comboStrategy.setFocus() 119 | 120 | -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) <2013-2014> 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | # THE SOFTWARE. 24 | # 25 | """ 26 | Initialise the QDarkStyleSheet module when used with python. 27 | 28 | This modules provides a function to transparently load the stylesheets 29 | with the correct rc file. 30 | """ 31 | import logging 32 | import platform 33 | 34 | 35 | __version__ = "2.3.0" 36 | 37 | 38 | def _logger(): 39 | return logging.getLogger('qdarkstyle') 40 | 41 | 42 | def load_stylesheet(pyside=True): 43 | """ 44 | Loads the stylesheet. Takes care of importing the rc module. 45 | 46 | :param pyside: True to load the pyside rc file, False to load the PyQt rc file 47 | 48 | :return the stylesheet string 49 | """ 50 | # Smart import of the rc file 51 | if pyside: 52 | import qdarkstyle.pyside_style_rc 53 | else: 54 | import qdarkstyle.pyqt_style_rc 55 | 56 | # Load the stylesheet content from resources 57 | if not pyside: 58 | # PyQt 4/5 compatibility 59 | try: 60 | from PyQt4.QtCore import QFile, QTextStream 61 | except ImportError: 62 | from PyQt5.QtCore import QFile, QTextStream 63 | else: 64 | from PySide.QtCore import QFile, QTextStream 65 | 66 | f = QFile(":qdarkstyle/style.qss") 67 | if not f.exists(): 68 | _logger().error("Unable to load stylesheet, file not found in " 69 | "resources") 70 | return "" 71 | else: 72 | f.open(QFile.ReadOnly | QFile.Text) 73 | ts = QTextStream(f) 74 | stylesheet = ts.readAll() 75 | if platform.system().lower() == 'darwin': # see issue #12 on github 76 | mac_fix = ''' 77 | QDockWidget::title 78 | { 79 | background-color: #31363b; 80 | text-align: center; 81 | height: 12px; 82 | } 83 | ''' 84 | stylesheet += mac_fix 85 | return stylesheet 86 | 87 | 88 | def load_stylesheet_pyqt5(): 89 | """ 90 | Loads the stylesheet for use in a pyqt5 application. 91 | 92 | :param pyside: True to load the pyside rc file, False to load the PyQt rc file 93 | 94 | :return the stylesheet string 95 | """ 96 | # Smart import of the rc file 97 | import qdarkstyle.pyqt5_style_rc 98 | 99 | # Load the stylesheet content from resources 100 | from PyQt5.QtCore import QFile, QTextStream 101 | 102 | f = QFile(":qdarkstyle/style.qss") 103 | if not f.exists(): 104 | _logger().error("Unable to load stylesheet, file not found in " 105 | "resources") 106 | return "" 107 | else: 108 | f.open(QFile.ReadOnly | QFile.Text) 109 | ts = QTextStream(f) 110 | stylesheet = ts.readAll() 111 | if platform.system().lower() == 'darwin': # see issue #12 on github 112 | mac_fix = ''' 113 | QDockWidget::title 114 | { 115 | background-color: #31363b; 116 | text-align: center; 117 | height: 12px; 118 | } 119 | ''' 120 | stylesheet += mac_fix 121 | return stylesheet 122 | -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/compile_qrc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # The MIT License (MIT) 5 | # 6 | # Copyright (c) <2013-2014> 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in 16 | # all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | # THE SOFTWARE. 25 | # 26 | """ 27 | Utility scripts to compile the qrc file. The script will 28 | attempt to compile the qrc file using the following tools: 29 | - rcc 30 | - pyside-rcc 31 | - pyrcc4 32 | 33 | Delete the compiled files that you don't want to use 34 | manually after running this script. 35 | """ 36 | from __future__ import print_function 37 | import os 38 | 39 | 40 | def compile_all(): 41 | """ 42 | Compile style.qrc using rcc, pyside-rcc and pyrcc4 43 | """ 44 | # print("Compiling for Qt: style.qrc -> style.rcc") 45 | # os.system("rcc style.qrc -o style.rcc") 46 | print("Compiling for PyQt4: style.qrc -> pyqt_style_rc.py") 47 | os.system("pyrcc4 -py3 style.qrc -o pyqt_style_rc.py") 48 | print("Compiling for PyQt5: style.qrc -> pyqt5_style_rc.py") 49 | os.system("pyrcc5 style.qrc -o pyqt5_style_rc.py") 50 | print("Compiling for PySide: style.qrc -> pyside_style_rc.py") 51 | os.system("pyside-rcc -py3 style.qrc -o pyside_style_rc.py") 52 | 53 | 54 | if __name__ == "__main__": 55 | compile_all() 56 | -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/Hmovetoolbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/Hmovetoolbar.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/Hsepartoolbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/Hsepartoolbar.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/Vmovetoolbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/Vmovetoolbar.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/Vsepartoolbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/Vsepartoolbar.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/branch_closed-on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/branch_closed-on.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/branch_closed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/branch_closed.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/branch_open-on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/branch_open-on.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/branch_open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/branch_open.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/checkbox_checked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/checkbox_checked.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/checkbox_checked_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/checkbox_checked_disabled.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/checkbox_checked_focus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/checkbox_checked_focus.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/checkbox_indeterminate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/checkbox_indeterminate.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/checkbox_indeterminate_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/checkbox_indeterminate_disabled.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/checkbox_indeterminate_focus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/checkbox_indeterminate_focus.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/checkbox_unchecked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/checkbox_unchecked.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/checkbox_unchecked_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/checkbox_unchecked_disabled.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/checkbox_unchecked_focus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/checkbox_unchecked_focus.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/close-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/close-hover.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/close-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/close-pressed.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/close.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/down_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/down_arrow.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/down_arrow_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/down_arrow_disabled.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/left_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/left_arrow.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/left_arrow_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/left_arrow_disabled.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/radio_checked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/radio_checked.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/radio_checked_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/radio_checked_disabled.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/radio_checked_focus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/radio_checked_focus.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/radio_unchecked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/radio_unchecked.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/radio_unchecked_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/radio_unchecked_disabled.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/radio_unchecked_focus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/radio_unchecked_focus.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/right_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/right_arrow.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/right_arrow_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/right_arrow_disabled.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/sizegrip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/sizegrip.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/stylesheet-branch-end.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/stylesheet-branch-end.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/stylesheet-branch-more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/stylesheet-branch-more.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/stylesheet-vline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/stylesheet-vline.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/transparent.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/undock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/undock.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/up_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/up_arrow.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/rc/up_arrow_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/qdarkstyle/rc/up_arrow_disabled.png -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/style.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | rc/up_arrow_disabled.png 4 | rc/Hmovetoolbar.png 5 | rc/stylesheet-branch-end.png 6 | rc/branch_closed-on.png 7 | rc/stylesheet-vline.png 8 | rc/branch_closed.png 9 | rc/branch_open-on.png 10 | rc/transparent.png 11 | rc/right_arrow_disabled.png 12 | rc/sizegrip.png 13 | rc/close.png 14 | rc/close-hover.png 15 | rc/close-pressed.png 16 | rc/down_arrow.png 17 | rc/Vmovetoolbar.png 18 | rc/left_arrow.png 19 | rc/stylesheet-branch-more.png 20 | rc/up_arrow.png 21 | rc/right_arrow.png 22 | rc/left_arrow_disabled.png 23 | rc/Hsepartoolbar.png 24 | rc/branch_open.png 25 | rc/Vsepartoolbar.png 26 | rc/down_arrow_disabled.png 27 | rc/undock.png 28 | rc/checkbox_checked_disabled.png 29 | rc/checkbox_checked_focus.png 30 | rc/checkbox_checked.png 31 | rc/checkbox_indeterminate.png 32 | rc/checkbox_indeterminate_focus.png 33 | rc/checkbox_unchecked_disabled.png 34 | rc/checkbox_unchecked_focus.png 35 | rc/checkbox_unchecked.png 36 | rc/radio_checked_disabled.png 37 | rc/radio_checked_focus.png 38 | rc/radio_checked.png 39 | rc/radio_unchecked_disabled.png 40 | rc/radio_unchecked_focus.png 41 | rc/radio_unchecked.png 42 | 43 | 44 | style.qss 45 | 46 | 47 | -------------------------------------------------------------------------------- /vnTrader/qdarkstyle/style.qss: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) <2013-2014> 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | QToolTip 25 | { 26 | border: 1px solid #76797C; 27 | background-color: rgb(90, 102, 117);; 28 | color: white; 29 | padding: 5px; 30 | opacity: 200; 31 | } 32 | 33 | QWidget 34 | { 35 | color: #eff0f1; 36 | background-color: #31363b; 37 | selection-background-color:#3daee9; 38 | selection-color: #eff0f1; 39 | background-clip: border; 40 | border-image: none; 41 | border: 0px transparent black; 42 | outline: 0; 43 | } 44 | 45 | QWidget:item:hover 46 | { 47 | background-color: #3daee9; 48 | color: #eff0f1; 49 | } 50 | 51 | QWidget:item:selected 52 | { 53 | background-color: #3daee9; 54 | } 55 | 56 | QCheckBox 57 | { 58 | spacing: 5px; 59 | outline: none; 60 | color: #eff0f1; 61 | margin-bottom: 2px; 62 | } 63 | 64 | QCheckBox:disabled 65 | { 66 | color: #76797C; 67 | } 68 | 69 | QCheckBox::indicator, 70 | QGroupBox::indicator 71 | { 72 | width: 18px; 73 | height: 18px; 74 | } 75 | QGroupBox::indicator 76 | { 77 | margin-left: 2px; 78 | } 79 | 80 | QCheckBox::indicator:unchecked 81 | { 82 | image: url(:/qss_icons/rc/checkbox_unchecked.png); 83 | } 84 | 85 | QCheckBox::indicator:unchecked:hover, 86 | QCheckBox::indicator:unchecked:focus, 87 | QCheckBox::indicator:unchecked:pressed, 88 | QGroupBox::indicator:unchecked:hover, 89 | QGroupBox::indicator:unchecked:focus, 90 | QGroupBox::indicator:unchecked:pressed 91 | { 92 | border: none; 93 | image: url(:/qss_icons/rc/checkbox_unchecked_focus.png); 94 | } 95 | 96 | QCheckBox::indicator:checked 97 | { 98 | image: url(:/qss_icons/rc/checkbox_checked.png); 99 | } 100 | 101 | QCheckBox::indicator:checked:hover, 102 | QCheckBox::indicator:checked:focus, 103 | QCheckBox::indicator:checked:pressed, 104 | QGroupBox::indicator:checked:hover, 105 | QGroupBox::indicator:checked:focus, 106 | QGroupBox::indicator:checked:pressed 107 | { 108 | border: none; 109 | image: url(:/qss_icons/rc/checkbox_checked_focus.png); 110 | } 111 | 112 | 113 | QCheckBox::indicator:indeterminate 114 | { 115 | image: url(:/qss_icons/rc/checkbox_indeterminate.png); 116 | } 117 | 118 | QCheckBox::indicator:indeterminate:focus, 119 | QCheckBox::indicator:indeterminate:hover, 120 | QCheckBox::indicator:indeterminate:pressed 121 | { 122 | image: url(:/qss_icons/rc/checkbox_indeterminate_focus.png); 123 | } 124 | 125 | QCheckBox::indicator:checked:disabled, 126 | QGroupBox::indicator:checked:disabled 127 | { 128 | image: url(:/qss_icons/rc/checkbox_checked_disabled.png); 129 | } 130 | 131 | QCheckBox::indicator:unchecked:disabled, 132 | QGroupBox::indicator:unchecked:disabled 133 | { 134 | image: url(:/qss_icons/rc/checkbox_unchecked_disabled.png); 135 | } 136 | 137 | QRadioButton 138 | { 139 | spacing: 5px; 140 | outline: none; 141 | color: #eff0f1; 142 | margin-bottom: 2px; 143 | } 144 | 145 | QRadioButton:disabled 146 | { 147 | color: #76797C; 148 | } 149 | QRadioButton::indicator 150 | { 151 | width: 21px; 152 | height: 21px; 153 | } 154 | 155 | QRadioButton::indicator:unchecked 156 | { 157 | image: url(:/qss_icons/rc/radio_unchecked.png); 158 | } 159 | 160 | 161 | QRadioButton::indicator:unchecked:hover, 162 | QRadioButton::indicator:unchecked:focus, 163 | QRadioButton::indicator:unchecked:pressed 164 | { 165 | border: none; 166 | outline: none; 167 | image: url(:/qss_icons/rc/radio_unchecked_focus.png); 168 | } 169 | 170 | QRadioButton::indicator:checked 171 | { 172 | border: none; 173 | outline: none; 174 | image: url(:/qss_icons/rc/radio_checked.png); 175 | } 176 | 177 | QRadioButton::indicator:checked:hover, 178 | QRadioButton::indicator:checked:focus, 179 | QRadioButton::indicator:checked:pressed 180 | { 181 | border: none; 182 | outline: none; 183 | image: url(:/qss_icons/rc/radio_checked_focus.png); 184 | } 185 | 186 | QRadioButton::indicator:checked:disabled 187 | { 188 | outline: none; 189 | image: url(:/qss_icons/rc/radio_checked_disabled.png); 190 | } 191 | 192 | QRadioButton::indicator:unchecked:disabled 193 | { 194 | image: url(:/qss_icons/rc/radio_unchecked_disabled.png); 195 | } 196 | 197 | 198 | QMenuBar 199 | { 200 | background-color: #31363b; 201 | color: #eff0f1; 202 | } 203 | 204 | QMenuBar::item 205 | { 206 | background: transparent; 207 | } 208 | 209 | QMenuBar::item:selected 210 | { 211 | background: transparent; 212 | border: 1px solid #76797C; 213 | } 214 | 215 | QMenuBar::item:pressed 216 | { 217 | border: 1px solid #76797C; 218 | background-color: #3daee9; 219 | color: #eff0f1; 220 | margin-bottom:-1px; 221 | padding-bottom:1px; 222 | } 223 | 224 | QMenu 225 | { 226 | border: 1px solid #76797C; 227 | color: #eff0f1; 228 | margin: 2px; 229 | } 230 | 231 | QMenu::icon 232 | { 233 | margin: 5px; 234 | } 235 | 236 | QMenu::item 237 | { 238 | padding: 5px 30px 5px 30px; 239 | margin-left: 5px; 240 | border: 1px solid transparent; /* reserve space for selection border */ 241 | } 242 | 243 | QMenu::item:selected 244 | { 245 | color: #eff0f1; 246 | } 247 | 248 | QMenu::separator { 249 | height: 2px; 250 | background: lightblue; 251 | margin-left: 10px; 252 | margin-right: 5px; 253 | } 254 | 255 | QMenu::indicator { 256 | width: 18px; 257 | height: 18px; 258 | } 259 | 260 | /* non-exclusive indicator = check box style indicator 261 | (see QActionGroup::setExclusive) */ 262 | QMenu::indicator:non-exclusive:unchecked { 263 | image: url(:/qss_icons/rc/checkbox_unchecked.png); 264 | } 265 | 266 | QMenu::indicator:non-exclusive:unchecked:selected { 267 | image: url(:/qss_icons/rc/checkbox_unchecked_disabled.png); 268 | } 269 | 270 | QMenu::indicator:non-exclusive:checked { 271 | image: url(:/qss_icons/rc/checkbox_checked.png); 272 | } 273 | 274 | QMenu::indicator:non-exclusive:checked:selected { 275 | image: url(:/qss_icons/rc/checkbox_checked_disabled.png); 276 | } 277 | 278 | /* exclusive indicator = radio button style indicator (see QActionGroup::setExclusive) */ 279 | QMenu::indicator:exclusive:unchecked { 280 | image: url(:/qss_icons/rc/radio_unchecked.png); 281 | } 282 | 283 | QMenu::indicator:exclusive:unchecked:selected { 284 | image: url(:/qss_icons/rc/radio_unchecked_disabled.png); 285 | } 286 | 287 | QMenu::indicator:exclusive:checked { 288 | image: url(:/qss_icons/rc/radio_checked.png); 289 | } 290 | 291 | QMenu::indicator:exclusive:checked:selected { 292 | image: url(:/qss_icons/rc/radio_checked_disabled.png); 293 | } 294 | 295 | QMenu::right-arrow { 296 | margin: 5px; 297 | image: url(:/qss_icons/rc/right_arrow.png) 298 | } 299 | 300 | 301 | QWidget:disabled 302 | { 303 | color: #454545; 304 | background-color: #31363b; 305 | } 306 | 307 | QAbstractItemView 308 | { 309 | alternate-background-color: #31363b; 310 | color: #eff0f1; 311 | border: 1px solid 3A3939; 312 | border-radius: 2px; 313 | } 314 | 315 | QWidget:focus, QMenuBar:focus 316 | { 317 | border: 1px solid #3daee9; 318 | } 319 | 320 | QTabWidget:focus, QCheckBox:focus, QRadioButton:focus, QSlider:focus 321 | { 322 | border: none; 323 | } 324 | 325 | QLineEdit 326 | { 327 | background-color: #232629; 328 | padding: 5px; 329 | border-style: solid; 330 | border: 1px solid #76797C; 331 | border-radius: 2px; 332 | color: #eff0f1; 333 | } 334 | 335 | QGroupBox { 336 | border:1px solid #76797C; 337 | border-radius: 2px; 338 | margin-top: 20px; 339 | } 340 | 341 | QGroupBox::title { 342 | subcontrol-origin: margin; 343 | subcontrol-position: top center; 344 | padding-left: 10px; 345 | padding-right: 10px; 346 | padding-top: 10px; 347 | } 348 | 349 | QAbstractScrollArea 350 | { 351 | border-radius: 2px; 352 | border: 1px solid #76797C; 353 | background-color: transparent; 354 | } 355 | 356 | QScrollBar:horizontal 357 | { 358 | height: 15px; 359 | margin: 3px 15px 3px 15px; 360 | border: 1px transparent #2A2929; 361 | border-radius: 4px; 362 | background-color: #2A2929; 363 | } 364 | 365 | QScrollBar::handle:horizontal 366 | { 367 | background-color: #605F5F; 368 | min-width: 5px; 369 | border-radius: 4px; 370 | } 371 | 372 | QScrollBar::add-line:horizontal 373 | { 374 | margin: 0px 3px 0px 3px; 375 | border-image: url(:/qss_icons/rc/right_arrow_disabled.png); 376 | width: 10px; 377 | height: 10px; 378 | subcontrol-position: right; 379 | subcontrol-origin: margin; 380 | } 381 | 382 | QScrollBar::sub-line:horizontal 383 | { 384 | margin: 0px 3px 0px 3px; 385 | border-image: url(:/qss_icons/rc/left_arrow_disabled.png); 386 | height: 10px; 387 | width: 10px; 388 | subcontrol-position: left; 389 | subcontrol-origin: margin; 390 | } 391 | 392 | QScrollBar::add-line:horizontal:hover,QScrollBar::add-line:horizontal:on 393 | { 394 | border-image: url(:/qss_icons/rc/right_arrow.png); 395 | height: 10px; 396 | width: 10px; 397 | subcontrol-position: right; 398 | subcontrol-origin: margin; 399 | } 400 | 401 | 402 | QScrollBar::sub-line:horizontal:hover, QScrollBar::sub-line:horizontal:on 403 | { 404 | border-image: url(:/qss_icons/rc/left_arrow.png); 405 | height: 10px; 406 | width: 10px; 407 | subcontrol-position: left; 408 | subcontrol-origin: margin; 409 | } 410 | 411 | QScrollBar::up-arrow:horizontal, QScrollBar::down-arrow:horizontal 412 | { 413 | background: none; 414 | } 415 | 416 | 417 | QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal 418 | { 419 | background: none; 420 | } 421 | 422 | QScrollBar:vertical 423 | { 424 | background-color: #2A2929; 425 | width: 15px; 426 | margin: 15px 3px 15px 3px; 427 | border: 1px transparent #2A2929; 428 | border-radius: 4px; 429 | } 430 | 431 | QScrollBar::handle:vertical 432 | { 433 | background-color: #605F5F; 434 | min-height: 5px; 435 | border-radius: 4px; 436 | } 437 | 438 | QScrollBar::sub-line:vertical 439 | { 440 | margin: 3px 0px 3px 0px; 441 | border-image: url(:/qss_icons/rc/up_arrow_disabled.png); 442 | height: 10px; 443 | width: 10px; 444 | subcontrol-position: top; 445 | subcontrol-origin: margin; 446 | } 447 | 448 | QScrollBar::add-line:vertical 449 | { 450 | margin: 3px 0px 3px 0px; 451 | border-image: url(:/qss_icons/rc/down_arrow_disabled.png); 452 | height: 10px; 453 | width: 10px; 454 | subcontrol-position: bottom; 455 | subcontrol-origin: margin; 456 | } 457 | 458 | QScrollBar::sub-line:vertical:hover,QScrollBar::sub-line:vertical:on 459 | { 460 | 461 | border-image: url(:/qss_icons/rc/up_arrow.png); 462 | height: 10px; 463 | width: 10px; 464 | subcontrol-position: top; 465 | subcontrol-origin: margin; 466 | } 467 | 468 | 469 | QScrollBar::add-line:vertical:hover, QScrollBar::add-line:vertical:on 470 | { 471 | border-image: url(:/qss_icons/rc/down_arrow.png); 472 | height: 10px; 473 | width: 10px; 474 | subcontrol-position: bottom; 475 | subcontrol-origin: margin; 476 | } 477 | 478 | QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical 479 | { 480 | background: none; 481 | } 482 | 483 | 484 | QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical 485 | { 486 | background: none; 487 | } 488 | 489 | QTextEdit 490 | { 491 | background-color: #232629; 492 | color: #eff0f1; 493 | border: 1px solid #76797C; 494 | } 495 | 496 | QPlainTextEdit 497 | { 498 | background-color: #232629;; 499 | color: #eff0f1; 500 | border-radius: 2px; 501 | border: 1px solid #76797C; 502 | } 503 | 504 | QHeaderView::section 505 | { 506 | background-color: #76797C; 507 | color: #eff0f1; 508 | padding: 5px; 509 | border: 1px solid #76797C; 510 | } 511 | 512 | QSizeGrip { 513 | image: url(:/qss_icons/rc/sizegrip.png); 514 | width: 12px; 515 | height: 12px; 516 | } 517 | 518 | 519 | QMainWindow::separator 520 | { 521 | background-color: #31363b; 522 | color: white; 523 | padding-left: 4px; 524 | spacing: 2px; 525 | border: 1px dashed #76797C; 526 | } 527 | 528 | QMainWindow::separator:hover 529 | { 530 | 531 | background-color: #787876; 532 | color: white; 533 | padding-left: 4px; 534 | border: 1px solid #76797C; 535 | spacing: 2px; 536 | } 537 | 538 | 539 | QMenu::separator 540 | { 541 | height: 1px; 542 | background-color: #76797C; 543 | color: white; 544 | padding-left: 4px; 545 | margin-left: 10px; 546 | margin-right: 5px; 547 | } 548 | 549 | 550 | QFrame 551 | { 552 | border-radius: 2px; 553 | border: 1px solid #76797C; 554 | } 555 | 556 | QFrame[frameShape="0"] 557 | { 558 | border-radius: 2px; 559 | border: 1px transparent #76797C; 560 | } 561 | 562 | QStackedWidget 563 | { 564 | border: 1px transparent black; 565 | } 566 | 567 | QToolBar { 568 | border: 1px transparent #393838; 569 | background: 1px solid #31363b; 570 | font-weight: bold; 571 | } 572 | 573 | QToolBar::handle:horizontal { 574 | image: url(:/qss_icons/rc/Hmovetoolbar.png); 575 | } 576 | QToolBar::handle:vertical { 577 | image: url(:/qss_icons/rc/Vmovetoolbar.png); 578 | } 579 | QToolBar::separator:horizontal { 580 | image: url(:/qss_icons/rc/Hsepartoolbar.png); 581 | } 582 | QToolBar::separator:vertical { 583 | image: url(:/qss_icons/rc/Vsepartoolbar.png); 584 | } 585 | QToolButton#qt_toolbar_ext_button { 586 | background: #58595a 587 | } 588 | 589 | QPushButton 590 | { 591 | color: #eff0f1; 592 | background-color: #31363b; 593 | border-width: 1px; 594 | border-color: #76797C; 595 | border-style: solid; 596 | padding: 5px; 597 | border-radius: 2px; 598 | outline: none; 599 | } 600 | 601 | QPushButton:disabled 602 | { 603 | background-color: #31363b; 604 | border-width: 1px; 605 | border-color: #454545; 606 | border-style: solid; 607 | padding-top: 5px; 608 | padding-bottom: 5px; 609 | padding-left: 10px; 610 | padding-right: 10px; 611 | border-radius: 2px; 612 | color: #454545; 613 | } 614 | 615 | QPushButton:focus { 616 | background-color: #3daee9; 617 | color: white; 618 | } 619 | 620 | QPushButton:pressed 621 | { 622 | background-color: #3daee9; 623 | padding-top: -15px; 624 | padding-bottom: -17px; 625 | } 626 | 627 | QComboBox 628 | { 629 | selection-background-color: #3daee9; 630 | border-style: solid; 631 | border: 1px solid #76797C; 632 | border-radius: 2px; 633 | padding: 5px; 634 | min-width: 75px; 635 | } 636 | 637 | QPushButton:checked{ 638 | background-color: #76797C; 639 | border-color: #6A6969; 640 | } 641 | 642 | QComboBox:hover,QPushButton:hover,QAbstractSpinBox:hover,QLineEdit:hover,QTextEdit:hover,QPlainTextEdit:hover,QAbstractView:hover,QTreeView:hover 643 | { 644 | border: 1px solid #3daee9; 645 | color: #eff0f1; 646 | } 647 | 648 | QComboBox:on 649 | { 650 | padding-top: 3px; 651 | padding-left: 4px; 652 | selection-background-color: #4a4a4a; 653 | } 654 | 655 | QComboBox QAbstractItemView 656 | { 657 | background-color: #232629; 658 | border-radius: 2px; 659 | border: 1px solid #76797C; 660 | selection-background-color: #3daee9; 661 | } 662 | 663 | QComboBox::drop-down 664 | { 665 | subcontrol-origin: padding; 666 | subcontrol-position: top right; 667 | width: 15px; 668 | 669 | border-left-width: 0px; 670 | border-left-color: darkgray; 671 | border-left-style: solid; 672 | border-top-right-radius: 3px; 673 | border-bottom-right-radius: 3px; 674 | } 675 | 676 | QComboBox::down-arrow 677 | { 678 | image: url(:/qss_icons/rc/down_arrow_disabled.png); 679 | } 680 | 681 | QComboBox::down-arrow:on, QComboBox::down-arrow:hover, 682 | QComboBox::down-arrow:focus 683 | { 684 | image: url(:/qss_icons/rc/down_arrow.png); 685 | } 686 | 687 | QAbstractSpinBox { 688 | padding: 5px; 689 | border: 1px solid #76797C; 690 | background-color: #232629; 691 | color: #eff0f1; 692 | border-radius: 2px; 693 | min-width: 75px; 694 | } 695 | 696 | QAbstractSpinBox:up-button 697 | { 698 | background-color: transparent; 699 | subcontrol-origin: border; 700 | subcontrol-position: center right; 701 | } 702 | 703 | QAbstractSpinBox:down-button 704 | { 705 | background-color: transparent; 706 | subcontrol-origin: border; 707 | subcontrol-position: center left; 708 | } 709 | 710 | QAbstractSpinBox::up-arrow,QAbstractSpinBox::up-arrow:disabled,QAbstractSpinBox::up-arrow:off { 711 | image: url(:/qss_icons/rc/up_arrow_disabled.png); 712 | width: 10px; 713 | height: 10px; 714 | } 715 | QAbstractSpinBox::up-arrow:hover 716 | { 717 | image: url(:/qss_icons/rc/up_arrow.png); 718 | } 719 | 720 | 721 | QAbstractSpinBox::down-arrow,QAbstractSpinBox::down-arrow:disabled,QAbstractSpinBox::down-arrow:off 722 | { 723 | image: url(:/qss_icons/rc/down_arrow_disabled.png); 724 | width: 10px; 725 | height: 10px; 726 | } 727 | QAbstractSpinBox::down-arrow:hover 728 | { 729 | image: url(:/qss_icons/rc/down_arrow.png); 730 | } 731 | 732 | 733 | QLabel 734 | { 735 | border: 0px solid black; 736 | } 737 | 738 | QTabWidget{ 739 | border: 0px transparent black; 740 | } 741 | 742 | QTabWidget::pane { 743 | border: 1px solid #76797C; 744 | padding: 5px; 745 | margin: 0px; 746 | } 747 | 748 | QTabBar 749 | { 750 | qproperty-drawBase: 0; 751 | left: 5px; /* move to the right by 5px */ 752 | border-radius: 3px; 753 | } 754 | 755 | QTabBar:focus 756 | { 757 | border: 0px transparent black; 758 | } 759 | 760 | QTabBar::close-button { 761 | image: url(:/qss_icons/rc/close.png); 762 | background: transparent; 763 | } 764 | 765 | QTabBar::close-button:hover 766 | { 767 | image: url(:/qss_icons/rc/close-hover.png); 768 | background: transparent; 769 | } 770 | 771 | QTabBar::close-button:pressed { 772 | image: url(:/qss_icons/rc/close-pressed.png); 773 | background: transparent; 774 | } 775 | 776 | /* TOP TABS */ 777 | QTabBar::tab:top { 778 | color: #eff0f1; 779 | border: 1px solid #76797C; 780 | border-bottom: 1px transparent black; 781 | background-color: #31363b; 782 | padding: 5px; 783 | min-width: 50px; 784 | border-top-left-radius: 2px; 785 | border-top-right-radius: 2px; 786 | } 787 | 788 | QTabBar::tab:top:!selected 789 | { 790 | color: #eff0f1; 791 | background-color: #54575B; 792 | border: 1px solid #76797C; 793 | border-bottom: 1px transparent black; 794 | border-top-left-radius: 2px; 795 | border-top-right-radius: 2px; 796 | } 797 | 798 | QTabBar::tab:top:!selected:hover { 799 | background-color: #3daee9; 800 | } 801 | 802 | /* BOTTOM TABS */ 803 | QTabBar::tab:bottom { 804 | color: #eff0f1; 805 | border: 1px solid #76797C; 806 | border-top: 1px transparent black; 807 | background-color: #31363b; 808 | padding: 5px; 809 | border-bottom-left-radius: 2px; 810 | border-bottom-right-radius: 2px; 811 | min-width: 50px; 812 | } 813 | 814 | QTabBar::tab:bottom:!selected 815 | { 816 | color: #eff0f1; 817 | background-color: #54575B; 818 | border: 1px solid #76797C; 819 | border-top: 1px transparent black; 820 | border-bottom-left-radius: 2px; 821 | border-bottom-right-radius: 2px; 822 | } 823 | 824 | QTabBar::tab:bottom:!selected:hover { 825 | background-color: #3daee9; 826 | } 827 | 828 | /* LEFT TABS */ 829 | QTabBar::tab:left { 830 | color: #eff0f1; 831 | border: 1px solid #76797C; 832 | border-left: 1px transparent black; 833 | background-color: #31363b; 834 | padding: 5px; 835 | border-top-right-radius: 2px; 836 | border-bottom-right-radius: 2px; 837 | min-height: 50px; 838 | } 839 | 840 | QTabBar::tab:left:!selected 841 | { 842 | color: #eff0f1; 843 | background-color: #54575B; 844 | border: 1px solid #76797C; 845 | border-left: 1px transparent black; 846 | border-top-right-radius: 2px; 847 | border-bottom-right-radius: 2px; 848 | } 849 | 850 | QTabBar::tab:left:!selected:hover { 851 | background-color: #3daee9; 852 | } 853 | 854 | 855 | /* RIGHT TABS */ 856 | QTabBar::tab:right { 857 | color: #eff0f1; 858 | border: 1px solid #76797C; 859 | border-right: 1px transparent black; 860 | background-color: #31363b; 861 | padding: 5px; 862 | border-top-left-radius: 2px; 863 | border-bottom-left-radius: 2px; 864 | min-height: 50px; 865 | } 866 | 867 | QTabBar::tab:right:!selected 868 | { 869 | color: #eff0f1; 870 | background-color: #54575B; 871 | border: 1px solid #76797C; 872 | border-right: 1px transparent black; 873 | border-top-left-radius: 2px; 874 | border-bottom-left-radius: 2px; 875 | } 876 | 877 | QTabBar::tab:right:!selected:hover { 878 | background-color: #3daee9; 879 | } 880 | 881 | QTabBar QToolButton::right-arrow:enabled { 882 | image: url(:/qss_icons/rc/right_arrow.png); 883 | } 884 | 885 | QTabBar QToolButton::left-arrow:enabled { 886 | image: url(:/qss_icons/rc/left_arrow.png); 887 | } 888 | 889 | QTabBar QToolButton::right-arrow:disabled { 890 | image: url(:/qss_icons/rc/right_arrow_disabled.png); 891 | } 892 | 893 | QTabBar QToolButton::left-arrow:disabled { 894 | image: url(:/qss_icons/rc/left_arrow_disabled.png); 895 | } 896 | 897 | 898 | QDockWidget { 899 | background: #31363b; 900 | border: 1px solid #403F3F; 901 | titlebar-close-icon: url(:/qss_icons/rc/close.png); 902 | titlebar-normal-icon: url(:/qss_icons/rc/undock.png); 903 | } 904 | 905 | QDockWidget::close-button, QDockWidget::float-button { 906 | border: 1px solid transparent; 907 | border-radius: 2px; 908 | background: transparent; 909 | } 910 | 911 | QDockWidget::close-button:hover, QDockWidget::float-button:hover { 912 | background: rgba(255, 255, 255, 10); 913 | } 914 | 915 | QDockWidget::close-button:pressed, QDockWidget::float-button:pressed { 916 | padding: 1px -1px -1px 1px; 917 | background: rgba(255, 255, 255, 10); 918 | } 919 | 920 | QTreeView, QListView 921 | { 922 | border: 1px solid #76797C; 923 | background-color: #232629; 924 | } 925 | 926 | QTreeView:branch:selected, QTreeView:branch:hover 927 | { 928 | background: url(:/qss_icons/rc/transparent.png); 929 | } 930 | 931 | QTreeView::branch:has-siblings:!adjoins-item { 932 | border-image: url(:/qss_icons/rc/transparent.png); 933 | } 934 | 935 | QTreeView::branch:has-siblings:adjoins-item { 936 | border-image: url(:/qss_icons/rc/transparent.png); 937 | } 938 | 939 | QTreeView::branch:!has-children:!has-siblings:adjoins-item { 940 | border-image: url(:/qss_icons/rc/transparent.png); 941 | } 942 | 943 | QTreeView::branch:has-children:!has-siblings:closed, 944 | QTreeView::branch:closed:has-children:has-siblings { 945 | image: url(:/qss_icons/rc/branch_closed.png); 946 | } 947 | 948 | QTreeView::branch:open:has-children:!has-siblings, 949 | QTreeView::branch:open:has-children:has-siblings { 950 | image: url(:/qss_icons/rc/branch_open.png); 951 | } 952 | 953 | QTreeView::branch:has-children:!has-siblings:closed:hover, 954 | QTreeView::branch:closed:has-children:has-siblings:hover { 955 | image: url(:/qss_icons/rc/branch_closed-on.png); 956 | } 957 | 958 | QTreeView::branch:open:has-children:!has-siblings:hover, 959 | QTreeView::branch:open:has-children:has-siblings:hover { 960 | image: url(:/qss_icons/rc/branch_open-on.png); 961 | } 962 | 963 | QListView::item:!selected:hover, QTreeView::item:!selected:hover { 964 | background: rgba(167,218,245, 0.3); 965 | outline: 0; 966 | color: #eff0f1 967 | } 968 | 969 | QListView::item:selected:hover, QTreeView::item:selected:hover { 970 | background: #3daee9; 971 | color: #eff0f1; 972 | } 973 | 974 | QSlider::groove:horizontal { 975 | border: 1px solid #565a5e; 976 | height: 4px; 977 | background: #565a5e; 978 | margin: 0px; 979 | border-radius: 2px; 980 | } 981 | 982 | QSlider::handle:horizontal { 983 | background: #232629; 984 | border: 1px solid #565a5e; 985 | width: 16px; 986 | height: 16px; 987 | margin: -8px 0; 988 | border-radius: 9px; 989 | } 990 | 991 | QSlider::groove:vertical { 992 | border: 1px solid #565a5e; 993 | width: 4px; 994 | background: #565a5e; 995 | margin: 0px; 996 | border-radius: 3px; 997 | } 998 | 999 | QSlider::handle:vertical { 1000 | background: #232629; 1001 | border: 1px solid #565a5e; 1002 | width: 16px; 1003 | height: 16px; 1004 | margin: 0 -8px; 1005 | border-radius: 9px; 1006 | } 1007 | 1008 | QToolButton { 1009 | background-color: transparent; 1010 | border: 1px transparent #76797C; 1011 | border-radius: 2px; 1012 | margin: 3px; 1013 | padding: 5px; 1014 | } 1015 | 1016 | QToolButton[popupMode="1"] { /* only for MenuButtonPopup */ 1017 | padding-right: 20px; /* make way for the popup button */ 1018 | border: 1px #76797C; 1019 | border-radius: 5px; 1020 | } 1021 | 1022 | QToolButton[popupMode="2"] { /* only for InstantPopup */ 1023 | padding-right: 10px; /* make way for the popup button */ 1024 | border: 1px #76797C; 1025 | } 1026 | 1027 | 1028 | QToolButton:hover, QToolButton::menu-button:hover { 1029 | background-color: transparent; 1030 | border: 1px solid #3daee9; 1031 | padding: 5px; 1032 | } 1033 | 1034 | QToolButton:checked, QToolButton:pressed, 1035 | QToolButton::menu-button:pressed { 1036 | background-color: #3daee9; 1037 | border: 1px solid #3daee9; 1038 | padding: 5px; 1039 | } 1040 | 1041 | /* the subcontrol below is used only in the InstantPopup or DelayedPopup mode */ 1042 | QToolButton::menu-indicator { 1043 | image: url(:/qss_icons/rc/down_arrow.png); 1044 | top: -7px; left: -2px; /* shift it a bit */ 1045 | } 1046 | 1047 | /* the subcontrols below are used only in the MenuButtonPopup mode */ 1048 | QToolButton::menu-button { 1049 | border: 1px transparent #76797C; 1050 | border-top-right-radius: 6px; 1051 | border-bottom-right-radius: 6px; 1052 | /* 16px width + 4px for border = 20px allocated above */ 1053 | width: 16px; 1054 | outline: none; 1055 | } 1056 | 1057 | QToolButton::menu-arrow { 1058 | image: url(:/qss_icons/rc/down_arrow.png); 1059 | } 1060 | 1061 | QToolButton::menu-arrow:open { 1062 | border: 1px solid #76797C; 1063 | } 1064 | 1065 | QPushButton::menu-indicator { 1066 | subcontrol-origin: padding; 1067 | subcontrol-position: bottom right; 1068 | left: 8px; 1069 | } 1070 | 1071 | QTableView 1072 | { 1073 | border: 1px solid #76797C; 1074 | gridline-color: #31363b; 1075 | background-color: #232629; 1076 | } 1077 | 1078 | 1079 | QTableView, QHeaderView 1080 | { 1081 | border-radius: 0px; 1082 | } 1083 | 1084 | QTableView::item:pressed, QListView::item:pressed, QTreeView::item:pressed { 1085 | background: #3daee9; 1086 | color: #eff0f1; 1087 | } 1088 | 1089 | QTableView::item:selected:active, QTreeView::item:selected:active, QListView::item:selected:active { 1090 | background: #3daee9; 1091 | color: #eff0f1; 1092 | } 1093 | 1094 | 1095 | QHeaderView 1096 | { 1097 | background-color: #31363b; 1098 | border: 1px transparent; 1099 | border-radius: 0px; 1100 | margin: 0px; 1101 | padding: 0px; 1102 | 1103 | } 1104 | 1105 | QHeaderView::section { 1106 | background-color: #31363b; 1107 | color: #eff0f1; 1108 | padding: 5px; 1109 | border: 1px solid #76797C; 1110 | border-radius: 0px; 1111 | text-align: center; 1112 | } 1113 | 1114 | QHeaderView::section::vertical::first, QHeaderView::section::vertical::only-one 1115 | { 1116 | border-top: 1px solid #76797C; 1117 | } 1118 | 1119 | QHeaderView::section::vertical 1120 | { 1121 | border-top: transparent; 1122 | } 1123 | 1124 | QHeaderView::section::horizontal::first, QHeaderView::section::horizontal::only-one 1125 | { 1126 | border-left: 1px solid #76797C; 1127 | } 1128 | 1129 | QHeaderView::section::horizontal 1130 | { 1131 | border-left: transparent; 1132 | } 1133 | 1134 | 1135 | QHeaderView::section:checked 1136 | { 1137 | color: white; 1138 | background-color: #334e5e; 1139 | } 1140 | 1141 | /* style the sort indicator */ 1142 | QHeaderView::down-arrow { 1143 | image: url(:/qss_icons/rc/down_arrow.png); 1144 | } 1145 | 1146 | QHeaderView::up-arrow { 1147 | image: url(:/qss_icons/rc/up_arrow.png); 1148 | } 1149 | 1150 | 1151 | QTableCornerButton::section { 1152 | background-color: #31363b; 1153 | border: 1px transparent #76797C; 1154 | border-radius: 0px; 1155 | } 1156 | 1157 | QToolBox { 1158 | padding: 5px; 1159 | border: 1px transparent black; 1160 | } 1161 | 1162 | QToolBox::tab { 1163 | color: #eff0f1; 1164 | background-color: #31363b; 1165 | border: 1px solid #76797C; 1166 | border-bottom: 1px transparent #31363b; 1167 | border-top-left-radius: 5px; 1168 | border-top-right-radius: 5px; 1169 | } 1170 | 1171 | QToolBox::tab:selected { /* italicize selected tabs */ 1172 | font: italic; 1173 | background-color: #31363b; 1174 | border-color: #3daee9; 1175 | } 1176 | 1177 | QStatusBar::item { 1178 | border: 0px transparent dark; 1179 | } 1180 | 1181 | 1182 | QFrame[height="3"], QFrame[width="3"] { 1183 | background-color: #76797C; 1184 | } 1185 | 1186 | 1187 | QSplitter::handle { 1188 | border: 1px dashed #76797C; 1189 | } 1190 | 1191 | QSplitter::handle:hover { 1192 | background-color: #787876; 1193 | border: 1px solid #76797C; 1194 | } 1195 | 1196 | QSplitter::handle:horizontal { 1197 | width: 1px; 1198 | } 1199 | 1200 | QSplitter::handle:vertical { 1201 | height: 1px; 1202 | } 1203 | 1204 | QProgressBar { 1205 | border: 1px solid #76797C; 1206 | border-radius: 5px; 1207 | text-align: center; 1208 | } 1209 | 1210 | QProgressBar::chunk { 1211 | background-color: #05B8CC; 1212 | } 1213 | 1214 | QDateEdit 1215 | { 1216 | selection-background-color: #3daee9; 1217 | border-style: solid; 1218 | border: 1px solid #3375A3; 1219 | border-radius: 2px; 1220 | padding: 1px; 1221 | min-width: 75px; 1222 | } 1223 | 1224 | QDateEdit:on 1225 | { 1226 | padding-top: 3px; 1227 | padding-left: 4px; 1228 | selection-background-color: #4a4a4a; 1229 | } 1230 | 1231 | QDateEdit QAbstractItemView 1232 | { 1233 | background-color: #232629; 1234 | border-radius: 2px; 1235 | border: 1px solid #3375A3; 1236 | selection-background-color: #3daee9; 1237 | } 1238 | 1239 | QDateEdit::drop-down 1240 | { 1241 | subcontrol-origin: padding; 1242 | subcontrol-position: top right; 1243 | width: 15px; 1244 | border-left-width: 0px; 1245 | border-left-color: darkgray; 1246 | border-left-style: solid; 1247 | border-top-right-radius: 3px; 1248 | border-bottom-right-radius: 3px; 1249 | } 1250 | 1251 | QDateEdit::down-arrow 1252 | { 1253 | image: url(:/qss_icons/rc/down_arrow_disabled.png); 1254 | } 1255 | 1256 | QDateEdit::down-arrow:on, QDateEdit::down-arrow:hover, 1257 | QDateEdit::down-arrow:focus 1258 | { 1259 | image: url(:/qss_icons/rc/down_arrow.png); 1260 | } -------------------------------------------------------------------------------- /vnTrader/setting/VT_setting.json: -------------------------------------------------------------------------------- 1 | { 2 | "fontFamily": "Microsoft Yahei", 3 | "fontSize": 12, 4 | 5 | "mdAddress" : "tcp://data.tushare.org:8910", 6 | "tdAddress" : "tcp://gw.quantos.org:8901", 7 | 8 | "username" : "YourTelephoneNumber", 9 | "token" : "YourToken", 10 | 11 | "darkStyle": true 12 | } 13 | -------------------------------------------------------------------------------- /vnTrader/setting/vnpy.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quantOS-org/TradeSim/8106314f089bbdd3d8eaad34493f5b76f2e6b5c0/vnTrader/setting/vnpy.ico -------------------------------------------------------------------------------- /vnTrader/start.bat: -------------------------------------------------------------------------------- 1 | python vtMain.py 2 | pause -------------------------------------------------------------------------------- /vnTrader/uiMainWindow.py: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | from builtins import str 4 | 5 | import psutil 6 | 7 | # import sys 8 | # PyQt 4/5 compatibility 9 | try: 10 | from PyQt4.QtGui import QMainWindow, QDialog, QDockWidget, QAction, QHeaderView, QMessageBox, QLabel, QVBoxLayout 11 | from PyQt4 import QtCore 12 | except ImportError: 13 | from PyQt5.QtWidgets import QMainWindow, QDialog, QDockWidget, QAction, QHeaderView, QMessageBox, QLabel, QVBoxLayout 14 | from PyQt5 import QtCore 15 | 16 | from uiBasicWidget import * 17 | 18 | import uiBasicWidget as wgs 19 | #from . import uiBasicWidget as wgs 20 | 21 | ######################################################################## 22 | class MainWindow(QMainWindow): 23 | """主窗口""" 24 | signalStatusBar = QtCore.pyqtSignal(type(Event())) 25 | 26 | # ---------------------------------------------------------------------- 27 | def __init__(self, mainEngine, eventEngine, app, sheets): 28 | """Constructor""" 29 | super(MainWindow, self).__init__() 30 | 31 | self.mainEngine = mainEngine 32 | self.eventEngine = eventEngine 33 | self.app = app 34 | self.sheets = sheets 35 | 36 | self.widgetDict = {} # 用来保存子窗口的字典 37 | 38 | self.initUi() 39 | self.eventEngine.register(EVENT_TITLE, self.updateTitle) 40 | 41 | self.sid = None 42 | 43 | def updateTitle(self, event): 44 | (user, stratid) = event.dict_['data'] 45 | #self.setWindowTitle('VnTrader: ' + str(user) + "/" + str(stratid)) 46 | self.sid = stratid 47 | 48 | # ---------------------------------------------------------------------- 49 | def initUi(self): 50 | """初始化界面""" 51 | self.setWindowTitle('VnTrader') 52 | self.initCentral() 53 | self.initMenu() 54 | # self.initStatusBar() 55 | 56 | def showLogin(self): 57 | self.connectQuantOS() 58 | 59 | # ---------------------------------------------------------------------- 60 | def initCentral(self): 61 | 62 | """初始化中心区域""" 63 | widgetTradingW, dockTradingW = self.createDock(wgs.TradingWidget, u'交易', QtCore.Qt.LeftDockWidgetArea) 64 | 65 | widgetMarketM, dockMarketM = self.createDock(wgs.MarketMonitor, u'行情', QtCore.Qt.RightDockWidgetArea) 66 | widgetPositionM, dockPositionM = self.createDock(wgs.PositionMonitor, u'持仓', QtCore.Qt.RightDockWidgetArea) 67 | 68 | widgetAccountM, dockAccountM = self.createDock(wgs.AccountMonitor, u'资金', QtCore.Qt.BottomDockWidgetArea) 69 | widgetLogM, dockLogM = self.createDock(wgs.LogMonitor, u'日志', QtCore.Qt.BottomDockWidgetArea) 70 | 71 | widgetTradeM, dockTradeM = self.createDock(wgs.TradeMonitor, u'成交', QtCore.Qt.BottomDockWidgetArea) 72 | widgetOrderM, dockOrderM = self.createDock(wgs.OrderMonitor, u'委托', QtCore.Qt.BottomDockWidgetArea) 73 | 74 | self.tabifyDockWidget(dockTradeM, dockOrderM) 75 | self.tabifyDockWidget(dockAccountM, dockLogM) 76 | 77 | dockOrderM.raise_() 78 | dockLogM.raise_() 79 | 80 | # 连接组件之间的信号 81 | widgetPositionM.itemDoubleClicked.connect(widgetTradingW.closePosition) 82 | widgetMarketM.itemDoubleClicked.connect(widgetTradingW.fillSymbol) 83 | 84 | # ---------------------------------------------------------------------- 85 | def initMenu(self): 86 | """初始化菜单""" 87 | # 创建操作 88 | connectQuantOSAction = QAction(u'连接和切换策略', self) 89 | connectQuantOSAction.triggered.connect(self.connectQuantOS) 90 | 91 | exitAction = QAction(u'退出', self) 92 | exitAction.triggered.connect(self.close) 93 | 94 | aboutAction = QAction(u'关于', self) 95 | aboutAction.triggered.connect(self.openAbout) 96 | 97 | colorAction = QAction(u'变色', self) 98 | colorAction.triggered.connect(self.changeColor) 99 | 100 | # 创建菜单 101 | menubar = self.menuBar() 102 | 103 | # 设计为只显示存在的接口 104 | sysMenu = menubar.addMenu(u'系统') 105 | if 'quantos' in self.mainEngine.gatewayDict: 106 | sysMenu.addAction(connectQuantOSAction) 107 | sysMenu.addSeparator() 108 | sysMenu.addAction(exitAction) 109 | 110 | # 帮助 111 | helpMenu = menubar.addMenu(u'帮助') 112 | helpMenu.addAction(aboutAction) 113 | helpMenu.addAction(colorAction) 114 | 115 | # ---------------------------------------------------------------------- 116 | def initStatusBar(self): 117 | """初始化状态栏""" 118 | self.statusLabel = QLabel() 119 | self.statusLabel.setAlignment(QtCore.Qt.AlignLeft) 120 | 121 | self.statusBar().addPermanentWidget(self.statusLabel) 122 | self.statusLabel.setText(self.getCpuMemory()) 123 | 124 | self.sbCount = 0 125 | self.sbTrigger = 10 # 10秒刷新一次 126 | self.signalStatusBar.connect(self.updateStatusBar) 127 | self.eventEngine.register(EVENT_TIMER, self.signalStatusBar.emit) 128 | 129 | # ---------------------------------------------------------------------- 130 | def updateStatusBar(self, event): 131 | """在状态栏更新CPU和内存信息""" 132 | self.sbCount += 1 133 | 134 | if self.sbCount == self.sbTrigger: 135 | self.sbCount = 0 136 | self.statusLabel.setText(self.getCpuMemory()) 137 | 138 | # ---------------------------------------------------------------------- 139 | def getCpuMemory(self): 140 | """获取CPU和内存状态信息""" 141 | cpuPercent = psutil.cpu_percent() 142 | memoryPercent = psutil.virtual_memory().percent 143 | return u'CPU使用率:%d%% 内存使用率:%d%%' % (cpuPercent, memoryPercent) 144 | 145 | # ---------------------------------------------------------------------- 146 | 147 | def connectQuantOS(self): 148 | self.mainEngine.connect('quantos') 149 | 150 | # ---------------------------------------------------------------------- 151 | 152 | def openAbout(self): 153 | """打开关于""" 154 | try: 155 | self.widgetDict['aboutW'].show() 156 | except KeyError: 157 | self.widgetDict['aboutW'] = AboutWidget(self) 158 | self.widgetDict['aboutW'].show() 159 | 160 | # ---------------------------------------------------------------------- 161 | def closeEvent(self, event): 162 | """关闭事件""" 163 | reply = QMessageBox.question(self, u'退出', 164 | u'确认退出?', QMessageBox.Yes | 165 | QMessageBox.No, QMessageBox.No) 166 | 167 | if reply == QMessageBox.Yes: 168 | for widget in list(self.widgetDict.values()): 169 | widget.close() 170 | 171 | self.mainEngine.exit() 172 | event.accept() 173 | else: 174 | event.ignore() 175 | 176 | # ---------------------------------------------------------------------- 177 | def createDock(self, widgetClass, widgetName, widgetArea): 178 | """创建停靠组件""" 179 | widget = widgetClass(self.mainEngine, self.eventEngine) 180 | dock = QDockWidget(widgetName) 181 | dock.setWidget(widget) 182 | dock.setObjectName(widgetName) 183 | dock.setFeatures(dock.DockWidgetFloatable | dock.DockWidgetMovable) 184 | self.addDockWidget(widgetArea, dock) 185 | return widget, dock 186 | 187 | def changeColor(self): 188 | self.app.setStyleSheet(self.sheets[1]) 189 | self.sheets = [self.sheets[1], self.sheets[0]] 190 | 191 | 192 | ######################################################################## 193 | class AboutWidget(QDialog): 194 | """显示关于信息""" 195 | 196 | # ---------------------------------------------------------------------- 197 | def __init__(self, parent=None): 198 | """Constructor""" 199 | super(AboutWidget, self).__init__(parent) 200 | 201 | self.initUi() 202 | 203 | # ---------------------------------------------------------------------- 204 | def initUi(self): 205 | """""" 206 | self.setWindowTitle(u'关于VnTrader') 207 | 208 | text = u""" 209 | quantos trade client 210 | """ 211 | 212 | label = QLabel() 213 | label.setText(text) 214 | label.setMinimumWidth(500) 215 | 216 | vbox = QVBoxLayout() 217 | vbox.addWidget(label) 218 | 219 | self.setLayout(vbox) 220 | -------------------------------------------------------------------------------- /vnTrader/vtConstant.py: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | from __future__ import unicode_literals 3 | HD_SERVER = 'ipc:///home/xchen/vt_historydata' 4 | 5 | # 默认空值 6 | EMPTY_STRING = '' 7 | EMPTY_UNICODE = u'' 8 | EMPTY_INT = 0 9 | EMPTY_FLOAT = 0.0 10 | EMPTY_LONG = 0 11 | 12 | # 方向常量 13 | DIRECTION_NONE = u'无方向' 14 | DIRECTION_LONG = u'多' 15 | DIRECTION_SHORT = u'空' 16 | DIRECTION_UNKNOWN = u'未知' 17 | DIRECTION_NET = u'净' 18 | DIRECTION_SELL = u'卖出' # IB接口 19 | 20 | # 开平常量 21 | OFFSET_NONE = u'无开平' 22 | OFFSET_OPEN = u'开仓' 23 | OFFSET_CLOSE = u'平仓' 24 | OFFSET_CLOSETODAY = u'平今' 25 | OFFSET_CLOSEYESTERDAY = u'平昨' 26 | OFFSET_UNKNOWN = u'未知' 27 | 28 | # 状态常量 29 | STATUS_NOTTRADED = u'未成交' 30 | STATUS_PARTTRADED = u'部分成交' 31 | STATUS_ALLTRADED = u'全部成交' 32 | STATUS_CANCELLED = u'已撤销' 33 | STATUS_UNKNOWN = u'未知' 34 | STATUS_REJECTED = u'拒单' 35 | 36 | # 合约类型常量 37 | PRODUCT_EQUITY = u'股票' 38 | PRODUCT_FUTURES = u'期货' 39 | PRODUCT_BOND = u'债券' 40 | PRODUCT_OPTION = u'期权' 41 | PRODUCT_INDEX = u'指数' 42 | PRODUCT_COMBINATION = u'组合' 43 | PRODUCT_FOREX = u'外汇' 44 | PRODUCT_UNKNOWN = u'未知' 45 | PRODUCT_SPOT = u'现货' 46 | PRODUCT_DEFER = u'延期' 47 | PRODUCT_NONE = '' 48 | 49 | # 价格类型常量 50 | PRICETYPE_LIMITPRICE = u'限价' 51 | PRICETYPE_MARKETPRICE = u'市价' 52 | PRICETYPE_FAK = u'FAK' 53 | PRICETYPE_FOK = u'FOK' 54 | PRICETYPE_PRSPLIT = u'PRSPLIT' 55 | PRICETYPE_VWAP = u'VWAP' 56 | PRICETYPE_TWAP = u'TWAP' 57 | PRICETYPE_BESTBIDASK = u'BESTBIDASK' 58 | PRICETYPE_SMARTPRICE = u'SMARTPRICE' 59 | 60 | # 期权类型 61 | OPTION_CALL = u'看涨期权' 62 | OPTION_PUT = u'看跌期权' 63 | 64 | # 交易所类型 65 | #EXCHANGE_SSE = 'SSE' # 上交所 66 | #EXCHANGE_SZSE = 'SZSE' # 深交所 67 | #EXCHANGE_CFFEX = 'CFFEX' # 中金所 68 | #EXCHANGE_SHFE = 'SHFE' # 上期所 69 | #EXCHANGE_CZCE = 'CZCE' # 郑商所 70 | #EXCHANGE_DCE = 'DCE' # 大商所 71 | 72 | EXCHANGE_SSE = 'SH' # 上交所 73 | EXCHANGE_SZSE = 'SZ' # 深交所 74 | EXCHANGE_CFFEX = 'CFE' # 中金所 75 | EXCHANGE_SHFE = 'SHF' # 上期所 76 | EXCHANGE_CZCE = 'CZC' # 郑商所 77 | EXCHANGE_DCE = 'DCE' # 大商所 78 | EXCHANGE_CSI = 'CSI' # 中证指数 79 | EXCHANGE_HKH = 'HKH' # 沪港通 80 | EXCHANGE_HKS = 'HKS' # 深港通 81 | EXCHANGE_JZ = 'JZ' # 均直 82 | EXCHANGE_SPOT = 'SPOT' # 现货 83 | EXCHANGE_IB = 'IB' # 银行间市场 84 | EXCHANGE_FX = 'FX' # 外汇 85 | EXCHANGE_INE = 'INE' # 能源 86 | 87 | EXCHANGE_SGE = 'SGE' # 上金所 88 | EXCHANGE_UNKNOWN = 'UNKNOWN'# 未知交易所 89 | EXCHANGE_NONE = '' # 空交易所 90 | EXCHANGE_HKEX = 'HKEX' # 港交所 91 | 92 | EXCHANGE_SMART = 'SMART' # IB智能路由(股票、期权) 93 | EXCHANGE_NYMEX = 'NYMEX' # IB 期货 94 | EXCHANGE_GLOBEX = 'GLOBEX' # CME电子交易平台 95 | EXCHANGE_IDEALPRO = 'IDEALPRO' # IB外汇ECN 96 | 97 | EXCHANGE_CME = 'CME' # CME交易所 98 | EXCHANGE_ICE = 'ICE' # ICE交易所 99 | 100 | EXCHANGE_OANDA = 'OANDA' # OANDA外汇做市商 101 | EXCHANGE_OKCOIN = 'OKCOIN' # OKCOIN比特币交易所 102 | 103 | # 货币类型 104 | CURRENCY_USD = 'USD' # 美元 105 | CURRENCY_CNY = 'CNY' # 人民币 106 | CURRENCY_UNKNOWN = 'UNKNOWN' # 未知货币 107 | CURRENCY_NONE = '' # 空货币 -------------------------------------------------------------------------------- /vnTrader/vtEngine.py: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | from builtins import object 4 | 5 | 6 | import shelve 7 | from collections import OrderedDict 8 | 9 | from eventEngine import * 10 | from vtGateway import * 11 | 12 | 13 | ######################################################################## 14 | class MainEngine(object): 15 | """主引擎""" 16 | 17 | #---------------------------------------------------------------------- 18 | def __init__(self): 19 | """Constructor""" 20 | # 创建事件引擎 21 | self.eventEngine = EventEngine2() 22 | self.eventEngine.start() 23 | 24 | # 创建数据引擎 25 | self.dataEngine = DataEngine(self.eventEngine) 26 | 27 | # 调用一个个初始化函数 28 | self.initGateway() 29 | 30 | #---------------------------------------------------------------------- 31 | def initGateway(self): 32 | """初始化接口对象""" 33 | # 用来保存接口对象的字典 34 | self.gatewayDict = OrderedDict() 35 | 36 | # 创建我们想要接入的接口对象 37 | from gateway.quantosGateway import QuantOSGateway 38 | self.addGateway(QuantOSGateway, 'quantos') 39 | self.gatewayDict['quantos'].setQryEnabled(True) 40 | 41 | #---------------------------------------------------------------------- 42 | def addGateway(self, gateway, gatewayName=None): 43 | """创建接口""" 44 | self.gatewayDict[gatewayName] = gateway(self.eventEngine, self.dataEngine, gatewayName) 45 | 46 | #---------------------------------------------------------------------- 47 | def connect(self, gatewayName): 48 | """连接特定名称的接口""" 49 | if gatewayName in self.gatewayDict: 50 | gateway = self.gatewayDict[gatewayName] 51 | gateway.connect() 52 | else: 53 | self.writeLog(u'接口不存在:%s' %gatewayName) 54 | 55 | #---------------------------------------------------------------------- 56 | def subscribe(self, symbols, gatewayName): 57 | """订阅特定接口的行情""" 58 | if gatewayName in self.gatewayDict: 59 | gateway = self.gatewayDict[gatewayName] 60 | gateway.subscribe(symbols) 61 | else: 62 | self.writeLog(u'接口不存在:%s' %gatewayName) 63 | 64 | #---------------------------------------------------------------------- 65 | def sendOrder(self, orderReq, gatewayName): 66 | """对特定接口发单""" 67 | if gatewayName in self.gatewayDict: 68 | gateway = self.gatewayDict[gatewayName] 69 | gateway.sendOrder(orderReq) 70 | else: 71 | self.writeLog(u'接口不存在:%s' %gatewayName) 72 | 73 | #---------------------------------------------------------------------- 74 | def sendBasketOrder(self, req): 75 | """""" 76 | gatewayName = 'quantos' 77 | if gatewayName in self.gatewayDict: 78 | gateway = self.gatewayDict[gatewayName] 79 | return gateway.sendBasketOrder(req) 80 | else: 81 | self.writeLog(u'接口不存在:%s' %gatewayName) 82 | 83 | #---------------------------------------------------------------------- 84 | def cancelOrder(self, cancelOrderReq, gatewayName): 85 | """对特定接口撤单""" 86 | if gatewayName in self.gatewayDict: 87 | gateway = self.gatewayDict[gatewayName] 88 | gateway.cancelOrder(cancelOrderReq) 89 | else: 90 | self.writeLog(u'接口不存在:%s' %gatewayName) 91 | 92 | #---------------------------------------------------------------------- 93 | def qryAccount(self, gatewayName): 94 | """查询特定接口的账户""" 95 | if gatewayName in self.gatewayDict: 96 | gateway = self.gatewayDict[gatewayName] 97 | gateway.qryAccount() 98 | else: 99 | self.writeLog(u'接口不存在:%s' %gatewayName) 100 | 101 | #---------------------------------------------------------------------- 102 | def qryPosition(self, gatewayName): 103 | """查询特定接口的持仓""" 104 | if gatewayName in self.gatewayDict: 105 | gateway = self.gatewayDict[gatewayName] 106 | gateway.qryPosition() 107 | else: 108 | self.writeLog(u'接口不存在:%s' %gatewayName) 109 | 110 | #---------------------------------------------------------------------- 111 | def exit(self): 112 | """退出程序前调用,保证正常退出""" 113 | # 安全关闭所有接口 114 | for gateway in list(self.gatewayDict.values()): 115 | gateway.close() 116 | 117 | # 停止事件引擎 118 | self.eventEngine.stop() 119 | 120 | #---------------------------------------------------------------------- 121 | def writeLog(self, content): 122 | """快速发出日志事件""" 123 | log = VtLogData() 124 | log.logContent = content 125 | event = Event(type_=EVENT_LOG) 126 | event.dict_['data'] = log 127 | self.eventEngine.put(event) 128 | 129 | #---------------------------------------------------------------------- 130 | def getContract(self, symbol): 131 | """查询合约""" 132 | return self.dataEngine.getContract(symbol) 133 | 134 | #---------------------------------------------------------------------- 135 | def getAllContracts(self): 136 | """查询所有合约(返回列表)""" 137 | return self.dataEngine.getAllContracts() 138 | 139 | #---------------------------------------------------------------------- 140 | def getOrder(self, vtOrderID): 141 | """查询委托""" 142 | return self.dataEngine.getOrder(vtOrderID) 143 | 144 | #---------------------------------------------------------------------- 145 | def getAllWorkingOrders(self): 146 | """查询所有的活跃的委托(返回列表)""" 147 | return self.dataEngine.getAllWorkingOrders() 148 | 149 | #---------------------------------------------------------------------- 150 | def getAllOrders(self): 151 | """""" 152 | return self.dataEngine.getAllOrders() 153 | 154 | 155 | 156 | ######################################################################## 157 | class DataEngine(object): 158 | """数据引擎""" 159 | #---------------------------------------------------------------------- 160 | def __init__(self, eventEngine): 161 | """Constructor""" 162 | self.eventEngine = eventEngine 163 | 164 | # 保存合约详细信息的字典 165 | self.contractDict = {} 166 | 167 | # 保存委托数据的字典 168 | self.orderDict = {} 169 | 170 | # 保存活动委托数据的字典(即可撤销) 171 | self.workingOrderDict = {} 172 | 173 | # 注册事件监听 174 | self.registerEvent() 175 | 176 | #---------------------------------------------------------------------- 177 | def updateContract(self, event): 178 | """更新合约数据""" 179 | contract = event.dict_['data'] 180 | self.contractDict[contract.symbol] = contract 181 | 182 | def clearContract(self, event): 183 | self.contractDict.clear() 184 | 185 | #---------------------------------------------------------------------- 186 | def getContract(self, symbol): 187 | """查询合约对象""" 188 | try: 189 | return self.contractDict[symbol] 190 | except KeyError: 191 | return None 192 | 193 | #---------------------------------------------------------------------- 194 | def getAllContracts(self): 195 | """查询所有合约对象(返回列表)""" 196 | return list(self.contractDict.values()) 197 | 198 | #---------------------------------------------------------------------- 199 | def updateOrder(self, event): 200 | """更新委托数据""" 201 | order = event.dict_['data'] 202 | self.orderDict[order.vtOrderID] = order 203 | 204 | # 如果订单的状态是全部成交或者撤销,则需要从workingOrderDict中移除 205 | if order.status in (STATUS_ALLTRADED, STATUS_CANCELLED, STATUS_REJECTED): 206 | if order.vtOrderID in self.workingOrderDict: 207 | del self.workingOrderDict[order.vtOrderID] 208 | # 否则则更新字典中的数据 209 | else: 210 | self.workingOrderDict[order.vtOrderID] = order 211 | 212 | #---------------------------------------------------------------------- 213 | def getOrder(self, vtOrderID): 214 | """查询委托""" 215 | try: 216 | return self.orderDict[vtOrderID] 217 | except KeyError: 218 | return None 219 | 220 | #---------------------------------------------------------------------- 221 | def getAllWorkingOrders(self): 222 | """查询所有活动委托(返回列表)""" 223 | return list(self.workingOrderDict.values()) 224 | 225 | #---------------------------------------------------------------------- 226 | def getAllOrders(self): 227 | """""" 228 | return list(self.orderDict.values()) 229 | 230 | #---------------------------------------------------------------------- 231 | def registerEvent(self): 232 | """注册事件监听""" 233 | self.eventEngine.register(EVENT_CONTRACT, self.updateContract) 234 | self.eventEngine.register(EVENT_CONTRACT_CLEAR, self.clearContract) 235 | self.eventEngine.register(EVENT_ORDER, self.updateOrder) 236 | 237 | 238 | 239 | -------------------------------------------------------------------------------- /vnTrader/vtFunction.py: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | """ 4 | 包含一些开放中常用的函数 5 | """ 6 | from builtins import str 7 | 8 | import os 9 | import decimal 10 | import json 11 | from datetime import datetime 12 | 13 | MAX_NUMBER = 10000000000000 14 | MAX_DECIMAL = 4 15 | 16 | #---------------------------------------------------------------------- 17 | def safeUnicode(value): 18 | """检查接口数据潜在的错误,保证转化为的字符串正确""" 19 | # 检查是数字接近0时会出现的浮点数上限 20 | if type(value) is int or type(value) is float: 21 | if value > MAX_NUMBER: 22 | value = 0 23 | 24 | # 检查防止小数点位过多 25 | if type(value) is float: 26 | d = decimal.Decimal(str(value)) 27 | if abs(d.as_tuple().exponent) > MAX_DECIMAL: 28 | value = round(value, ndigits=MAX_DECIMAL) 29 | 30 | return str(value) 31 | 32 | #---------------------------------------------------------------------- 33 | def todayDate(): 34 | """获取当前本机电脑时间的日期""" 35 | return datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) 36 | 37 | #---------------------------------------------------------------------- 38 | def filePath(): 39 | """获取当前文件所处目录的绝对地址""" 40 | return os.path.abspath(os.path.dirname(__file__)) 41 | 42 | 43 | -------------------------------------------------------------------------------- /vnTrader/vtGateway.py: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | from builtins import str 4 | from builtins import object 5 | 6 | import time 7 | 8 | from eventEngine import * 9 | from vtConstant import * 10 | 11 | 12 | ######################################################################## 13 | class VtGateway(object): 14 | """交易接口""" 15 | 16 | #---------------------------------------------------------------------- 17 | def __init__(self, eventEngine, gatewayName): 18 | """Constructor""" 19 | self.eventEngine = eventEngine 20 | self.gatewayName = gatewayName 21 | 22 | #---------------------------------------------------------------------- 23 | def onTick(self, tick): 24 | """市场行情推送""" 25 | # 通用事件 26 | event1 = Event(type_=EVENT_TICK) 27 | event1.dict_['data'] = tick 28 | self.eventEngine.put(event1) 29 | 30 | # 特定合约代码的事件 31 | event2 = Event(type_=EVENT_TICK+tick.symbol) 32 | event2.dict_['data'] = tick 33 | self.eventEngine.put(event2) 34 | 35 | #---------------------------------------------------------------------- 36 | def onTrade(self, trade): 37 | """成交信息推送""" 38 | # 通用事件 39 | event1 = Event(type_=EVENT_TRADE) 40 | event1.dict_['data'] = trade 41 | self.eventEngine.put(event1) 42 | 43 | # 特定合约的成交事件 44 | event2 = Event(type_=EVENT_TRADE+trade.symbol) 45 | event2.dict_['data'] = trade 46 | self.eventEngine.put(event2) 47 | 48 | #---------------------------------------------------------------------- 49 | def onOrder(self, order): 50 | """订单变化推送""" 51 | # 通用事件 52 | event1 = Event(type_=EVENT_ORDER) 53 | event1.dict_['data'] = order 54 | self.eventEngine.put(event1) 55 | 56 | # 特定订单编号的事件 57 | event2 = Event(type_=EVENT_ORDER+order.vtOrderID) 58 | event2.dict_['data'] = order 59 | self.eventEngine.put(event2) 60 | 61 | #---------------------------------------------------------------------- 62 | def onPosition(self, position): 63 | """持仓信息推送""" 64 | # 通用事件 65 | event1 = Event(type_=EVENT_POSITION) 66 | event1.dict_['data'] = position 67 | self.eventEngine.put(event1) 68 | 69 | # 特定合约代码的事件 70 | event2 = Event(type_=EVENT_POSITION+position.symbol) 71 | event2.dict_['data'] = position 72 | self.eventEngine.put(event2) 73 | 74 | #---------------------------------------------------------------------- 75 | def onAccount(self, account): 76 | """账户信息推送""" 77 | # 通用事件 78 | event1 = Event(type_=EVENT_ACCOUNT) 79 | event1.dict_['data'] = account 80 | self.eventEngine.put(event1) 81 | 82 | # 特定合约代码的事件 83 | event2 = Event(type_=EVENT_ACCOUNT+str(account.vtAccountID)) 84 | event2.dict_['data'] = account 85 | self.eventEngine.put(event2) 86 | 87 | #---------------------------------------------------------------------- 88 | def onError(self, error): 89 | """错误信息推送""" 90 | # 通用事件 91 | event1 = Event(type_=EVENT_ERROR) 92 | event1.dict_['data'] = error 93 | self.eventEngine.put(event1) 94 | 95 | #---------------------------------------------------------------------- 96 | def onLog(self, log): 97 | """日志推送""" 98 | # 通用事件 99 | event1 = Event(type_=EVENT_LOG) 100 | event1.dict_['data'] = log 101 | self.eventEngine.put(event1) 102 | 103 | #---------------------------------------------------------------------- 104 | def onContract(self, contract): 105 | """合约基础信息推送""" 106 | # 通用事件 107 | event1 = Event(type_=EVENT_CONTRACT) 108 | event1.dict_['data'] = contract 109 | self.eventEngine.put(event1) 110 | 111 | def clearContract(self): 112 | event1 = Event(type_=EVENT_CONTRACT_CLEAR) 113 | self.eventEngine.put(event1) 114 | 115 | def clearPosition(self): 116 | event1 = Event(type_=EVENT_CLEAR) 117 | self.eventEngine.put(event1) 118 | 119 | def changeTitle(self, user, stratid): 120 | event1 = Event(type_=EVENT_TITLE) 121 | event1.dict_['data'] = (user, stratid) 122 | self.eventEngine.put(event1) 123 | 124 | #---------------------------------------------------------------------- 125 | def connect(self): 126 | """连接""" 127 | pass 128 | 129 | #---------------------------------------------------------------------- 130 | def subscribe(self, subscribeReq): 131 | """订阅行情""" 132 | pass 133 | 134 | #---------------------------------------------------------------------- 135 | def sendOrder(self, orderReq): 136 | """发单""" 137 | pass 138 | 139 | #---------------------------------------------------------------------- 140 | def cancelOrder(self, cancelOrderReq): 141 | """撤单""" 142 | pass 143 | 144 | #---------------------------------------------------------------------- 145 | def qryAccount(self): 146 | """查询账户资金""" 147 | pass 148 | 149 | #---------------------------------------------------------------------- 150 | def qryPosition(self): 151 | """查询持仓""" 152 | pass 153 | 154 | #---------------------------------------------------------------------- 155 | def close(self): 156 | """关闭""" 157 | pass 158 | 159 | #---------------------------------------------------------------------- 160 | def sendBasketOrder(self, basketOrderReq): 161 | """""" 162 | pass 163 | 164 | 165 | 166 | ######################################################################## 167 | class VtBaseData(object): 168 | """回调函数推送数据的基础类,其他数据类继承于此""" 169 | 170 | #---------------------------------------------------------------------- 171 | def __init__(self): 172 | """Constructor""" 173 | self.gatewayName = EMPTY_STRING # Gateway名称 174 | self.rawData = None # 原始数据 175 | 176 | 177 | ######################################################################## 178 | class VtTickData(VtBaseData): 179 | """Tick行情数据类""" 180 | 181 | #---------------------------------------------------------------------- 182 | def __init__(self): 183 | """Constructor""" 184 | super(VtTickData, self).__init__() 185 | 186 | # 代码相关 187 | self.symbol = EMPTY_STRING # 合约代码 188 | self.exchange = EMPTY_STRING # 交易所代码 189 | self.name = EMPTY_STRING # 代码名称 190 | 191 | # 成交数据 192 | self.lastPrice = EMPTY_FLOAT # 最新成交价 193 | self.lastVolume = EMPTY_INT # 最新成交量 194 | self.volume = EMPTY_INT # 今天总成交量 195 | self.volchg = EMPTY_INT # 今天总成交量 196 | self.turnover = EMPTY_LONG # 今天总成交额 197 | self.openInterest = EMPTY_INT # 持仓量 198 | self.time = EMPTY_STRING # 时间 11:20:56.5 199 | self.date = EMPTY_STRING # 日期 20151009 200 | 201 | # 常规行情 202 | self.openPrice = EMPTY_FLOAT # 今日开盘价 203 | self.highPrice = EMPTY_FLOAT # 今日最高价 204 | self.lowPrice = EMPTY_FLOAT # 今日最低价 205 | self.preClosePrice = EMPTY_FLOAT 206 | 207 | self.upperLimit = EMPTY_FLOAT # 涨停价 208 | self.lowerLimit = EMPTY_FLOAT # 跌停价 209 | 210 | # 五档行情 211 | self.bidPrice1 = EMPTY_FLOAT 212 | self.bidPrice2 = EMPTY_FLOAT 213 | self.bidPrice3 = EMPTY_FLOAT 214 | self.bidPrice4 = EMPTY_FLOAT 215 | self.bidPrice5 = EMPTY_FLOAT 216 | 217 | self.askPrice1 = EMPTY_FLOAT 218 | self.askPrice2 = EMPTY_FLOAT 219 | self.askPrice3 = EMPTY_FLOAT 220 | self.askPrice4 = EMPTY_FLOAT 221 | self.askPrice5 = EMPTY_FLOAT 222 | 223 | self.bidVolume1 = EMPTY_INT 224 | self.bidVolume2 = EMPTY_INT 225 | self.bidVolume3 = EMPTY_INT 226 | self.bidVolume4 = EMPTY_INT 227 | self.bidVolume5 = EMPTY_INT 228 | 229 | self.askVolume1 = EMPTY_INT 230 | self.askVolume2 = EMPTY_INT 231 | self.askVolume3 = EMPTY_INT 232 | self.askVolume4 = EMPTY_INT 233 | self.askVolume5 = EMPTY_INT 234 | 235 | 236 | ######################################################################## 237 | class VtTradeData(VtBaseData): 238 | """成交数据类""" 239 | 240 | #---------------------------------------------------------------------- 241 | def __init__(self): 242 | """Constructor""" 243 | super(VtTradeData, self).__init__() 244 | 245 | # 代码编号相关 246 | self.symbol = EMPTY_STRING # 合约代码 247 | self.exchange = EMPTY_STRING # 交易所代码 248 | self.name = EMPTY_STRING # 代码名称 249 | 250 | self.tradeID = EMPTY_STRING # 成交编号 251 | self.vtTradeID = EMPTY_STRING # 成交在vt系统中的唯一编号,通常是 Gateway名.成交编号 252 | 253 | self.orderID = EMPTY_STRING # 订单编号 254 | self.vtOrderID = EMPTY_STRING # 订单在vt系统中的唯一编号,通常是 Gateway名.订单编号 255 | self.taskID = EMPTY_STRING 256 | # 成交相关 257 | self.direction = EMPTY_UNICODE # 成交方向 258 | self.offset = EMPTY_UNICODE # 成交开平仓 259 | self.price = EMPTY_FLOAT # 成交价格 260 | self.volume = EMPTY_INT # 成交数量 261 | self.tradeTime = EMPTY_STRING # 成交时间 262 | 263 | 264 | ######################################################################## 265 | class VtOrderData(VtBaseData): 266 | """订单数据类""" 267 | 268 | #---------------------------------------------------------------------- 269 | def __init__(self): 270 | """Constructor""" 271 | super(VtOrderData, self).__init__() 272 | 273 | # 代码编号相关 274 | self.symbol = EMPTY_STRING # 合约代码 275 | self.exchange = EMPTY_STRING # 交易所代码 276 | self.name = EMPTY_STRING # 代码名称 277 | 278 | self.orderID = EMPTY_STRING # 订单编号 279 | self.vtOrderID = EMPTY_STRING # 订单在vt系统中的唯一编号,通常是 Gateway名.订单编号 280 | self.taskID = EMPTY_STRING 281 | 282 | # 报单相关 283 | self.direction = EMPTY_UNICODE # 报单方向 284 | self.offset = EMPTY_UNICODE # 报单开平仓 285 | self.price = EMPTY_FLOAT # 报单价格 286 | self.totalVolume = EMPTY_INT # 报单总数量 287 | self.tradedVolume = EMPTY_INT # 报单成交数量 288 | self.status = EMPTY_UNICODE # 报单状态 289 | 290 | self.orderTime = EMPTY_STRING # 发单时间 291 | self.cancelTime = EMPTY_STRING # 撤单时间 292 | 293 | # CTP/LTS相关 294 | self.frontID = EMPTY_INT # 前置机编号 295 | self.sessionID = EMPTY_INT # 连接编号 296 | 297 | self.priceType = EMPTY_STRING 298 | self.tradePrice = EMPTY_FLOAT 299 | 300 | 301 | ######################################################################## 302 | class VtPositionData(VtBaseData): 303 | """持仓数据类""" 304 | 305 | #---------------------------------------------------------------------- 306 | def __init__(self): 307 | """Constructor""" 308 | super(VtPositionData, self).__init__() 309 | 310 | # 代码编号相关 311 | self.symbol = EMPTY_STRING # 合约代码 312 | self.exchange = EMPTY_STRING # 交易所代码 313 | self.name = EMPTY_STRING # 代码名称 314 | 315 | # 持仓相关 316 | self.direction = EMPTY_STRING # 持仓方向 317 | self.position = EMPTY_INT # 持仓量 318 | self.frozen = EMPTY_INT # 冻结数量 319 | self.price = EMPTY_FLOAT # 持仓均价 320 | self.vtPositionName = EMPTY_STRING # 持仓在vt系统中的唯一代码,symbol.方向 321 | 322 | # 20151020添加 323 | self.ydPosition = EMPTY_INT # 昨持仓 324 | self.tdPosition = EMPTY_INT # 今持仓 325 | 326 | self.commission = EMPTY_FLOAT 327 | self.enable = EMPTY_INT 328 | self.want = EMPTY_INT 329 | self.initPosition = EMPTY_INT 330 | self.trading = EMPTY_FLOAT 331 | self.holding = EMPTY_FLOAT 332 | self.last = EMPTY_FLOAT 333 | self.mktval = EMPTY_FLOAT 334 | 335 | 336 | 337 | ######################################################################## 338 | class VtAccountData(VtBaseData): 339 | """账户数据类""" 340 | #---------------------------------------------------------------------- 341 | def __init__(self): 342 | """Constructor""" 343 | super(VtAccountData, self).__init__() 344 | 345 | # 账号代码相关 346 | self.accountID = EMPTY_INT # 账户代码 347 | self.vtAccountID = EMPTY_STRING # 账户在vt中的唯一代码,通常是 Gateway名.账户代码 348 | self.type = EMPTY_STRING 349 | 350 | self.frozen_balance = EMPTY_FLOAT 351 | self.enable_balance = EMPTY_FLOAT 352 | self.float_pnl = EMPTY_FLOAT 353 | self.init_balance = EMPTY_FLOAT 354 | self.deposit_balance = EMPTY_FLOAT 355 | self.holding_pnl = EMPTY_FLOAT 356 | self.close_pnl = EMPTY_FLOAT 357 | self.margin = EMPTY_FLOAT 358 | self.trading_pnl = EMPTY_FLOAT 359 | self.commission = EMPTY_FLOAT 360 | 361 | ######################################################################## 362 | class VtErrorData(VtBaseData): 363 | """错误数据类""" 364 | 365 | #---------------------------------------------------------------------- 366 | def __init__(self): 367 | """Constructor""" 368 | super(VtErrorData, self).__init__() 369 | 370 | self.errorID = EMPTY_STRING # 错误代码 371 | self.errorMsg = EMPTY_UNICODE # 错误信息 372 | self.additionalInfo = EMPTY_UNICODE # 补充信息 373 | 374 | self.errorTime = time.strftime('%X', time.localtime()) # 错误生成时间 375 | 376 | 377 | ######################################################################## 378 | class VtLogData(VtBaseData): 379 | """日志数据类""" 380 | 381 | #---------------------------------------------------------------------- 382 | def __init__(self): 383 | """Constructor""" 384 | super(VtLogData, self).__init__() 385 | 386 | self.logTime = time.strftime('%X', time.localtime()) # 日志生成时间 387 | self.logContent = EMPTY_UNICODE # 日志信息 388 | 389 | 390 | ######################################################################## 391 | class VtContractData(VtBaseData): 392 | """合约详细信息类""" 393 | 394 | #---------------------------------------------------------------------- 395 | def __init__(self): 396 | """Constructor""" 397 | super(VtContractData, self).__init__() 398 | 399 | self.exchange = EMPTY_STRING # 交易所代码 400 | self.symbol = EMPTY_STRING # 合约代码 401 | self.name = EMPTY_UNICODE # 合约中文名 402 | 403 | self.productClass = EMPTY_UNICODE # 合约类型 404 | self.size = EMPTY_INT # 合约大小 405 | self.lotsize = EMPTY_INT # 合约最小交易数量 406 | self.priceTick = EMPTY_FLOAT # 合约最小价格TICK 407 | 408 | # 期权相关 409 | self.strikePrice = EMPTY_FLOAT # 期权行权价 410 | self.underlyingSymbol = EMPTY_STRING # 标的物合约代码 411 | self.optionType = EMPTY_UNICODE # 期权类型 412 | 413 | 414 | ######################################################################## 415 | class VtSubscribeReq(object): 416 | """订阅行情时传入的对象类""" 417 | 418 | #---------------------------------------------------------------------- 419 | def __init__(self): 420 | """Constructor""" 421 | self.symbol = EMPTY_STRING # 代码 422 | self.exchange = EMPTY_STRING # 交易所 423 | 424 | # 以下为IB相关 425 | self.productClass = EMPTY_UNICODE # 合约类型 426 | self.currency = EMPTY_STRING # 合约货币 427 | self.expiry = EMPTY_STRING # 到期日 428 | self.strikePrice = EMPTY_FLOAT # 行权价 429 | self.optionType = EMPTY_UNICODE # 期权类型 430 | 431 | 432 | ######################################################################## 433 | class VtOrderReq(object): 434 | """发单时传入的对象类""" 435 | 436 | #---------------------------------------------------------------------- 437 | def __init__(self): 438 | """Constructor""" 439 | self.symbol = EMPTY_STRING # 代码 440 | self.exchange = EMPTY_STRING # 交易所 441 | self.price = EMPTY_FLOAT # 价格 442 | self.volume = EMPTY_INT # 数量 443 | self.urgency = EMPTY_INT 444 | 445 | self.priceType = EMPTY_STRING # 价格类型 446 | self.direction = EMPTY_STRING # 买卖 447 | self.offset = EMPTY_STRING # 开平 448 | 449 | # 以下为IB相关 450 | self.productClass = EMPTY_UNICODE # 合约类型 451 | self.currency = EMPTY_STRING # 合约货币 452 | self.expiry = EMPTY_STRING # 到期日 453 | self.strikePrice = EMPTY_FLOAT # 行权价 454 | self.optionType = EMPTY_UNICODE # 期权类型 455 | 456 | 457 | ######################################################################## 458 | class VtCancelOrderReq(object): 459 | """撤单时传入的对象类""" 460 | 461 | #---------------------------------------------------------------------- 462 | def __init__(self): 463 | """Constructor""" 464 | self.symbol = EMPTY_STRING # 代码 465 | self.exchange = EMPTY_STRING # 交易所 466 | 467 | # 以下字段主要和CTP、LTS类接口相关 468 | self.orderID = EMPTY_STRING # 报单号 469 | self.frontID = EMPTY_STRING # 前置机号 470 | self.sessionID = EMPTY_STRING # 会话号 471 | 472 | 473 | ######################################################################## 474 | class VtBasketOrderReq(object): 475 | """""" 476 | 477 | #---------------------------------------------------------------------- 478 | def __init__(self): 479 | """Constructor""" 480 | self.positions = [] 481 | self.algo = EMPTY_STRING 482 | self.params = {} 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | -------------------------------------------------------------------------------- /vnTrader/vtMain.py: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | import sys 4 | import os 5 | import ctypes 6 | import platform 7 | 8 | #from imp import reload 9 | try: 10 | from PyQt4.QtGui import QIcon, QApplication 11 | except ImportError: 12 | from PyQt5.QtWidgets import QApplication 13 | from PyQt5.QtGui import QIcon 14 | 15 | from vtEngine import MainEngine 16 | from uiMainWindow import MainWindow 17 | from uiBasicWidget import BASIC_FONT 18 | import qdarkstyle 19 | 20 | 21 | #---------------------------------------------------------------------- 22 | def main(): 23 | """主程序入口""" 24 | # 重载sys模块,设置默认字符串编码方式为utf8 25 | ''' 26 | reload(sys) 27 | try: 28 | sys.setdefaultencoding('utf8') 29 | except: 30 | pass 31 | ''' 32 | 33 | # 设置Windows底部任务栏图标 34 | if 'Windows' in platform.uname() : 35 | ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID('vn.trader') 36 | 37 | # 初始化Qt应用对象 38 | icon = os.path.join(os.getcwd(), 'setting', 'vnpy.ico') 39 | 40 | app = QApplication(['VnTrader','']) 41 | app.setWindowIcon(QIcon(icon)) 42 | app.setFont(BASIC_FONT) 43 | 44 | darksheet = qdarkstyle.load_stylesheet(pyside=False) 45 | whitesheet = app.styleSheet() 46 | sheets = [whitesheet, darksheet] 47 | # 设置Qt的皮肤 48 | try: 49 | f = open("setting/VT_setting.json") 50 | setting = json.load(f) 51 | if setting['darkStyle']: 52 | app.setStyleSheet(darksheet) 53 | sheets = [darksheet, whitesheet] 54 | except: 55 | pass 56 | 57 | # 初始化主引擎和主窗口对象 58 | mainEngine = MainEngine() 59 | mainWindow = MainWindow(mainEngine, mainEngine.eventEngine, app, sheets) 60 | mainWindow.showMaximized() 61 | mainWindow.showLogin() 62 | 63 | # 在主线程中启动Qt事件循环 64 | sys.exit(app.exec_()) 65 | 66 | if __name__ == '__main__': 67 | main() 68 | --------------------------------------------------------------------------------