├── .idea ├── .gitignore ├── inspectionProfiles │ ├── Project_Default.xml │ └── profiles_settings.xml ├── modules.xml ├── starquant.iml └── vcs.xml ├── LICENSE ├── README.md ├── data └── iquantdb.sql ├── log └── error_2023-03-10.txt ├── quant ├── __init__.py ├── account.py ├── api │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-38.pyc │ │ ├── gmapi.cpython-38.pyc │ │ ├── singleton.cpython-38.pyc │ │ └── tdxapi.cpython-38.pyc │ ├── futuapi.py │ ├── gmapi.py │ ├── singleton.py │ └── tdxapi.py ├── brokers │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-38.pyc │ │ ├── broker.cpython-38.pyc │ │ └── myquantbroker.cpython-38.pyc │ ├── broker.py │ └── myquantbroker.py ├── config.ini ├── empyrical │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-38.pyc │ │ ├── _version.cpython-38.pyc │ │ ├── stats.cpython-38.pyc │ │ └── utils.cpython-38.pyc │ ├── _version.py │ ├── stats.py │ └── utils.py ├── enums │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-38.pyc │ │ ├── brokers.cpython-38.pyc │ │ ├── dfcolumns.cpython-38.pyc │ │ ├── orderside.cpython-38.pyc │ │ ├── ratings.cpython-38.pyc │ │ ├── scopes.cpython-38.pyc │ │ ├── strategys.cpython-38.pyc │ │ └── strategytype.cpython-38.pyc │ ├── brokers.py │ ├── dfcolumns.py │ ├── orderside.py │ ├── ratings.py │ ├── scopes.py │ ├── strategys.py │ └── strategytype.py ├── indicators.py ├── job │ ├── __init__.py │ ├── run_bt.bat │ └── run_live.bat ├── logger.py ├── metric.py ├── model │ ├── .idea │ │ ├── inspectionProfiles │ │ │ └── profiles_settings.xml │ │ ├── misc.xml │ │ ├── model.iml │ │ ├── modules.xml │ │ └── workspace.xml │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-38.pyc │ │ ├── asset.cpython-38.pyc │ │ ├── backtestrecord.cpython-38.pyc │ │ ├── executionreport.cpython-38.pyc │ │ ├── myorder.cpython-38.pyc │ │ ├── order.cpython-38.pyc │ │ ├── position.cpython-38.pyc │ │ ├── setting.cpython-38.pyc │ │ ├── stockindicator.cpython-38.pyc │ │ ├── stockpool.cpython-38.pyc │ │ └── traderecord.cpython-38.pyc │ ├── asset.py │ ├── backtestrecord.py │ ├── executionreport.py │ ├── log.py │ ├── myorder.py │ ├── order.py │ ├── position.py │ ├── setting.py │ ├── stockindicator.py │ ├── stockpool.py │ └── traderecord.py ├── picker.py ├── quantengine.py ├── reporter.py ├── signalengine.py ├── signals.py ├── signals │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-38.pyc │ │ ├── kdjsignal.cpython-38.pyc │ │ ├── macdsignal.cpython-38.pyc │ │ └── signalbase.cpython-38.pyc │ ├── kdjsignal.py │ ├── macdsignal.py │ └── signalbase.py ├── startengine.py ├── startengine_bt.py ├── startengine_live.py ├── stockdata.py ├── strategyengine.py ├── strategys │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-38.pyc │ │ ├── gridstrategy.cpython-38.pyc │ │ ├── meanrevertingstrategy.cpython-38.pyc │ │ └── strategybase.cpython-38.pyc │ ├── gridstrategy.py │ ├── meanrevertingstrategy.py │ └── strategybase.py ├── trader.py ├── updateposition.py └── util │ ├── __init__.py │ ├── __pycache__ │ ├── __init__.cpython-38.pyc │ ├── configutil.cpython-38.pyc │ ├── datetimeutil.cpython-38.pyc │ ├── fileutil.cpython-38.pyc │ ├── globalvars.cpython-38.pyc │ ├── numberutil.cpython-38.pyc │ ├── pdutil.cpython-38.pyc │ ├── putil.cpython-38.pyc │ ├── stockutil.cpython-38.pyc │ └── stringutil.cpython-38.pyc │ ├── configutil.py │ ├── datetimeutil.py │ ├── enumutil.py │ ├── fileutil.py │ ├── globalvars.py │ ├── numberutil.py │ ├── pdutil.py │ ├── putil.py │ ├── redisutil.py │ ├── stockutil.py │ └── stringutil.py └── requirements.txt /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/starquant.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /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 | # starquant<星球量化> 2 | 3 | starquant是基于掘金量化系统进行开发的量化交易框架,支持本地部署,支持断点回测, 4 | 支持自定义交易策略,选股数据及交易数据本地存贮及本地自定义统计。 5 | [图文使用说明](https://articles.zsxq.com/id_dnsu1lupjbr9.html) [视频使用说明](https://www.bilibili.com/video/BV1cb411o7oM/?vd_source=477f34fda05c82844e1e16ac83810323) 6 | 7 | ### 环境说明: 8 | 9 | * 支持的系统版本:Windows 10 10 | 11 | * 支持的Python版本: python 3.8 及以上版本 12 | 13 | * 掘金客户端:v3.16 14 | 15 | * 掘金API版本:3.0.159 (pip install gm==3.0.159) 16 | 17 | * Mysql: v5.0.96 18 | 19 | ### 安装说明 20 | 21 | * 必要库安装: 22 | ``` 23 | pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/ 24 | ``` 25 | ### 使用说明 26 | #### 掘金客户端配置: 27 | 1. 下载安装掘金客户端 28 | 29 | 首先从[掘金官网](https://myquant.cn) 下载掘金客户端v3.16,安装并注册帐号,具体可参考[官网指引](https://myquant.cn/docs/guide/35#32961c39feb7af92) 30 | 31 | 2. 注册帐号并登录客户端创建一个模拟的交易帐号。 32 | 33 | 3. 新建一个空策略,并记录策略ID 34 | 35 | 4. 挂接策略到交易帐号 36 | 37 | #### starquant量化交易框架参数设置: 38 | 39 | 1. 修改配置文件 40 | 41 | 配置文件路径:\starquant\quant\config.ini 42 | 43 | ``` 44 | [TOKEN] 45 | ##掘金token 46 | gmtoken = xxxxxx 47 | [ACCOUNT] 48 | 49 | ##绑定帐号的交易策略id 50 | strategy_id= 71878222-a222-222-2222-5811220c517b 51 | 52 | ##绑定帐号的交易策略id 53 | backtest_strategy_id= 71878222-a222-222-2222-5811220c517b 54 | 55 | ##指定连接数据库信息 56 | [DATABASE] 57 | tradedb = mysql+mysqlconnector://root:111111@localhost:3306/starquant 58 | 59 | ##掘金客户端安装路径 60 | [GOLDMINER] 61 | path =D:\Goldminer3\Hongshu Goldminer3\goldminer3.exe 62 | ``` 63 | 64 | 2. 数据库创建 65 | 66 | - 创建名称为starquantdb的空mysql数据库 67 | 68 | - 运行脚本数据库表: 69 | ``` 70 | mysql -uroot -p111111 stockdb < /iqunat/data/iquantdb.sql 71 | ``` 72 | 3. 修改setting表中,帐号ID字段值为你掘金创建的模拟交易帐号ID 73 | 74 | 4. 交易标的股票代码设置: 75 | 76 | 修改代码文件 starquant\quant\quantengine.py 可添加交易标的股票代码 77 | 78 | 5. 回测入口 79 | 80 | 运行 81 | ``` 82 | python starquant\quant\startengine_bt.py 83 | ``` 84 | 85 | 6. 实盘交易入口 86 | 87 | 运行 88 | ``` 89 | python starquant\quant\startengine_live.py 90 | ``` 91 | 92 | -------------------------------------------------------------------------------- /log/error_2023-03-10.txt: -------------------------------------------------------------------------------- 1 | 2023-03-10 16:01:00,751 - mylogger - ERROR - 4 2 | -------------------------------------------------------------------------------- /quant/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/__init__.py -------------------------------------------------------------------------------- /quant/account.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ############################################################################### 3 | # @author : vamed 4 | # @time :2021-02-02 5 | # @function:帐号类 6 | # Copyright (C) 2021-2023 7 | ############################################################################### 8 | # 9 | from gm.enum import MODE_BACKTEST 10 | from quant.metric import Metric 11 | 12 | class Account(): 13 | # 初始化交易参数 14 | def __init__(self,context): 15 | # 最大仓位 16 | self.MAX_POSITION=context.setting.max_position 17 | # 单股最大创位 18 | self.MAX_SINGLE_POSITION = context.setting.max_single_position 19 | # 最大持股数 20 | self.MAX_STOCK_NUMBER=context.setting.max_stock_number 21 | # 单股单次购买金额 22 | self.PER_BUY_AMOUNT=context.setting.per_buy_amount 23 | # 经纪商myquant,xtquant 24 | self.broker = context.setting.broker 25 | # 帐号ID 26 | self.account_id = context.setting.account_id 27 | # 帐号名称 28 | self.name=context.setting.name 29 | # 帐号类型 30 | self.account_type=context.setting.account_type 31 | # 初始总资金 32 | self.initial_capital = context.setting.initial_capital 33 | # # 34 | self.context=context 35 | # 36 | # 获取帐号ID 37 | def get_account_id(self): 38 | ret=self.account_id 39 | if self.context.mode==MODE_BACKTEST: 40 | if self.context.backtest_account_id=="": 41 | ret="{}{}".format(self.context.setting.account_id, self.context.backtest_id) 42 | else: 43 | ret=self.context.backtest_account_id 44 | return ret 45 | 46 | #打印显示当前帐号交易设置信息 47 | def display_run_param(self): 48 | print("=========================交易设置参数信息===============================") 49 | func_Account_type=lambda x:"实盘帐号" if x==1 else "测试帐号" 50 | print("当前运行帐号:{},id:{},帐号类型:{}".format(self.name,self.account_id,func_Account_type(self.account_type))) 51 | print("最大持仓位:{:.2f}%".format(self.MAX_POSITION*100)) 52 | print("最大持股数:{}".format(self.MAX_STOCK_NUMBER)) 53 | print("单股单次购买金额:{}".format(self.PER_BUY_AMOUNT)) 54 | print("最大单股持仓位:{:.2f}%".format(self.MAX_SINGLE_POSITION*100)) 55 | 56 | # 更新最大仓位设置 57 | def update_position_setting(self): 58 | win_rate= Metric().get_win_rate(account_id=self.get_account_id()) 59 | pos=self.get_kelly_position(win_rate[0],win_rate[1],win_rate[2]) 60 | # 更新最大仓位设置 61 | self.MAX_POSITION=pos 62 | # 最大持股数 63 | # 单股单次购买金额 64 | if self.PER_BUY_AMOUNT<(self.MAX_POSITION/self.MAX_STOCK_NUMBER)*self.initial_capital: 65 | self.PER_BUY_AMOUNT= (self.MAX_POSITION/self.MAX_STOCK_NUMBER)*self.initial_capital 66 | # 凯利公式计算最大仓位 67 | @classmethod 68 | def get_kelly_position(self,win_rate,exp_win,exp_loss): 69 | ret=0.1 70 | if (win_rate>0) and (exp_win>0): 71 | ret=win_rate-((1-win_rate)/exp_win*abs(exp_loss)) 72 | if ret<0: 73 | ret=0 74 | return ret 75 | 76 | 77 | if __name__=="__main__": 78 | pos=Account.get_kelly_position(0.21,0.0363,-0.0032) 79 | print(pos) 80 | -------------------------------------------------------------------------------- /quant/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/api/__init__.py -------------------------------------------------------------------------------- /quant/api/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/api/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /quant/api/__pycache__/gmapi.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/api/__pycache__/gmapi.cpython-38.pyc -------------------------------------------------------------------------------- /quant/api/__pycache__/singleton.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/api/__pycache__/singleton.cpython-38.pyc -------------------------------------------------------------------------------- /quant/api/__pycache__/tdxapi.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/api/__pycache__/tdxapi.cpython-38.pyc -------------------------------------------------------------------------------- /quant/api/futuapi.py: -------------------------------------------------------------------------------- 1 | from futu import * 2 | import pandas as pd 3 | 4 | from quant.api.singleton import singleton 5 | from quant.logger import Logger 6 | 7 | pd.set_option('display.max_rows', 5000) 8 | pd.set_option('display.max_columns', 5000) 9 | pd.set_option('display.width', 1000) 10 | 11 | # 富途接口 12 | @singleton 13 | class FutuApi(): 14 | # 拙金数据API封装 15 | # quote_ctx = None 16 | def __init__(self): 17 | self.quote_ctx = OpenQuoteContext(host='127.0.0.1', port=11111) 18 | 19 | def get_stock_info(self,code_list): 20 | ret, data =self.quote_ctx.get_stock_basicinfo(market=Market.SH, code_list= code_list) 21 | if ret == RET_OK: 22 | data['code']=data['code'].apply(lambda x:x.split('.')[1]) 23 | data.set_index('code',inplace=True) 24 | df=data.loc[:,['name']] 25 | # loginfo(df) 26 | return df 27 | else: 28 | Logger().loginfo('error:', data) 29 | # Logger().loginfo('******************************************') 30 | 31 | #获取资金流入量(单位是万) 32 | def get_stock_capital_in(self,code): 33 | ret, data = self.quote_ctx.get_capital_distribution(code) 34 | inflow=0 35 | if ret == RET_OK: 36 | inflow = round((data['capital_in_big'][0] + data['capital_in_mid'][0] + data['capital_in_small'][0] - 37 | data['capital_out_big'][0] - data['capital_out_mid'][0] - data['capital_out_small'][ 38 | 0]) / 10000, 2) 39 | return inflow 40 | 41 | #获取大单资金流入量(单位是万) 42 | def get_stock_capital_in_big(self,code): 43 | ret, data = self.quote_ctx.get_capital_distribution(code) 44 | inflow=0 45 | if ret == RET_OK: 46 | inflow = round((data['capital_in_big'][0] - data['capital_out_big'][0]) / 10000, 2) 47 | return inflow 48 | 49 | #获取大单资金流入量(单位是万) 50 | def get_rt_ticker(self,code): 51 | ret_sub, err_message = self.quote_ctx.subscribe(code, [SubType.TICKER], subscribe_push=False) 52 | # 先订阅逐笔类型。订阅成功后 FutuOpenD 将持续收到服务器的推送,False 代表暂时不需要推送给脚本 53 | if ret_sub == RET_OK: # 订阅成功 54 | ret, data = self.quote_ctx.get_rt_ticker(code, 1000) # 获取港股00700最近2个逐笔 55 | if ret == RET_OK: 56 | # print(data) 57 | # print(data['turnover'][0]) # 取第一条的成交金额 58 | # print(data['turnover'].values.tolist()) # 转为 list 59 | pass 60 | 61 | else: 62 | print('error:', data) 63 | else: 64 | print('subscription failed', err_message) 65 | 66 | return data 67 | # quote_ctx.close() # 关闭当条连接,FutuOpenD 会在1分钟后自动取消相应股票相应类型的订阅 68 | 69 | def get_hostory_kline(self,symbol, start=None, end=None): 70 | ret=self.quote_ctx.request_history_kline(symbol, start=start, end=end, ktype=KLType.K_DAY, autype=AuType.QFQ, fields=[KL_FIELD.ALL], 71 | max_count=1000, page_req_key=None, extended_time=False) 72 | df=ret[1] 73 | return df 74 | 75 | # 76 | def get_board(self,symbol_list): 77 | ret, data = self.quote_ctx.get_owner_plate(symbol_list) 78 | return data 79 | 80 | 81 | if __name__=='__main__': 82 | # df=FutuApi().get_board(['SH.600030']) 83 | # print(df) 84 | # ret, data = self.quote_ctx.get_owner_plate(['SH.600030']) 85 | df=FutuApi().get_hostory_kline(symbol='SH.BK0047') 86 | print(df) 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /quant/api/singleton.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ############################################################################### 3 | # @author : vamed 4 | # @time :2021-02-02 5 | # @function:单例 6 | # Copyright (C) 2021-2023 7 | ############################################################################### 8 | # 9 | def singleton(cls): 10 | _instance = {} # 新建空字典; 不要_instance = None, 否则 inner 函数中赋值时,_instance 只会在函数作用域搜索,判断时会报错; 11 | 12 | def inner(): # 判断是否已有实例,如无,则新建一个实例并返回 13 | if cls not in _instance: 14 | _instance[cls] = cls() 15 | 16 | return _instance[cls] # 返回的是实例对象 17 | 18 | return inner 19 | 20 | 21 | if __name__=="__main__": 22 | @singleton 23 | class Cls(): 24 | def __init__(self): 25 | pass # 可以照常添加属性和方法 26 | 27 | 28 | cls1 = Cls() 29 | cls2 = Cls() 30 | print(cls1 is cls2 ) # True 31 | -------------------------------------------------------------------------------- /quant/brokers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/brokers/__init__.py -------------------------------------------------------------------------------- /quant/brokers/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/brokers/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /quant/brokers/__pycache__/broker.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/brokers/__pycache__/broker.cpython-38.pyc -------------------------------------------------------------------------------- /quant/brokers/__pycache__/myquantbroker.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/brokers/__pycache__/myquantbroker.cpython-38.pyc -------------------------------------------------------------------------------- /quant/brokers/broker.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ############################################################################### 3 | # @author : vamed 4 | # @time :2021-02-02 5 | # @function:经纪商基类 6 | # Copyright (C) 2021-2023 7 | ############################################################################### 8 | # 9 | import datetime 10 | from abc import abstractmethod 11 | from gm.enum import MODE_BACKTEST 12 | from quant.model.order import Order 13 | from quant.model.position import Position 14 | from quant.util import numberutil 15 | 16 | # 经纪商基类 17 | class Broker(object): 18 | def __init__(self,context): 19 | self.context=context 20 | 21 | #限价买入指定量的股票 22 | @abstractmethod 23 | def buy(self,symbol,volume,price,factor,strategy,indicator): 24 | raise NotImplementedError('Method is required!') 25 | 26 | # 限价买出指定量的股票 27 | # @abstractmethod 28 | def sell(self,symbol,volume,price,factor,strategy,indicator): 29 | raise NotImplementedError('Method is required!') 30 | 31 | # 清仓 32 | @abstractmethod 33 | def sell_out(self,symbol,price,factor,strategy,indicator): 34 | raise NotImplementedError('Method is required!') 35 | 36 | #检查是否还有可买仓位 37 | @abstractmethod 38 | def hasPosition(self): 39 | raise NotImplementedError('Method is required!') 40 | 41 | #检查是否超过持仓股票数限制 42 | @abstractmethod 43 | def isUnderMaxStockNumber(self): 44 | raise NotImplementedError('Method is required!') 45 | 46 | #检查某股票是否超过持仓股票数限制 47 | @abstractmethod 48 | def isUnderMaxStockPosition(self,symbol): 49 | raise NotImplementedError('Method is required!') 50 | 51 | # 判断是否有未完成的订单 52 | @abstractmethod 53 | def has_unfinished_orders(self,symbol): 54 | raise NotImplementedError('Method is required!') 55 | 56 | # 金额转为量,并取整 57 | def get_amount(self,value,price): 58 | volume = int(value / price) 59 | volume = numberutil.get_neat(volume) 60 | return volume 61 | 62 | # 发送订单回回调 63 | def send_order_callback(self, account_id,cl_ord_id, symbol,side, price, volume,factor,strategy,indicator,trade_date): 64 | 65 | if self.send_order_event_handler: 66 | order = Order() 67 | order.account_id=account_id 68 | order.cl_ord_id = cl_ord_id 69 | order.symbol =symbol 70 | if side==2: 71 | side=-1 72 | order.side = side 73 | order.price = price 74 | order.volume = volume 75 | order.amount=price*volume 76 | order.factor = factor 77 | order.indicator = indicator 78 | order.strategy = strategy 79 | order.trade_date = trade_date.strftime('%Y-%m-%d') 80 | order.trade_time = trade_date 81 | order.record_time = datetime.datetime.now() 82 | order.status=1 83 | order.mode=self.context.mode 84 | # 更新持仓股票可用数 85 | if self.context.mode==MODE_BACKTEST: 86 | if order.side == -1: 87 | for i in range(len(self.context.positions)): 88 | if self.context.positions[i].symbol == order.symbol: 89 | self.context.positions[i].volume = self.context.positions[i].volume - order.volume 90 | self.context.positions[i].can_use_volume = self.context.positions[i].can_use_volume - order.volume 91 | if self.context.positions[i].volume<0: 92 | self.context.positions[i].volume=0 93 | if self.context.positions[i].can_use_volume<0: 94 | self.context.positions[i].can_use_volume=0 95 | break 96 | order.insert() 97 | self.send_order_event_handler(order) 98 | 99 | # 屏幕打印帐号持仓信息 100 | def display_account_position_info(self,pos): 101 | print("=========================当前帐号持仓信息===============================") 102 | if len(pos)>0: 103 | df=Position().to_df(pos) 104 | market_value=df['market_value'].sum() 105 | profit=df['profit_amount'].sum() 106 | print("当前持股数:{}".format(len(df))) 107 | print("当前持仓金额:{:.2f}元".format(market_value)) 108 | print("当前持仓收益:{:.2f}元".format(profit)) 109 | print("当前持仓收益率:{:.2f}%".format(profit/(market_value-profit)*100)) 110 | print(df) 111 | else: 112 | print("当前帐号没有持仓") 113 | 114 | # 屏幕打印帐号资金信息 115 | def display_account_asset_info(self): 116 | asset=self.getAsset() 117 | print("=========================资金信息===============================") 118 | print("当前运行帐号:{},id:{}".format(asset.account_name,asset.account_id)) 119 | print("初始总资金:{:.2f}元".format(asset.initial_capital)) 120 | print("帐号总资产现值:{:.2f}元".format(asset.total_asset)) 121 | print("总收益:{:.2f}元".format(asset.total_asset-asset.initial_capital)) 122 | print("总收益率:{:.2f}%".format((asset.total_asset-asset.initial_capital)/asset.initial_capital*100)) 123 | print("当前持仓市值:{:.2f}元".format(asset.market_value)) 124 | print("当前持仓位:{:.2f}%".format(asset.market_value/asset.initial_capital*100)) 125 | print("当前可用资金:{:.2f}元".format(asset.cash)) -------------------------------------------------------------------------------- /quant/brokers/myquantbroker.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ############################################################################### 3 | # @author : vamed 4 | # @time :2021-02-02 5 | # @function:掘金经纪商封装 6 | # Copyright (C) 2021-2023 7 | ############################################################################### 8 | # 9 | from gm.api import order_volume, get_unfinished_orders, order_cancel, order_close_all, order_cancel_all, \ 10 | order_target_percent 11 | from gm.enum import OrderSide_Sell, OrderType_Limit, PositionEffect_Open, PositionSide_Long, OrderSide_Buy, \ 12 | PositionEffect_Close, MODE_LIVE, MODE_BACKTEST, OrderType_Market 13 | 14 | from quant.brokers.broker import Broker 15 | from quant.account import Account 16 | from quant.enums.orderside import OrderSides 17 | from quant.model.asset import Asset 18 | from quant.model.order import Order 19 | from quant.model.position import Position 20 | import pandas as pd 21 | from quant.util import stockutil 22 | 23 | # 掘金经纪商 24 | class MyquantBroker(Broker): 25 | # 26 | def __init__(self,context): 27 | self.trade_records = dict() 28 | self.context = context 29 | self.account = Account(self.context) 30 | 31 | #限价买入指定量的股票 32 | def buy(self,symbol,volume,price,factor,strategy,indicator=''): 33 | ret=order_volume(symbol=symbol, volume=volume, side=OrderSide_Buy, order_type=OrderType_Limit, 34 | position_effect=PositionEffect_Open, price=price) 35 | print(ret) 36 | # 发送订单回回调 37 | self.send_order_callback(self.account.get_account_id(),ret[0].cl_ord_id,symbol,OrderSides.OrderSide_Buy.value, price, volume,factor,strategy,indicator,self.context.now) 38 | return ret 39 | 40 | # 限价买出指定量的股票 41 | def sell(self,symbol,volume,price,factor,strategy,indicator=''): 42 | ret=order_volume(symbol=symbol, volume=volume, side=OrderSide_Sell, order_type=OrderType_Limit, 43 | position_effect=PositionEffect_Close, price=price) 44 | print(ret) 45 | # 发送订单回回调 46 | self.send_order_callback( self.account.get_account_id(),ret[0].cl_ord_id, symbol, OrderSides.OrderSide_Sell.value, price, 47 | volume,factor,strategy,indicator,self.context.now) 48 | 49 | # 清仓某股票仓位 50 | def sell_out(self,symbol,price,factor,strategy,indicator=''): 51 | pos=self.getSymbolPosition(symbol) 52 | if pos.account_id: 53 | if (pos.can_use_volume>0): 54 | volume=pos.can_use_volume 55 | self.sell(symbol,volume=volume,price=price,factor=factor,strategy=strategy,indicator=indicator) 56 | 57 | 58 | #限价买入指定量的股票 59 | def order_target_percent(self,symbol,percent,factor,strategy,indicator=''): 60 | ret=order_target_percent(symbol=symbol, percent=percent, 61 | order_type=OrderType_Market, 62 | position_side=PositionSide_Long) 63 | # 发送订单回回调 64 | if ret[0].side==1: 65 | side=OrderSides.OrderSide_Buy.value 66 | else: 67 | side = OrderSides.OrderSide_Sell.value 68 | self.send_order_callback(self.account.get_account_id(),ret[0].cl_ord_id,symbol,side, ret[0].price, ret[0].volume,factor,strategy,indicator,self.context.now) 69 | 70 | return ret 71 | # 清仓 72 | def close_all(self): 73 | # 先撤消所有委托 74 | order_cancel_all() 75 | # 清仓 76 | order_close_all() 77 | 78 | # 判断是否有未完成的订单 79 | def has_unfinished_orders(self,code,side): 80 | ret=False 81 | orders = get_unfinished_orders() 82 | for i,o in enumerate(orders): 83 | if (o.symbol==code) and (o.side==side): 84 | ret=True 85 | break 86 | return ret 87 | 88 | # 订单取消 89 | def cancel_order(self,code,side): 90 | orders = get_unfinished_orders() 91 | ords=[] 92 | if side==0: 93 | for i, o in enumerate(orders): 94 | order = {'cl_ord_id': o['cl_ord_id'], 'account_id': o['account_id']} 95 | ords.append(order) 96 | else: 97 | for i, o in enumerate(orders): 98 | if (code!='') and (o.symbol != code): 99 | continue 100 | if (side!=0) and (o.side != side): 101 | continue 102 | order = {'cl_ord_id': o['cl_ord_id'], 'account_id': o['account_id']} 103 | ords.append(order) 104 | 105 | order_cancel(wait_cancel_orders=ords) 106 | 107 | # 获取当前资产总金额 108 | def getCapital(self): 109 | account = self.context.account() 110 | capital=account.cash.nav 111 | return capital 112 | 113 | # 获取帐号资产信息 114 | def getAsset(self): 115 | asset=Asset() 116 | account = self.context.account() 117 | asset.account_id=account.cash.account_id 118 | asset.account_name = account.cash.account_name 119 | asset.total_asset=account.cash.nav 120 | asset.cash=account.cash.available 121 | asset.frozen_cash=account.cash.order_frozen 122 | asset.market_value=account.cash.market_value 123 | asset.fpnl=account.cash.fpnl 124 | asset.initial_capital = self.account.initial_capital 125 | return asset 126 | 127 | # 获取当前持仓明细 128 | def getPositions(self,display=False): 129 | positions=[] 130 | pos = self.context.account().positions() 131 | symbols=[] 132 | for i in range(len(pos)): 133 | position=Position() 134 | # position.account_id=pos[i].account_id 135 | position.account_id=self.account.get_account_id() 136 | position.code = pos[i].symbol 137 | position.symbol = pos[i].symbol 138 | position.volume = pos[i].volume 139 | position.amount = pos[i].vwap*pos[i].volume 140 | position.on_road_volume = pos[i].volume_today 141 | position.frozen_volume = pos[i].order_frozen 142 | position.open_price=pos[i].vwap 143 | position.market_value = pos[i].market_value 144 | position.can_use_volume= position.volume-position.on_road_volume-position.frozen_volume 145 | position.price =round(position.market_value / position.volume, 2) 146 | position.profit_amount=position.market_value-position.amount 147 | position.date=pos[i].updated_at 148 | position.update_time=pos[i].updated_at 149 | position.created_at=pos[i].created_at 150 | position.profit_rate=round(position.profit_amount/position.amount*100,2) 151 | positions.append(position) 152 | symbols.append(pos[i].symbol) 153 | 154 | if self.context.mode == MODE_BACKTEST: 155 | pos=self.get_backtest_position(account_id=self.context.backtest_account_id,date=self.context.now.strftime("%Y-%m-%d")) 156 | for p in pos: 157 | if p.symbol not in symbols: 158 | positions.append(p) 159 | 160 | if display==True: 161 | self.display_account_position_info(positions) 162 | return positions 163 | 164 | # 获取某股票当前持仓金额 165 | def getSymbolPosition(self,symbol): 166 | ret=Position() 167 | ret.market_value=0 168 | ret.can_use_volume=0 169 | pos = self.context.account().position(symbol=symbol, side=PositionSide_Long) 170 | if pos!=None: 171 | ret.account_id=pos.account_id 172 | ret.code = pos.symbol 173 | ret.symbol = pos.symbol 174 | ret.volume = pos.volume 175 | ret.amount = pos.cost 176 | ret.on_road_volume = pos.volume_today 177 | ret.frozen_volume = pos.order_frozen 178 | ret.open_price=pos.vwap 179 | ret.market_value = pos.market_value 180 | ret.can_use_volume= ret.volume-ret.on_road_volume-ret.frozen_volume 181 | ret.price =round(ret.market_value / ret.volume, 2) 182 | ret.profit_amount=ret.market_value-ret.amount 183 | ret.date=pos.updated_at 184 | ret.update_time = pos.updated_at 185 | ret.profit_rate=round(ret.profit_amount/ret.amount*100,2) 186 | elif self.context.mode == MODE_BACKTEST: 187 | for p in self.context.positions: 188 | if p.symbol == symbol: 189 | ret=p 190 | break 191 | return ret 192 | 193 | # 获取当前股票持仓比例 194 | def getSymbolPositionRate(self, symbol): 195 | capital = self.getCapital() 196 | pos=self.getSymbolPosition(symbol) 197 | ret = pos.market_value / capital 198 | return ret 199 | 200 | # 获取当前持仓股票数量 201 | def getStockNumber(self): 202 | positions = self.context.account().positions() 203 | ret=len(positions) 204 | return ret 205 | 206 | # 获取当前持仓金额 207 | def getPositionAmount(self): 208 | ret = 0 209 | positions = self.context.account().positions() 210 | if len(positions)>0: 211 | df=pd.DataFrame(positions) 212 | ret = df['amount'].sum() 213 | return ret 214 | 215 | # 获取仓位比例 216 | def getPositionRate(self): 217 | capital=self.getCapital() 218 | amount=self.getPositionAmount() 219 | ret=round((amount/capital),2) 220 | return ret 221 | 222 | #检查是否还有可买仓位 223 | def hasPosition(self): 224 | ret=True 225 | if self.getPositionRate()>=self.account.MAX_POSITION: 226 | ret=False 227 | return ret 228 | 229 | #检查是否超过持仓股票数限制 230 | def isUnderMaxStockNumber(self): 231 | ret=True 232 | if self.getStockNumber()>=self.account.MAX_STOCK_NUMBER: 233 | ret=False 234 | return ret 235 | 236 | #检查某股票是否超过持仓股票数限制 237 | def isUnderMaxStockPosition(self,symbol): 238 | ret=True 239 | if self.getSymbolPositionRate(symbol)>=self.account.MAX_SINGLE_POSITION: 240 | ret=False 241 | return ret 242 | 243 | # 获取回测持仓 244 | def get_backtest_position(self,account_id,date): 245 | df=Order().get_unfinished_order(account_id=account_id,date=date) 246 | 247 | positions=[] 248 | for i,row in df.iterrows(): 249 | pos=Position() 250 | pos.account_id = row['account_id'] 251 | pos.symbol=row['symbol'] 252 | pos.code =stockutil.delSymbolPrefix(row['symbol']) 253 | pos.open_price=row['price'] 254 | pos.volume=row['volume'] 255 | pos.amount = pos.open_price * pos.volume 256 | pos.can_use_volume = row['volume'] 257 | pos.frozen_volume = 0 258 | pos.on_road_volume = 0 259 | pos.price=pos.open_price 260 | pos.market_value=pos.amount 261 | pos.profit=0 262 | pos.profit_rate=0 263 | pos.profit_amount=0 264 | positions.append(pos) 265 | return positions -------------------------------------------------------------------------------- /quant/config.ini: -------------------------------------------------------------------------------- 1 | [TOKEN] 2 | gmtoken = xxxxx 3 | 4 | [ACCOUNT] 5 | strategy_id=xxxx 6 | backtest_strategy_id=xxx 7 | 8 | [DATABASE] 9 | tradedb = mysql+mysqlconnector://root:111111@localhost:3306/iquant 10 | 11 | 12 | [GOLDMINER] 13 | path =D:\Goldminer3\Hongshu Goldminer3\goldminer3.exe -------------------------------------------------------------------------------- /quant/empyrical/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2016 Quantopian, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # flake8: noqa 16 | 17 | from ._version import get_versions 18 | __version__ = get_versions()['version'] 19 | del get_versions 20 | 21 | from .stats import ( 22 | cum_returns, 23 | cum_returns_final, 24 | aggregate_returns, 25 | max_drawdown, 26 | annual_return, 27 | annual_volatility, 28 | calmar_ratio, 29 | omega_ratio, 30 | sharpe_ratio, 31 | sortino_ratio, 32 | downside_risk, 33 | information_ratio, 34 | alpha_beta, 35 | alpha, 36 | beta, 37 | alpha_beta_aligned, 38 | alpha_aligned, 39 | beta_aligned, 40 | stability_of_timeseries, 41 | tail_ratio, 42 | cagr, 43 | DAILY, 44 | WEEKLY, 45 | MONTHLY, 46 | YEARLY 47 | ) 48 | -------------------------------------------------------------------------------- /quant/empyrical/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/empyrical/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /quant/empyrical/__pycache__/_version.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/empyrical/__pycache__/_version.cpython-38.pyc -------------------------------------------------------------------------------- /quant/empyrical/__pycache__/stats.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/empyrical/__pycache__/stats.cpython-38.pyc -------------------------------------------------------------------------------- /quant/empyrical/__pycache__/utils.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/empyrical/__pycache__/utils.cpython-38.pyc -------------------------------------------------------------------------------- /quant/empyrical/_version.py: -------------------------------------------------------------------------------- 1 | 2 | # This file was generated by 'versioneer.py' (0.16) from 3 | # revision-control system data, or from the parent directory name of an 4 | # unpacked source archive. Distribution tarballs contain a pre-generated copy 5 | # of this file. 6 | 7 | import json 8 | import sys 9 | 10 | version_json = ''' 11 | { 12 | "dirty": false, 13 | "error": null, 14 | "full-revisionid": "5d38634e0d68e65699383d2a97edaf34ce91fada", 15 | "version": "0.2.1" 16 | } 17 | ''' # END VERSION_JSON 18 | 19 | 20 | def get_versions(): 21 | return json.loads(version_json) 22 | -------------------------------------------------------------------------------- /quant/empyrical/utils.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2016 Quantopian, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | try: 17 | # fast versions 18 | import bottleneck as bn 19 | nanmean = bn.nanmean 20 | nanstd = bn.nanstd 21 | nansum = bn.nansum 22 | nanmax = bn.nanmax 23 | nanmin = bn.nanmin 24 | nanargmax = bn.nanargmax 25 | nanargmin = bn.nanargmin 26 | except ImportError: 27 | # slower numpy 28 | import numpy as np 29 | nanmean = np.nanmean 30 | nanstd = np.nanstd 31 | nansum = np.nansum 32 | nanmax = np.nanmax 33 | nanmin = np.nanmin 34 | nanargmax = np.nanargmax 35 | nanargmin = np.nanargmin 36 | -------------------------------------------------------------------------------- /quant/enums/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/enums/__init__.py -------------------------------------------------------------------------------- /quant/enums/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/enums/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /quant/enums/__pycache__/brokers.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/enums/__pycache__/brokers.cpython-38.pyc -------------------------------------------------------------------------------- /quant/enums/__pycache__/dfcolumns.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/enums/__pycache__/dfcolumns.cpython-38.pyc -------------------------------------------------------------------------------- /quant/enums/__pycache__/orderside.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/enums/__pycache__/orderside.cpython-38.pyc -------------------------------------------------------------------------------- /quant/enums/__pycache__/ratings.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/enums/__pycache__/ratings.cpython-38.pyc -------------------------------------------------------------------------------- /quant/enums/__pycache__/scopes.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/enums/__pycache__/scopes.cpython-38.pyc -------------------------------------------------------------------------------- /quant/enums/__pycache__/strategys.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/enums/__pycache__/strategys.cpython-38.pyc -------------------------------------------------------------------------------- /quant/enums/__pycache__/strategytype.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/enums/__pycache__/strategytype.cpython-38.pyc -------------------------------------------------------------------------------- /quant/enums/brokers.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ############################################################################### 3 | # @author : vamed 4 | # @time :2021-02-02 5 | # @function:经纪商枚举类 6 | # Copyright (C) 2021-2023 7 | ############################################################################### 8 | # 9 | from enum import Enum, unique 10 | 11 | @unique # @unique装饰器可以帮助我们检查保证没有重复值 12 | class Brokers(Enum): 13 | def __new__(cls, value=None): 14 | obj = object.__new__(cls) 15 | # obj.english = value 16 | return obj 17 | # 掘金 18 | myquant = 'myquant' 19 | # 迅投qmt 20 | xtquant = 'xtquant' 21 | 22 | if __name__=='__main__': 23 | # print(Factors.raising_limit.value) 24 | for b in Brokers: 25 | print(b.value) 26 | -------------------------------------------------------------------------------- /quant/enums/dfcolumns.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, unique 2 | 3 | @unique 4 | class DfColumns(Enum): 5 | def __new__(cls, value=None): 6 | obj = object.__new__(cls) 7 | # obj.english = value 8 | return obj 9 | 10 | close = 'close' 11 | dif = 'dif' 12 | dea = 'dea' 13 | macd = 'macd' 14 | k = 'k' 15 | d = 'd' 16 | j = 'j' 17 | # 分钟均价 18 | map = 'map' 19 | 20 | if __name__=='__main__': 21 | print(DfColumns.macd.value) -------------------------------------------------------------------------------- /quant/enums/orderside.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ############################################################################### 3 | # @author : vamed 4 | # @time :2021-02-02 5 | # @function:交易方向枚举类 6 | # Copyright (C) 2021-2023 7 | ############################################################################### 8 | # 9 | from enum import Enum, unique 10 | 11 | # 12 | @unique 13 | class OrderSides(Enum): 14 | def __new__(cls, value=None): 15 | obj = object.__new__(cls) 16 | # obj.english = value 17 | return obj 18 | 19 | OrderSide_Unknown = 0 20 | OrderSide_Buy = 1 # 买入 21 | OrderSide_Sell = 2 # 卖出 22 | 23 | if __name__=='__main__': 24 | pass 25 | -------------------------------------------------------------------------------- /quant/enums/ratings.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ############################################################################### 3 | # @author : vamed 4 | # @time :2021-02-02 5 | # @function:股票评级枚举类 6 | # Copyright (C) 2021-2023 7 | ############################################################################### 8 | # 9 | from enum import Enum, unique 10 | 11 | @unique 12 | class Ratings(Enum): 13 | def __new__(cls, value=None): 14 | obj = object.__new__(cls) 15 | # obj.english = value 16 | return obj 17 | 18 | BuyIn = '买入' 19 | SellOut = '卖出' 20 | Holding = '持有' 21 | Neutral = '观望' 22 | 23 | if __name__=='__main__': 24 | print(Ratings.raising_limit.value) -------------------------------------------------------------------------------- /quant/enums/scopes.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ############################################################################### 3 | # @author : vamed 4 | # @time :2021-02-02 5 | # @function:策略作用范围枚举类 6 | # Copyright (C) 2021-2023 7 | ############################################################################### 8 | # 9 | from enum import Enum, unique 10 | # 11 | @unique 12 | class Scopes(Enum): 13 | def __new__(cls, value=None): 14 | obj = object.__new__(cls) 15 | return obj 16 | 17 | stock = 'stock' 18 | universal= 'universal' 19 | -------------------------------------------------------------------------------- /quant/enums/strategys.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ############################################################################### 3 | # @author : vamed 4 | # @time :2021-02-02 5 | # @function:策略类型枚举类 6 | # Copyright (C) 2021-2023 7 | ############################################################################### 8 | # 9 | from enum import Enum, unique 10 | 11 | @unique 12 | class Strategys(Enum): 13 | def __new__(cls, value=None): 14 | obj = object.__new__(cls) 15 | # obj.english = value 16 | return obj 17 | 18 | GridStrategy='GridStrategy' 19 | MeanRevertingStrategy = 'MeanRevertingStrategy' 20 | TrendFollowingStrategy = 'TrendFollowingStrategy' 21 | DayTradingStrategy= 'DayTradingStrategy' 22 | -------------------------------------------------------------------------------- /quant/enums/strategytype.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ############################################################################### 3 | # @author : vamed 4 | # @time :2021-02-02 5 | # @function:策略驱动类型枚举类 6 | # Copyright (C) 2021-2023 7 | ############################################################################### 8 | # 9 | from enum import Enum, unique 10 | @unique # @unique装饰器可以帮助我们检查保证没有重复值 11 | class StrategyType(Enum): 12 | def __new__(cls, value=None): 13 | obj = object.__new__(cls) 14 | # obj.english = value 15 | return obj 16 | 17 | bar = 'bar' 18 | tick = 'tick' 19 | schedule = 'schedule' 20 | -------------------------------------------------------------------------------- /quant/indicators.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ############################################################################### 3 | # @author : vamed 4 | # @time :2021-02-02 5 | # @function:指标类 6 | # Copyright (C) 2021-2023 7 | ############################################################################### 8 | # 9 | 10 | import numpy as np 11 | import talib 12 | import pandas as pd 13 | from quant.api.gmapi import GmApi 14 | 15 | class Indicator(): 16 | def __init__(self): 17 | pass 18 | # sma 19 | @classmethod 20 | def sma(self,df): 21 | df.loc[:, 'ma5'] = talib.MA(df.close, timeperiod=5, matype=0) 22 | df.loc[:, 'ma10'] = talib.MA(df.close, timeperiod=10, matype=0) 23 | df.loc[:, 'ma20'] = talib.MA(df.close, timeperiod=20, matype=0) 24 | df.loc[:, 'ma60'] = talib.MA(df.close, timeperiod=60, matype=0) 25 | df.loc[:, 'ma120'] = talib.MA(df.close, timeperiod=120, matype=0) 26 | df.loc[:, 'ma250'] = talib.MA(df.close, timeperiod=250, matype=0) 27 | return df 28 | 29 | # macd 30 | @classmethod 31 | def macd(self,df): 32 | adjust=True 33 | df['dif'] = df['close'].ewm(span=12, adjust=adjust).mean() - df['close'].ewm(span=26, adjust=adjust).mean() 34 | df['dea'] = df['dif'].ewm(span=9, adjust=adjust).mean() 35 | df['macd'] = 2 * (df['dif'] - df['dea']) 36 | df['short_ema']=np.round(talib.EMA(df['close'].values, 12),3) 37 | df['long_ema'] = np.round(talib.EMA(df['close'].values, 26), 3) 38 | # df['dif']=df['short_ema']-df['long_ema'] 39 | # df['dea']=np.round(talib.EMA(df['dif'].values, 9),3) 40 | # df['macd'] = 2 * (np.round(df['dif'] - df['dea'],3)) 41 | 42 | return df 43 | 44 | # kdj 45 | @classmethod 46 | def kdj(self,df1): 47 | df=df1.copy() 48 | low_list =df['low'].rolling(window=9,min_periods=1).min() 49 | low_list.fillna(value=df['low'].expanding().min(), inplace=True) 50 | high_list = df['high'].rolling(window=9,min_periods=1).max() 51 | high_list.fillna(value=df['high'].expanding().max(), inplace=True) 52 | 53 | if len(high_list[high_list== low_list])==0: 54 | rsv = ((df['close'] - low_list) / (high_list - low_list) * 100) 55 | else: 56 | arr_t=high_list[high_list== low_list] 57 | arr_f=high_list[high_list!= low_list] 58 | rsv_f = ((df['close'][arr_f.index] - low_list[arr_f.index]) / (high_list[arr_f.index] - low_list[arr_f.index]) * 100) 59 | # rsv_t= ((df.loc[arr_t,['close']] - low_list[arr_t]) / (high_list[arr_t] - low_list[arr_t]) * 100) 60 | data= np.ones(len(arr_t))*100 61 | rsv_t=pd.Series(data,arr_t.index) 62 | rsv=pd.concat([rsv_f,rsv_t],axis=0,ignore_index=False).sort_index() 63 | 64 | df.loc[:,'k'] =np.round(rsv.ewm(com=2).mean(),3) 65 | df.loc[:,'d'] = np.round(df['k'].ewm(com=2).mean(),3) 66 | df.loc[:,'j'] = np.round(3 * df['k'] - 2 * df['d'],3) 67 | return df 68 | 69 | # rsi 70 | @classmethod 71 | def rsi(self,df): 72 | df["rsi_fast"] = talib.RSI(df['close'], timeperiod=9) 73 | df["rsi_slow"] = talib.RSI(df['close'], timeperiod=12) 74 | df["rsi_long"] = talib.RSI(df['close'], timeperiod=72) 75 | return df 76 | 77 | # atr 78 | @classmethod 79 | def atr(self,df): 80 | df['preclose']=df['close'].shift(1) 81 | df['preclose']=df['preclose'].fillna(method='bfill') 82 | df['tr']=df.apply(lambda x:max(x['high']-x['low'], abs(x['high'] - x['preclose']), abs(x['low'] - x['preclose'])),axis=1) 83 | df['atr'] = df['tr'].rolling(window=14, min_periods=1).mean() 84 | return df 85 | 86 | # mtm动量指标 87 | @classmethod 88 | def mtm(self,df,N=12, M=6): 89 | """ 90 | MTM动力指标 91 | 算法: 92 | MTM线  当日收盘价与N日前的收盘价的差 93 | MTMMA线 对上面的差值求N日移动平均 94 | 参数:N 间隔天数,也是求移动平均的天数,一般取6 95 | 用法: 96 | 1.MTM从下向上突破MTMMA,买入信号 97 | 2.MTM从上向下跌破MTMMA,卖出信号 98 | 3.股价续创新高,而MTM未配合上升,意味上涨动力减弱 99 | 4.股价续创新低,而MTM未配合下降,意味下跌动力减弱 100 | 5.股价与MTM在低位同步上升,将有反弹行情;反之,从高位同步下降,将有回落走势。 101 | """ 102 | df['mtm'] = np.round(df['close'] - df['close'].shift(N),3) 103 | df['mtmma'] =np.round(df['mtm'].rolling(M).mean(),3) 104 | return df 105 | 106 | # boll 107 | @classmethod 108 | def boll(self,df): 109 | df['boll_up'], df['boll_mid'], df['boll_low'] = talib.BBANDS(df['close'], timeperiod=20, nbdevup=2, nbdevdn=2, matype=0) 110 | return df 111 | 112 | # bias 113 | @classmethod 114 | def bias(self,df): 115 | df['bias_short'] = (df['close'] - df['close'].rolling(6, min_periods=1).mean()) / df['close'].rolling(6,min_periods=1).mean() * 100 116 | df['bias_mid'] = (df['close'] - df['close'].rolling(12, min_periods=1).mean()) / df['close'].rolling(12,min_periods=1).mean() * 100 117 | df['bias_long'] = (df['close'] - df['close'].rolling(24, min_periods=1).mean()) / df['close'].rolling(24,min_periods=1).mean() * 100 118 | df['bias_short'] = round(df['bias_short'], 2) 119 | df['bias_mid'] = round(df['bias_mid'], 2) 120 | df['bias_long'] = round(df['bias_long'], 2) 121 | return df 122 | 123 | # 计算所有指标 124 | @classmethod 125 | def cal_indicator(self,df): 126 | df= self.macd(df) 127 | df = self.sma(df) 128 | # df = self.kdj(df) 129 | df = self.rsi(df) 130 | df = self.boll(df) 131 | df = self.bias(df) 132 | return df 133 | 134 | # 计算即时kdj 135 | @classmethod 136 | def instant_kdj(self,high,low,close,k_pre,d_pre,df_bar): 137 | if high< df_bar['high'].max(): 138 | high=df_bar['high'].max() 139 | if low> df_bar['low'].min(): 140 | low=df_bar['low'].min() 141 | if high==low: 142 | rsv=100 143 | else: 144 | rsv=(close-low)/(high-low)*100 145 | k=(2/3*k_pre)+(1/3*rsv) 146 | d=(2/3*d_pre)+(1/3*k) 147 | j=(3*k)-(2*d) 148 | return np.round(k,3),np.round(d,3),np.round(j,3) 149 | 150 | @classmethod 151 | def ema(self,x,n,pre_y): 152 | return (2 * x + (n - 1) * pre_y) / (n + 1) 153 | 154 | # 计算即时macd 155 | @classmethod 156 | def instant_macd(self,price,pre_short_ema,pre_long_ema,pre_dea): 157 | short_ema = self.ema(price, 12, pre_short_ema) 158 | long_ema = self.ema(price, 26, pre_long_ema) 159 | dif = short_ema - long_ema 160 | dea = self.ema(dif, 9, pre_dea) 161 | macd = (dif - dea) * 2 162 | return (dif,dea,macd) 163 | 164 | # 计算即时boll 165 | @classmethod 166 | def instant_boll(self,close_19,price): 167 | close_20=np.append(close_19,price) 168 | boll_mid=np.mean(close_20,axis=0) 169 | # 求标准差 170 | arr_std = np.std(close_20, ddof=0) 171 | boll_up=boll_mid + 2 * arr_std 172 | boll_low = boll_mid - 2 * arr_std 173 | return boll_low,boll_mid,boll_up 174 | 175 | # 计算即时均值 176 | @classmethod 177 | def instant_sma(self,close_array,price): 178 | # arr=close_array[-n+1:] 179 | arr=np.append(close_array,price) 180 | ret=np.mean(arr) 181 | return ret 182 | 183 | # 计算即时mtm 184 | @classmethod 185 | def instant_mtm(self,pren_close,mtm5,price): 186 | mtm=np.round(price-pren_close,3) 187 | mtmma = np.round((mtm5 + mtm) / 6,3) 188 | return mtm,mtmma 189 | 190 | if __name__=="__main__": 191 | # df = pro.query('daily', ts_code='002526.SZ', start_date='20110801', end_date='20200810') 192 | df = GmApi().getSymbolHistoryKdata('SHSE.601318', start_time='2022-04-01', end_time='2022-11-10') 193 | # df1 = Indicator.boll(df.copy()) 194 | # df2=df.iloc[-20:-1,:].copy() 195 | 196 | # df['prenclose'] = df['close'].shift(6) 197 | # df['prenclose'].fillna(method='bfill',inplace=True) 198 | 199 | 200 | bol= Indicator.macd(df) 201 | # mtm=15.21-14.78 202 | # mtmma=(bol['mtm'].sum()+mtm)/6 203 | # 204 | print(bol) 205 | 206 | # macd=Indicator().instant_macd(38.46,38.234,39.015,-0.969) 207 | # print(macd) 208 | # bol=bol.tail(5) 209 | # mtmma=Indicator.instant_mtm(19.1739,bol['mtm'].sum(),19.92) 210 | # print(mtmma) 211 | # # 212 | # print(bol) 213 | # print(df) 214 | # df['k_pre'] = df['k'].shift(1) 215 | # df["k_pre"].fillna(method='bfill', inplace=True) 216 | # df['d_pre'] = df['d'].shift(1) 217 | # df["d_pre"].fillna(method='bfill', inplace=True) 218 | # # df=df.loc[(df['k'] >= df['d']) & (df['k_pre'] < df['d_pre'])] 219 | # df=df.loc[(df['k'] < df['d']) & (df['k_pre'] >= df['d_pre'])] 220 | # df.to_csv("e:\\300919.csv") 221 | # df.loc[(df['dif'] < df['dea']) & (df['dif_pre'] >= df['dea_pre']), ['signal']] = 0 222 | 223 | # 上向下突破上轨,买出 224 | # df.loc[(df['close_pre'] >= df['boll_up_pre']) & (df['close'] < df['boll_up']), ['signal']] = 0 225 | # print(df1.tail(10)) 226 | # df['pre_close'] = df['close'].shift() 227 | # df["pre_close"].fillna(method='bfill', inplace=True) 228 | # df['change'] = (df['close'] - df['pre_close']) / df['pre_close'] 229 | # df=Indicator.cal_indicator(df) 230 | # df.index=pd.to_datetime(df.index) 231 | # figure = mpyplot.figure(figsize=(20, 8), facecolor='#ffffff', edgecolor='#000000', linewidth=1) 232 | # plt= Plot_OldSync() 233 | # plt.plotfig(df=df,fig=figure) 234 | # plt.show() 235 | pass 236 | # print(df) 237 | -------------------------------------------------------------------------------- /quant/job/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/job/__init__.py -------------------------------------------------------------------------------- /quant/job/run_bt.bat: -------------------------------------------------------------------------------- 1 | D:\ProgramData\Anaconda3\envs\py38\python.exe E:/code/python/myquant/quant/startengine_live.py --mode 2 2 | 3 | -------------------------------------------------------------------------------- /quant/job/run_live.bat: -------------------------------------------------------------------------------- 1 | D:\ProgramData\Anaconda3\envs\py38\python.exe E:/code/python/myquant/quant/startengine_live.py --mode 1 2 | 3 | 4 | -------------------------------------------------------------------------------- /quant/logger.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ############################################################################### 3 | # @author : vamed 4 | # @time :2021-02-02 5 | # @function:日志类 6 | # Copyright (C) 2021-2023 7 | ############################################################################### 8 | # 9 | import datetime 10 | import sys,os 11 | base_dir=os.path.dirname(os.path.dirname(__file__))#获取pathtest的绝对路径 12 | sys.path.append(base_dir)#将pathtest的绝对路径加入到sys.path中 13 | import logging 14 | from quant.api.singleton import singleton 15 | 16 | @singleton 17 | class Logger(): 18 | # 拙金数据API封装 19 | # quote_ctx = None 20 | def __init__(self): 21 | self.logger = logging.getLogger("mylogger") 22 | # StreamHandler对象自定义日志级别 23 | self.logger_leverl=logging.DEBUG 24 | self.logger.setLevel(self.logger_leverl) 25 | # 日志格式 26 | self.formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 27 | 28 | # info handler类型 ,0:stream,1:file,2:both 29 | self.handler_type=0 30 | 31 | 32 | # 获取流handler 33 | def get_stream_handler(self,level): 34 | stream_handler = logging.StreamHandler() 35 | # StreamHandler对象自定义日志格式 36 | stream_handler.setFormatter(self.formatter) 37 | stream_handler.setLevel(level) 38 | return stream_handler 39 | 40 | # 获取流handler 41 | def get_file_handler(self,type,level): 42 | log_file = "{}/log/{}_{}.txt".format(base_dir,type, datetime.datetime.now().strftime("%Y-%m-%d")) 43 | file_handler = logging.FileHandler(log_file) 44 | file_handler.setLevel(level) 45 | file_handler.setFormatter(self.formatter) 46 | return file_handler 47 | 48 | # 信息日志 49 | def loginfo(self,message): 50 | if self.handler_type==0: 51 | stream_handler=self.get_stream_handler(logging.INFO) 52 | self.logger.addHandler(stream_handler) 53 | elif self.handler_type==1: 54 | file_handler = self.get_file_handler('info',logging.INFO) 55 | self.logger.addHandler(file_handler) 56 | else: 57 | stream_handler = self.get_stream_handler(logging.INFO) 58 | self.logger.addHandler(stream_handler) 59 | 60 | file_handler = self.get_file_handler('info',logging.INFO) 61 | self.logger.addHandler(file_handler) 62 | 63 | self.logger.info(message) 64 | for handler in self.logger.handlers: 65 | self.logger.removeHandler(handler) 66 | 67 | #错误日志 68 | def logerror(self,message): 69 | handler = self.get_file_handler('error',logging.ERROR) 70 | self.logger.addHandler(handler) 71 | self.logger.error(message) 72 | self.logger.removeHandler(handler) 73 | 74 | # 调试日志 75 | def logdebug(self,message): 76 | handler = self.get_stream_handler(logging.DEBUG) 77 | self.logger.addHandler(handler) 78 | self.logger.debug(message) 79 | self.logger.removeHandler(handler) 80 | 81 | if __name__=="__main__": 82 | Logger().loginfo("test done") 83 | -------------------------------------------------------------------------------- /quant/model/.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /quant/model/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /quant/model/.idea/model.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /quant/model/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /quant/model/.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 1650266167878 27 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /quant/model/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/model/__init__.py -------------------------------------------------------------------------------- /quant/model/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/model/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /quant/model/__pycache__/asset.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/model/__pycache__/asset.cpython-38.pyc -------------------------------------------------------------------------------- /quant/model/__pycache__/backtestrecord.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/model/__pycache__/backtestrecord.cpython-38.pyc -------------------------------------------------------------------------------- /quant/model/__pycache__/executionreport.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/model/__pycache__/executionreport.cpython-38.pyc -------------------------------------------------------------------------------- /quant/model/__pycache__/myorder.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/model/__pycache__/myorder.cpython-38.pyc -------------------------------------------------------------------------------- /quant/model/__pycache__/order.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/model/__pycache__/order.cpython-38.pyc -------------------------------------------------------------------------------- /quant/model/__pycache__/position.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/model/__pycache__/position.cpython-38.pyc -------------------------------------------------------------------------------- /quant/model/__pycache__/setting.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/model/__pycache__/setting.cpython-38.pyc -------------------------------------------------------------------------------- /quant/model/__pycache__/stockindicator.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/model/__pycache__/stockindicator.cpython-38.pyc -------------------------------------------------------------------------------- /quant/model/__pycache__/stockpool.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/model/__pycache__/stockpool.cpython-38.pyc -------------------------------------------------------------------------------- /quant/model/__pycache__/traderecord.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/model/__pycache__/traderecord.cpython-38.pyc -------------------------------------------------------------------------------- /quant/model/asset.py: -------------------------------------------------------------------------------- 1 | class Asset(): 2 | def __init__(self, account_id="", account_type=0, cash=-1, frozen_cash=-1, market_value=-1, total_asset=-1): 3 | self.account_id = account_id 4 | self.account_name = "" 5 | self.account_type = account_type 6 | self.cash = cash 7 | self.frozen_cash = frozen_cash 8 | self.market_value = market_value 9 | self.total_asset = total_asset 10 | self.initial_capital = -1 11 | self.fpnl=0 -------------------------------------------------------------------------------- /quant/model/backtestrecord.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ############################################################################### 3 | # @author : vamed 4 | # @time :2021-02-02 5 | # @function:回归记录 6 | # Copyright (C) 2021-2023 7 | ############################################################################### 8 | # 9 | 10 | from sqlalchemy import Column, Integer, String, create_engine,DateTime,Float,DECIMAL 11 | from sqlalchemy.ext.declarative import declarative_base 12 | from sqlalchemy.orm import sessionmaker 13 | 14 | from quant.util.configutil import get_config 15 | 16 | Base = declarative_base() 17 | # 定义映射类User,其继承上一步创建的Base 18 | class BacktestRecord(Base): 19 | # 指定本类映射到users表 20 | __tablename__ = 'backtest_record' 21 | __connectString__=get_config('DATABASE','tradedb') 22 | id = Column(Integer, primary_key=True, autoincrement=True) 23 | # 指定name映射到name字段; name字段为字符串类形, 24 | account_id = Column(String(50)) 25 | start = Column(String(20)) 26 | symbols = Column(String(200)) 27 | current= Column(String(20)) 28 | end = Column(String(20)) 29 | status = Column(String(20)) 30 | date = Column(String(20)) 31 | 32 | def insert(self): 33 | # 初始化数据库连接: 34 | engine = create_engine(self.__connectString__) 35 | # 创建DBSession类型: 36 | DBSession = sessionmaker(bind=engine) 37 | 38 | # 创建session对象: 39 | session = DBSession() 40 | session.add(self) 41 | # 提交即保存到数据库: 42 | session.commit() 43 | # 关闭session: 44 | session.close() 45 | 46 | def get_backtest_record(self,account_id): 47 | engine = create_engine(self.__connectString__) 48 | DBSession = sessionmaker(bind=engine, expire_on_commit=False) 49 | session = DBSession() 50 | ret = session.query(BacktestRecord).filter(BacktestRecord.account_id == account_id).one() 51 | # session.commit() 52 | engine.dispose() 53 | return ret 54 | 55 | # 获取未完成的回测 56 | def get_suspend_record(self): 57 | engine = create_engine(self.__connectString__) 58 | DBSession = sessionmaker(bind=engine) 59 | session = DBSession() 60 | ret = session.query(BacktestRecord).filter(BacktestRecord.status != 'finished').order_by(BacktestRecord.date.desc()).first() 61 | # session.commit() 62 | engine.dispose() 63 | return ret 64 | # 更新股票评级 65 | def update_backtest_record(self,account_id,date,status): 66 | engine = create_engine(self.__connectString__) 67 | DBSession = sessionmaker(bind=engine) 68 | session = DBSession() 69 | js={"current": date,"status": status} 70 | session.query(BacktestRecord).filter(BacktestRecord.account_id == account_id).update(js) 71 | session.commit() 72 | engine.dispose() 73 | 74 | if __name__ == '__main__': 75 | # b= BacktestRecord() 76 | # b.status="suspend" 77 | # b.account_id="dsssss" 78 | # b.date="2022-10-10" 79 | # b.start = "2021-10-10" 80 | # b.current = "2021-12-10" 81 | # b.end = "2022-10-10" 82 | # b.insert() 83 | b=BacktestRecord().get_suspend_record() 84 | # b=BacktestRecord().update_backtest_record(account_id='dsssss',date='2021-12-11',status='finished') 85 | if b==None: 86 | print(None) 87 | else: 88 | print(b.account_id) 89 | pass 90 | -------------------------------------------------------------------------------- /quant/model/executionreport.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ############################################################################### 3 | # @author : vamed 4 | # @time :2021-02-02 5 | # @function:交易事件回报 6 | # Copyright (C) 2021-2023 7 | ############################################################################### 8 | # 9 | from datetime import datetime 10 | from sqlalchemy import Column, Integer, String, create_engine,DateTime,Float,DECIMAL 11 | from sqlalchemy.ext.declarative import declarative_base 12 | from sqlalchemy.orm import sessionmaker 13 | import pandas as pd 14 | from quant.util.configutil import get_config 15 | 16 | Base = declarative_base() 17 | # 定义映射类User,其继承上一步创建的Base 18 | class ExecutionReport(Base): 19 | # 指定本类映射到users表 20 | __tablename__ = 'execution_report' 21 | __connectString__=get_config('DATABASE','tradedb') 22 | id = Column(Integer, primary_key=True, autoincrement=True) 23 | # 指定name映射到name字段; name字段为字符串类形, 24 | account_id = Column(String(50)) 25 | cl_ord_id = Column(String(50)) 26 | symbol = Column(String(20)) 27 | side = Column(Integer) 28 | mode = Column(Integer) 29 | price = Column(Float(10)) 30 | volume = Column(Integer) 31 | amount = Column(Float(20)) 32 | strategy=Column(String(50)) 33 | trade_date = Column(String(50)) 34 | trade_time = Column(DateTime,default=datetime.now()) 35 | record_time= Column(DateTime,default=datetime.now()) 36 | 37 | # __repr__方法用于输出该类的对象被print()时输出的字符串,如果不想写可以不写 38 | def __repr__(self): 39 | return "" % ( 40 | self.account_id,self.symbol,self.name,self.side,self.price,self.volume,self.amount,self.strategy,self.trade_date,self.trade_time) 41 | 42 | def insert(self): 43 | # 初始化数据库连接: 44 | engine = create_engine(self.__connectString__) 45 | # 创建DBSession类型: 46 | DBSession = sessionmaker(bind=engine) 47 | 48 | # 创建session对象: 49 | session = DBSession() 50 | # 创建新User对象: 51 | # 添加到session: 52 | session.add(self) 53 | # 提交即保存到数据库: 54 | session.commit() 55 | # 关闭session: 56 | session.close() 57 | 58 | def getRecord(self,start=None,end=None,acount='d28f4b30-4bf9-11ec-ad1b-00163e0a4100'): 59 | engine = create_engine(self.__connectString__) 60 | where='' 61 | if start!=None: 62 | where="trade_date>='{}'".format(start) 63 | if end!=None: 64 | if where!='': 65 | where=where+' and ' 66 | where=where+"trade_date<='{}'".format(end) 67 | if acount!="": 68 | if where != '': 69 | where = where + ' and ' 70 | where = where + " account_id='{}'".format(acount) 71 | if where!='': 72 | where=" where {}".format(where) 73 | df=pd.read_sql("select * from {} {}".format(self.__tablename__,where),con=engine) 74 | engine.dispose() 75 | return df 76 | # 获取单个股票交易记录 77 | def getStockRecord(self,symbol,start=None,end=None): 78 | engine = create_engine(self.__connectString__) 79 | where=" symbol='{}'".format(symbol) 80 | if start!=None: 81 | where=where+" and trade_time>='{}'".format(start) 82 | if end!=None: 83 | where=where+" and trade_time<='{}'".format(end) 84 | if where!='': 85 | where=' where {}'.format(where) 86 | df=pd.read_sql("select * from {} {}".format(self.__tablename__,where),con=engine) 87 | engine.dispose() 88 | return df 89 | # 获取单个股票交易记录 90 | def hasExistTradeRecord(self,symbol,trade_time,amount): 91 | ret=False 92 | engine = create_engine(self.__connectString__) 93 | where="where symbol='{}' and trade_time='{}' and amount={} ".format(symbol,trade_time,amount) 94 | df=pd.read_sql("select * from trade_record {}".format(where),con=engine) 95 | engine.dispose() 96 | if len(df)>0: 97 | ret=True 98 | return ret 99 | # 获取单个股票交易记录 100 | def getAccount(self): 101 | engine = create_engine(self.__connectString__) 102 | # where=" " 103 | # if start!=None: 104 | # where=where+" trade_time>='{}'".format(start) 105 | # if end!=None: 106 | # where=where+" and trade_time<='{}'".format(end) 107 | # if where!='': 108 | # where=' where {}'.format(where) 109 | # df=pd.read_sql("SELECT account_id,COUNT(*) AS cnt FROM trade_record GROUP BY account_id {}".format(where),con=engine) 110 | df = pd.read_sql("SELECT account_id,COUNT(*) AS cnt FROM trade_record GROUP BY account_id ", 111 | con=engine) 112 | engine.dispose() 113 | return df 114 | 115 | # 获取最多交易记录帐号 116 | def getAccountId(self): 117 | ret='' 118 | df=self.getAccount() 119 | if len(df)>0: 120 | df.sort_values(by=['cnt'],ascending=False,inplace=True) 121 | ret=df.at[0,'account_id'] 122 | return ret 123 | # 获取单个股票交易记录 124 | def getTradeRecord(self,symbol,acount,start=None,end=None): 125 | engine = create_engine(self.__connectString__) 126 | where=" symbol='{}' and account_id='{}'".format(symbol,acount) 127 | if start!=None: 128 | where=where+" and trade_time>='{}'".format(start) 129 | if end!=None: 130 | where=where+" and trade_time<='{}'".format(end) 131 | if where!='': 132 | where=' where {}'.format(where) 133 | df=pd.read_sql("select * from trade_record {}".format(where),con=engine) 134 | engine.dispose() 135 | df_buy=df.loc[df['side']==1,:] 136 | df_buy=df_buy.groupby(['trade_date']).sum(['amount', 'volume']) 137 | df_buy['buy']=round((df_buy['amount']/df_buy['volume']),2) 138 | 139 | df_sell = df.loc[df['side'] == -1, :] 140 | df_sell = df_sell.groupby(['trade_date']).sum(['amount', 'volume']) 141 | df_sell['sell'] = round((df_sell['amount'] / df_sell['volume']), 2) 142 | df_trade = df.groupby(['trade_date']).sum(['amount', 'volume']) 143 | df_trade['buy']=df_buy['buy'] 144 | df_trade['sell'] = df_sell['sell'] 145 | df_trade.index=pd.to_datetime(df_trade.index) 146 | # print(df_trade) 147 | return df_trade 148 | 149 | # def batch_insert(self,df): 150 | # for i,execrpt in df.iterrows(): 151 | # if execrpt.exec_type == 15: 152 | # trade_record = ExecutionReport() 153 | # trade_record.account_id = execrpt.account_id 154 | # trade_record.cl_ord_id = execrpt.cl_ord_id 155 | # trade_record.symbol = execrpt.symbol 156 | # if execrpt.side == 1: 157 | # trade_record.side = 1 158 | # elif execrpt.side == 2: 159 | # trade_record.side = -1 160 | # trade_record.price = execrpt.price 161 | # trade_record.volume = execrpt.volume 162 | # trade_record.amount =Decimal(execrpt.amount).quantize(Decimal('0.00')) 163 | # trade_record.trade_date = execrpt.created_at.strftime('%Y-%m-%d') 164 | # trade_record.record_time =datetime.now() 165 | # trade_record.trade_time=pd.to_datetime(execrpt.created_at).strftime('%Y-%m-%d %H:%M:%S') 166 | # if ExecutionReport().hasExistTradeRecord(trade_record.symbol, trade_record.trade_time, 167 | # trade_record.amount) == False: 168 | # try: 169 | # trade_record.insert() 170 | # except Exception as e: 171 | # logerror('交易记录保存错误:{}'.format(e)) 172 | if __name__ == '__main__': 173 | 174 | # StockPool().insert() 175 | # record=TradeRecord() 176 | # record.symbol='SHSE.600030' 177 | # record.side=1 178 | # record.price=11 179 | # record.volume=1000 180 | # record.amount=10000 181 | # record.trade_time=datetime.now() 182 | # record.record_time = datetime.now() 183 | # record.insert() 184 | print(ExecutionReport().getAccountId()) 185 | df= ExecutionReport().hasExistTradeRecord('SHSE.600743','2022-05-17 13:52:07.693056',33120) 186 | # df=TradeRecord().getAccount() 187 | print(df) 188 | # loginfo(TradeRecord().getRecord()) 189 | pass 190 | -------------------------------------------------------------------------------- /quant/model/log.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ############################################################################### 3 | # @author : vamed 4 | # @time :2021-02-02 5 | # @function:日志类 6 | # Copyright (C) 2021-2023 7 | ############################################################################### 8 | # 9 | import datetime 10 | from sqlalchemy import Column, Integer, String, create_engine,DateTime,Float,DECIMAL 11 | from sqlalchemy.ext.declarative import declarative_base 12 | from sqlalchemy.orm import sessionmaker 13 | from quant.util.configutil import get_config 14 | 15 | Base = declarative_base() 16 | # 定义映射类User,其继承上一步创建的Base 17 | class Log(Base): 18 | # 指定本类映射到users表 19 | __tablename__ = 'log' 20 | __connectString__=get_config('DATABASE','Tradedb') 21 | id = Column(Integer, primary_key=True, autoincrement=True) 22 | # 指定name映射到name字段; name字段为字符串类形, 23 | date = Column(String(50)) 24 | record_time =Column(DateTime) 25 | result = Column(Integer) 26 | type = Column(Integer) 27 | memo = Column(String(500)) 28 | 29 | # 获取股票dataframe 30 | def getdata(self,date,type): 31 | # 初始化数据库连接: 32 | engine = create_engine(self.__connectString__) 33 | DBSession = sessionmaker(bind=engine) 34 | session = DBSession() 35 | datacrawlLog = session.query(Log).filter(Log.date == date).filter(Log.type == type).filter(Log.result == 0).first() 36 | session.close() 37 | return datacrawlLog 38 | # 新增数据 39 | def insert(self): 40 | # 初始化数据库连接: 41 | engine = create_engine(self.__connectString__) 42 | # 创建DBSession类型: 43 | DBSession = sessionmaker(bind=engine) 44 | # 创建session对象: 45 | session = DBSession() 46 | # 添加到session: 47 | session.add(self) 48 | # 提交即保存到数据库: 49 | session.commit() 50 | # 关闭session: 51 | session.close() 52 | 53 | #删除数据 54 | def delete(self,date,type): 55 | engine = create_engine(self.__connectString__) 56 | DBSession = sessionmaker(bind=engine) 57 | # 创建session对象 58 | session = DBSession() 59 | session.query(Log).filter(Log.date==date).filter(Log.type==type).delete(synchronize_session=False) 60 | session.commit() 61 | engine.dispose() 62 | 63 | # log操作 64 | def log_operation(self,type,result,memo): 65 | dl = Log() 66 | dl.record_time = datetime.datetime.now() 67 | dl.type=type 68 | dl.result = result 69 | dl.date=datetime.datetime.now().strftime("%Y-%m-%d") 70 | dl.memo = memo 71 | dl.insert() 72 | 73 | # 每天数据更新结果检查 74 | def check_log(self,date,type): 75 | engine = create_engine(self.__connectString__) 76 | DBSession = sessionmaker(bind=engine) 77 | 78 | dl = Log() 79 | dl.record_time = datetime.datetime.now() 80 | dl.type=1 81 | # if len(result)==0: 82 | # dl.result = 0 83 | # dl.memo = '成功更新所有数据' 84 | # else: 85 | # dl.result = 1 86 | # str2 = ',' 87 | # memo = str2.join(result) 88 | # dl.memo = memo 89 | dl.date=date 90 | Log().delete(date, 1) 91 | dl.insert() 92 | 93 | if __name__ == '__main__': 94 | Log().delete('2022-05-06',0) 95 | d= Log().getdata('2022-05-06',0) 96 | if d!=None: 97 | print(d.date) 98 | -------------------------------------------------------------------------------- /quant/model/myorder.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ############################################################################### 3 | # @author : vamed 4 | # @time :2021-02-02 5 | # @function:掘金订单类 6 | # Copyright (C) 2021-2023 7 | ############################################################################### 8 | # 9 | from datetime import datetime 10 | from sqlalchemy import Column, Integer, String, create_engine,DateTime,Float,DECIMAL 11 | from sqlalchemy.ext.declarative import declarative_base 12 | from sqlalchemy.orm import sessionmaker 13 | import pandas as pd 14 | 15 | from quant.util import stringutil 16 | from quant.util.configutil import get_config 17 | 18 | pd.set_option('display.max_rows', 5000) 19 | pd.set_option('display.max_columns', 5000) 20 | pd.set_option('display.width', 1000) 21 | 22 | Base = declarative_base() 23 | # 定义映射类User,其继承上一步创建的Base 24 | class MyOrder(Base): 25 | # 指定本类映射到users表 26 | __tablename__ = 'myorder' 27 | __connectString__=get_config('DATABASE','tradedb') 28 | id = Column(Integer, primary_key=True, autoincrement=True) 29 | # 指定name映射到name字段; name字段为字符串类形, 30 | account_id = Column(String(50)) 31 | symbol = Column(String(20)) 32 | side = Column(Integer) 33 | price = Column(Float(10)) 34 | volume = Column(Integer) 35 | amount = Column(Float(20)) 36 | strategy=Column(String(50)) 37 | trade_date = Column(String(50)) 38 | trade_time = Column(DateTime,default=datetime.now()) 39 | record_time= Column(DateTime,default=datetime.now()) 40 | status = Column(Integer) 41 | mode = Column(Integer) 42 | cl_ord_id = Column(String(50)) 43 | order_id = Column(String(50)) 44 | ex_ord_id = Column(String(50)) 45 | created_at = Column(DateTime, default=datetime.now()) 46 | updated_at = Column(DateTime, default=datetime.now()) 47 | 48 | def tojson(self): 49 | js = {'account_id': self.account_id, 'symbol': self.symbol, 50 | 'side': self.side,'price': self.price,'volume': self.volume,'amount': self.amount,'strategy': self.strategy,'trade_date': self.trade_date 51 | ,'trade_time': self.trade_time,'record_time': self.record_time,'status': self.status,'cl_ord_id': self.cl_ord_id,'order_id': self.order_id 52 | ,'ex_ord_id': self.ex_ord_id,'created_at': self.created_at,'updated_at': self.updated_at} 53 | return js 54 | 55 | # 获取股票dataframe 56 | def getdata(self,order_id): 57 | # 初始化数据库连接: 58 | engine = create_engine(self.__connectString__) 59 | DBSession = sessionmaker(bind=engine) 60 | session = DBSession() 61 | order = session.query(MyOrder).filter(MyOrder.order_id == order_id).first() 62 | return order 63 | 64 | # 新增数据 65 | def insert(self): 66 | # 初始化数据库连接: 67 | engine = create_engine(self.__connectString__) 68 | # 创建DBSession类型: 69 | DBSession = sessionmaker(bind=engine) 70 | # 创建session对象: 71 | session = DBSession() 72 | # 添加到session: 73 | session.add(self) 74 | # 提交即保存到数据库: 75 | session.commit() 76 | # 关闭session: 77 | session.close() 78 | 79 | # 修改更新数据 80 | def update(self): 81 | json=self.tojson() 82 | print(json) 83 | engine = create_engine(self.__connectString__) 84 | DBSession = sessionmaker(bind=engine) 85 | session = DBSession() 86 | session.query(MyOrder).filter(MyOrder.order_id == self.order_id).update(json) 87 | session.commit() 88 | engine.dispose() 89 | 90 | # 保存 91 | def save(self): 92 | order=self.getdata(self.order_id) 93 | if order==None: 94 | self.insert() 95 | else: 96 | self.update() 97 | 98 | # 获取清仓股票 99 | def get_sellout_profit(self,account_id='59ae65ad-c062-11ec-bde8-00163e0a4100',start='2022-09-27',end='2022-09-29'): 100 | engine = create_engine(self.__connectString__) 101 | df_sellout = pd.read_sql("SELECT symbol,cast(SUM(side*volume) AS signed) as volume FROM `myorder` WHERE STATUS=3 AND account_id='{}' AND trade_date>='{}' AND trade_date<='{}' GROUP BY symbol HAVING volume=0 order BY volume;".format(account_id,start,end), con=engine) 102 | 103 | df=pd.read_sql("SELECT * FROM `myorder` WHERE STATUS=3 AND account_id='{}' AND trade_date>='{}' AND trade_date<='{}'".format(account_id,start,end), con=engine) 104 | df_stock= df.loc[df['symbol'].isin(df_sellout['symbol'].values), :].copy() 105 | # engine.dispose() 106 | df_stock=df_stock.groupby(by=['symbol']).apply(lambda x:-(x['side']*x['volume']*x['price']).sum()) 107 | # print(df_stock) 108 | 109 | df_profit = pd.DataFrame(data=df_stock) 110 | df_profit.reset_index(inplace=True) 111 | df_profit.columns=['symbol','profit'] 112 | # print(df_gb.columns) 113 | # return df_profit['profit'].sum() 114 | return df_profit 115 | 116 | def get_day_sellout_stock(self,account_id='59ae65ad-c062-11ec-bde8-00163e0a4100',date=datetime.now().strftime('%Y-%m-%d')): 117 | engine = create_engine(self.__connectString__) 118 | df_sellout = pd.read_sql("SELECT symbol,cast(SUM(side*volume) AS signed) as volume FROM `myorder` WHERE STATUS=3 AND account_id='{}' AND symbol IN (SELECT symbol FROM myorder WHERE STATUS=3 AND side=-1 AND account_id='{}' AND trade_date='{}') GROUP BY symbol HAVING volume=0 order BY volume;".format(account_id,account_id,date), con=engine) 119 | return df_sellout 120 | # print(df_sellout) 121 | 122 | def get_day_sellout_profit(self,account_id='59ae65ad-c062-11ec-bde8-00163e0a4100',date=datetime.now().strftime('%Y-%m-%d')): 123 | df_sellout = self.get_day_sellout_stock(account_id=account_id, date=date) 124 | engine = create_engine(self.__connectString__) 125 | df = pd.read_sql("SELECT * FROM `myorder` WHERE STATUS=3 AND account_id='{}' AND trade_date<='{}'".format(account_id, date), con=engine) 126 | symbols=stringutil.array_to_string(df_sellout['symbol'].values) 127 | symbols="'{}'".format(symbols.replace(',',"','")) 128 | df_sell=pd.read_sql("SELECT symbol,cast(SUM(side*volume) AS signed) as volume FROM `myorder` WHERE STATUS=3 AND side=-1 AND account_id='{}' AND symbol IN ({}) AND trade_date='{}' GROUP BY symbol order BY volume;".format(account_id,symbols, date), con=engine) 129 | engine.dispose() 130 | 131 | df_out = df.loc[df.symbol.isin(df_sellout.symbol) & (df['side'] == 1), :].copy() 132 | df_ret = pd.DataFrame(columns=df_out.columns) 133 | 134 | df_ret=pd.concat([df_ret,df.loc[(df['trade_date']== date) & (df['side'] == -1) & (df['symbol'].isin(df_sell['symbol'])), :].copy()],axis=0,ignore_index=True) 135 | 136 | df_sell.set_index('symbol', inplace=True) 137 | 138 | for i,row in df_sellout.iterrows(): 139 | df_symbol=df_out.loc[(df_out['symbol']==row['symbol']) ,:].copy() 140 | df_symbol.sort_values(by='updated_at', ascending=False,inplace=True) 141 | df_symbol['volume_cumsum']=df_symbol['volume'].cumsum() 142 | 143 | df_ret=pd.concat([df_ret,df_symbol.loc[df_symbol['volume_cumsum']<=-df_sell.at[row['symbol'],'volume'],:].copy()],axis=0,ignore_index=True) 144 | 145 | return df_ret 146 | 147 | # 获取单个股票交易记录 148 | def getAccount(self): 149 | engine = create_engine(self.__connectString__) 150 | # where=" " 151 | # if start!=None: 152 | # where=where+" trade_time>='{}'".format(start) 153 | # if end!=None: 154 | # where=where+" and trade_time<='{}'".format(end) 155 | # if where!='': 156 | # where=' where {}'.format(where) 157 | # df=pd.read_sql("SELECT account_id,COUNT(*) AS cnt FROM trade_record GROUP BY account_id {}".format(where),con=engine) 158 | df = pd.read_sql("SELECT account_id,COUNT(*) AS cnt FROM {} GROUP BY account_id ".format(self.__tablename__), 159 | con=engine) 160 | engine.dispose() 161 | return df 162 | 163 | if __name__ == '__main__': 164 | 165 | # StockPool().insert() 166 | # record=TradeRecord() 167 | # record.symbol='SHSE.600030' 168 | # record.side=1 169 | # record.price=11 170 | # record.volume=1000 171 | # record.amount=10000 172 | # record.trade_time=datetime.now() 173 | # record.record_time = datetime.now() 174 | # record.insert() 175 | # print(MyOrder().getAccountId()) 176 | # df= MyOrder().get_day_sellout_stock(date='2022-09-29') 177 | df = MyOrder().get_day_sellout_profit(date='2022-09-29') 178 | # df=TradeRecord().getAccount() 179 | print(df) 180 | # loginfo(TradeRecord().getRecord()) 181 | pass 182 | -------------------------------------------------------------------------------- /quant/model/position.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ############################################################################### 3 | # @author : vamed 4 | # @time :2021-02-02 5 | # @function:持仓类 6 | # Copyright (C) 2021-2023 7 | ############################################################################### 8 | # 9 | 10 | from datetime import datetime 11 | from sqlalchemy import Column, Integer, String, create_engine, DateTime, Float, DECIMAL, text 12 | from sqlalchemy.ext.declarative import declarative_base 13 | from sqlalchemy.orm import sessionmaker 14 | import pandas as pd 15 | import datacompy 16 | from quant.util.configutil import get_config 17 | Base = declarative_base() 18 | 19 | pd.set_option('display.max_rows', 5000) 20 | pd.set_option('display.max_columns', 5000) 21 | pd.set_option('display.width', 1000) 22 | # 定义映射类User,其继承上一步创建的Base 23 | class Position(Base): 24 | # 指定本类映射到users表 25 | __tablename__ = 'position' 26 | __connectString__=get_config('DATABASE','tradedb') 27 | id = Column(Integer, primary_key=True, autoincrement=True) 28 | account_id= Column(String(50)) 29 | # 指定name映射到name字段; name字段为字符串类形, 30 | code = Column(String(20)) 31 | symbol= Column(String(20)) 32 | name = Column(String(20)) 33 | #平均建仓成本 34 | open_price=Column(Float(10)) 35 | # 持仓总数量 36 | volume=Column(Integer) 37 | amount=Column(Float(10)) 38 | #可用数量 39 | can_use_volume=Column(Integer) 40 | # 冻结量 41 | frozen_volume = Column(Integer) 42 | # 昨量 43 | yesterday_volume= Column(Integer) 44 | # 当天量 45 | on_road_volume= Column(Integer) 46 | # 现价 47 | price = Column(Float(10)) 48 | # 持仓市值(现值) 49 | market_value= Column(Float(10)) 50 | 51 | # 持仓盈利 52 | profit_amount = Column(Float(20)) 53 | profit_rate = Column(Float(10)) 54 | 55 | # ------------------------------------ 56 | inflow = Column(Float(20)) 57 | factor = Column(String(30)) 58 | rating = Column(String(30)) 59 | strategy = Column(String(20)) 60 | 61 | # pe = Column(Float(20)) 62 | # holderchange = Column(Float(20)) 63 | # # 公司利润 64 | # profit = Column(Float(20)) 65 | # industry = Column(String(30)) 66 | # industry_name = Column(String(30)) 67 | date = Column(String(20)) 68 | # seldate = Column(DateTime, default=datetime.now()) 69 | update_time= Column(DateTime, default=datetime.now()) 70 | created_at= Column(DateTime, default=datetime.now()) 71 | def insert(self): 72 | # 初始化数据库连接: 73 | engine = create_engine(self.__connectString__) 74 | # 创建DBSession类型: 75 | DBSession = sessionmaker(bind=engine) 76 | # 创建session对象: 77 | session = DBSession() 78 | # 添加到session: 79 | session.add(self) 80 | # 提交即保存到数据库: 81 | session.commit() 82 | # 关闭session: 83 | session.close() 84 | 85 | # 批量插入股票池股票 86 | def batch_insert(self,df): 87 | engine = create_engine(self.__connectString__) 88 | df.to_sql(Position.__tablename__, engine, index=False, if_exists='append') 89 | engine.dispose() 90 | 91 | # 获取股票池股票 92 | def get_all_stock(self): 93 | engine = create_engine(self.__connectString__) 94 | df=pd.read_sql('select * from {}'.format(self.__tablename__),con=engine) 95 | engine.dispose() 96 | return df 97 | 98 | # 获取股票池股票 99 | def get_stock_by_code(self,code): 100 | engine = create_engine(self.__connectString__) 101 | df=pd.read_sql("SELECT * FROM {} WHERE CODE='{}' ".format(self.__tablename__,code),con=engine) 102 | engine.dispose() 103 | return df 104 | 105 | # 获取股票池股票 106 | def get_stock(self,start=None,end=None,factor=None): 107 | engine = create_engine(self.__connectString__) 108 | where='' 109 | if factor!=None: 110 | where = " factor='{}'".format(factor) 111 | if start != None: 112 | if where !='': 113 | where=where+" and " 114 | where = where + " seldate>='{}'".format(start) 115 | if end != None: 116 | if where != '': 117 | where = where + " and " 118 | where = where + " seldate<='{}'".format(end) 119 | if where != '': 120 | where = ' where {}'.format(where) 121 | df = pd.read_sql("select * from {} {}".format(self.__tablename__,where), con=engine) 122 | engine.dispose() 123 | return df 124 | 125 | # 删除代码列数据 126 | def delete(self,symbols=[]): 127 | engine = create_engine(self.__connectString__) 128 | DBSession = sessionmaker(bind=engine) 129 | # 创建session对象 130 | session = DBSession() 131 | session.query(Position).filter(Position.code.in_(symbols)).delete(synchronize_session=False) 132 | session.commit() 133 | engine.dispose() 134 | 135 | # 删除所有数据 136 | def delete_all(self,account_id): 137 | engine = create_engine(self.__connectString__) 138 | DBSession = sessionmaker(bind=engine) 139 | # 创建session对象 140 | session = DBSession() 141 | # session.query(Position).filter(text('1=1')).delete(synchronize_session=False) 142 | session.query(Position).filter(Position.account_id==account_id).delete(synchronize_session=False) 143 | session.commit() 144 | engine.dispose() 145 | 146 | # 查询并删除某策略的数据 147 | def delete_strategy(self,strategy): 148 | engine = create_engine(self.__connectString__) 149 | DBSession = sessionmaker(bind=engine) 150 | # 创建session对象 151 | session = DBSession() 152 | session.query(Position).filter(Position.strategy==strategy).delete(synchronize_session=False) 153 | session.commit() 154 | engine.dispose() 155 | 156 | # 更新股票评级 157 | def update(self,code,js): 158 | engine = create_engine(self.__connectString__) 159 | DBSession = sessionmaker(bind=engine) 160 | session = DBSession() 161 | session.query(Position).filter(Position.code == code).update(js) 162 | session.commit() 163 | engine.dispose() 164 | 165 | def get_position(self): 166 | engine = create_engine(self.__connectString__) 167 | DBSession = sessionmaker(bind=engine) 168 | session = DBSession() 169 | ret = session.query(Position).all() 170 | session.commit() 171 | engine.dispose() 172 | return ret 173 | 174 | #对象转json 175 | # @classmethod 176 | def to_json(self): 177 | js = {'account_id': self.account_id, 'code': self.code ,'symbol': self.symbol ,'profit_amount': self.profit_amount,'profit_rate': self.profit_rate , 178 | 'name': self.name ,'price': self.price ,'open_price': self.open_price,'volume': self.volume,'amount': self.amount,'can_use_volume': self.can_use_volume 179 | ,'frozen_volume': self.frozen_volume ,'on_road_volume': self.on_road_volume,'market_value': self.market_value,'update_time': self.update_time,'created_at': self.created_at } 180 | return js 181 | 182 | # 对象数组转dataframe 183 | @classmethod 184 | def to_df(self,pos): 185 | df=pd.DataFrame() 186 | if len(pos)>0: 187 | df=pd.DataFrame(pos[0].to_json(),index=[0]) 188 | for i in range(len(pos)): 189 | if i>0: 190 | # df=df.append(pd.DataFrame(pos[i].to_json(),index=[i])) 191 | df=pd.concat([df,pd.DataFrame(pos[i].to_json(),index=[i])],axis=0,ignore_index=True) 192 | # df['symbol']=df['code'].apply(lambda x:stockutil.xtToGmSymbol(x)) 193 | return df 194 | 195 | # 获取持仓策略及评级 196 | def get_symbols_strategy(self,account_id,symbols=[]): 197 | engine = create_engine(self.__connectString__) 198 | DBSession = sessionmaker(bind=engine) 199 | # 创建session对象 200 | session = DBSession() 201 | query = session.query(Position).filter(Position.account_id==account_id).filter(Position.symbol.in_(symbols)) 202 | df = pd.read_sql(query.statement, engine) 203 | session.commit() 204 | engine.dispose() 205 | df.set_index('symbol',inplace=True) 206 | df['stock_type']='holding' 207 | df=df.loc[:,['strategy','rating','stock_type','open_price']] 208 | return df 209 | 210 | # 获取本地数据持仓数据 211 | def get_local_position(self,account_id='59ae65ad-c062-11ec-bde8-00163e0a4100'): 212 | engine = create_engine(self.__connectString__) 213 | df_pos = pd.read_sql("SELECT * FROM `position` WHERE account_id='{}'".format(account_id), con=engine) 214 | engine.dispose() 215 | return df_pos 216 | 217 | # 检查持仓是否和交易记录一致 218 | def check_position_data(self,account_id='59ae65ad-c062-11ec-bde8-00163e0a4100'): 219 | engine = create_engine(self.__connectString__) 220 | df_ord = pd.read_sql("SELECT symbol,cast(SUM(side*volume) AS signed) AS volume FROM `myorder` WHERE STATUS=3 AND account_id='{}' GROUP BY symbol HAVING volume>0 order BY volume".format(account_id), con=engine) 221 | df_pos = pd.read_sql("SELECT * FROM `position` WHERE account_id='{}'".format(account_id), con=engine) 222 | engine.dispose() 223 | 224 | # df_ord = df_ord.loc[:, ['symbol', 'volume']] 225 | print(df_ord) 226 | 227 | df_pos=df_pos.loc[:,['symbol','volume']] 228 | df_pos.sort_values(by='volume',ascending=True,inplace=True) 229 | print(df_pos) 230 | compare = datacompy.Compare(df_ord, df_pos, join_columns='volume') 231 | 232 | return compare.matches() 233 | # print(compare.matches()) # 最后判断是否相等,返回 bool 234 | # print(compare.report()) # 打印报告详情,返回 string 235 | 236 | # 获取缓存持仓信息 237 | def get_cache_position(self, positions, symbol): 238 | ret = None 239 | for pos in positions: 240 | if pos.symbol == symbol: 241 | ret = pos 242 | break 243 | return ret 244 | 245 | 246 | 247 | if __name__ == '__main__': 248 | b= Position().get_symbols_strategy('62bbe5fb-3f95-11ed-976c-00163e18a8b3',['SZSE.002265','SZSE.002850','SHSE.601633']) 249 | print(b) 250 | # Position().delete(['002781', '002437', '002679', '300782', '000987', '600661', '000333', '600690', '601231', '600410', '603808', '399001', '000001']) 251 | # loginfo(StockPool().delete()) 252 | pass 253 | -------------------------------------------------------------------------------- /quant/model/setting.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ############################################################################### 3 | # @author : vamed 4 | # @time :2021-02-02 5 | # @function:帐号交易参数设置类 6 | # Copyright (C) 2021-2023 7 | ############################################################################### 8 | # 9 | import pandas as pd 10 | from gm.enum import MODE_LIVE, MODE_BACKTEST 11 | from sqlalchemy import Column, Integer, String, create_engine,DateTime,Float,DECIMAL 12 | from sqlalchemy.ext.declarative import declarative_base 13 | from sqlalchemy.orm import sessionmaker 14 | 15 | from quant.util.configutil import get_config 16 | 17 | Base = declarative_base() 18 | # 定义映射类User,其继承上一步创建的Base 19 | class Setting(Base): 20 | # 指定本类映射到users表 21 | __tablename__ = 'setting' 22 | __connectString__=get_config('DATABASE','Tradedb') 23 | id = Column(Integer, primary_key=True, autoincrement=True) 24 | # 指定name映射到name字段; name字段为字符串类形, 25 | account_id = Column(String(50)) 26 | max_position =Column(Float(5)) 27 | max_single_position = Column(Float(5)) 28 | max_stock_number=Column(Integer) 29 | per_buy_amount = Column(Float(10)) 30 | memo = Column(String(200)) 31 | name = Column(String(50)) 32 | broker = Column(String(50)) 33 | initial_capital=Column(Float(10)) 34 | account_type=Column(Integer) 35 | 36 | 37 | def tojson(self): 38 | js = {'account_id': self.account_id, 'max_position': self.max_position, 39 | 'max_single_position': self.max_single_position,'per_buy_amount': self.per_buy_amount,'memo': self.memo,'name': self.name,'broker': self.broker,'account_type': self.account_type} 40 | return js 41 | 42 | # 获取所有帐号设置 43 | def get_all_data(self): 44 | engine = create_engine(self.__connectString__) 45 | df=pd.read_sql("select * from setting",con=engine) 46 | engine.dispose() 47 | return df 48 | 49 | # 获取股票dataframe 50 | def getdata(self,account_id): 51 | # 初始化数据库连接: 52 | engine = create_engine(self.__connectString__) 53 | DBSession = sessionmaker(bind=engine) 54 | session = DBSession() 55 | setting = session.query(Setting).filter(Setting.account_id == account_id).first() 56 | return setting 57 | # 新增数据 58 | def insert(self): 59 | # 初始化数据库连接: 60 | engine = create_engine(self.__connectString__) 61 | # 创建DBSession类型: 62 | DBSession = sessionmaker(bind=engine) 63 | # 创建session对象: 64 | session = DBSession() 65 | # 添加到session: 66 | session.add(self) 67 | # 提交即保存到数据库: 68 | session.commit() 69 | # 关闭session: 70 | session.close() 71 | 72 | def update(self): 73 | json=self.tojson() 74 | engine = create_engine(self.__connectString__) 75 | DBSession = sessionmaker(bind=engine) 76 | session = DBSession() 77 | session.query(Setting).filter(Setting.account_id == self.account_id).update(json) 78 | session.commit() 79 | engine.dispose() 80 | 81 | def save(self): 82 | setting=self.getdata(self.account_id) 83 | if setting==None: 84 | self.insert() 85 | else: 86 | self.update() 87 | 88 | # 止损 89 | @classmethod 90 | def get_setting_accountid(self,context): 91 | ret = "" 92 | if context.mode==MODE_BACKTEST: 93 | ret="back_test" 94 | else: 95 | ret=context.account().id 96 | return ret 97 | 98 | # 获取当天发生的交易帐号 99 | def get_trade_account(self,date): 100 | engine = create_engine(self.__connectString__) 101 | # df=pd.read_sql("SELECT * FROM setting WHERE account_id IN (SELECT DISTINCT account_id FROM `order` WHERE trade_date='{}')".format(date),con=engine) 102 | df = pd.read_sql("SELECT * FROM setting ", con=engine) 103 | engine.dispose() 104 | return df 105 | if __name__ == '__main__': 106 | s=Setting() 107 | # s=Setting().getdata("back_test") 108 | s.account_id="live" 109 | s.max_position = 0.5 110 | s.max_single_position = 0.5 111 | s.per_buy_amount = 0.5 112 | s.memo = "实时" 113 | print(s.tojson()) 114 | df=pd.DataFrame( s.tojson(), ) 115 | # df = json_normalize(s) 116 | 117 | print(df) 118 | # s.per_buy_amount=10000 119 | # # s.save() 120 | # setting = Setting().getdata("live") 121 | # print(setting.max_position) 122 | pass 123 | # Log().delete('2022-05-06',0) 124 | # d= Log().getdata('2022-05-06',0) 125 | # if d!=None: 126 | # print(d.date) 127 | -------------------------------------------------------------------------------- /quant/model/stockindicator.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ############################################################################### 3 | # @author : vamed 4 | # @time :2021-02-02 5 | # @function:股票 6 | # Copyright (C) 2021-2023 7 | ############################################################################### 8 | # 9 | import datetime 10 | 11 | class StockIndicator(): 12 | def __init__(self): 13 | self.symbol = '' 14 | self.open = '' 15 | self.close = '' 16 | self.low = '' 17 | self.high = '' 18 | self.volume = '' 19 | self.amount = '' 20 | self.eob = '' 21 | self.pre_close = '' 22 | self.k_pre = '' 23 | self.d_pre = '' 24 | self.j_pre = '' 25 | self.date = '' 26 | self.inflow = 0 27 | self.strategy='' 28 | self.stock_type='' 29 | self.minute_kdj=[] 30 | self.inflow_time = datetime.datetime.strptime("{} {}".format(datetime.datetime.now().strftime("%Y-%m-%d"),"09:30:00"),"%Y-%m-%d %H:%M:%S") 31 | 32 | # 'minute_kdj': self.minute_kdj, 33 | def to_json(self): 34 | js = {'symbol': self.symbol, 'open': self.open, 'close': self.close, 35 | 'low': self.low, 'high': self.high, 36 | 'volume': self.volume, 'amount': self.amount, 'eob': self.eob, 'pre_close': self.pre_close, 37 | 'k_pre': self.k_pre,'d_pre': self.d_pre, 'j_pre': self.j_pre, 'date': self.date, 'inflow': self.inflow, 'strategy': self.strategy,'stock_type': self.stock_type, 'inflow_time': self.inflow_time} 38 | return js -------------------------------------------------------------------------------- /quant/model/stockpool.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ############################################################################### 3 | # @author : vamed 4 | # @time :2021-02-02 5 | # @function:股票池类 6 | # Copyright (C) 2021-2023 7 | ############################################################################### 8 | # 9 | import pandas as pd 10 | from sqlalchemy import Column, Integer, String, create_engine, DateTime, Float, DECIMAL 11 | from sqlalchemy.ext.declarative import declarative_base 12 | from sqlalchemy.orm import sessionmaker 13 | from quant.util.configutil import get_config 14 | 15 | Base = declarative_base() 16 | # 定义映射类User,其继承上一步创建的Base 17 | class StockPool(Base): 18 | # 指定本类映射到users表 19 | __tablename__ = 'stock_pool' 20 | __connectString__ = get_config('DATABASE', 'tradedb') 21 | 22 | id = Column(Integer, primary_key=True, autoincrement=True) 23 | # 指定name映射到name字段; name字段为字符串类形, 24 | code = Column(String(20)) 25 | symbol = Column(String(20)) 26 | name = Column(String(20)) 27 | change = Column(DECIMAL(6, 2)) 28 | price = Column(Float(10)) 29 | close = Column(Float(10)) 30 | date = Column(String(20)) 31 | strategy = Column(String(30)) 32 | rating = Column(String(30)) 33 | 34 | def insert(self): 35 | # 初始化数据库连接: 36 | engine = create_engine(self.__connectString__) 37 | # 创建DBSession类型: 38 | DBSession = sessionmaker(bind=engine) 39 | 40 | # 创建session对象: 41 | session = DBSession() 42 | # 添加到session: 43 | session.add(self) 44 | # 提交即保存到数据库: 45 | session.commit() 46 | # 关闭session: 47 | session.close() 48 | 49 | # 获取股票dataframe 50 | def getPositionStock(self): 51 | engine = create_engine(self.__connectString__) 52 | df = pd.read_sql("SELECT * FROM pick_time WHERE strategy='by_holding'", con=engine) 53 | engine.dispose() 54 | return df 55 | 56 | # 获取股票dataframe 57 | def getStock(self): 58 | engine = create_engine(self.__connectString__) 59 | df = pd.read_sql('select * from pick_time', con=engine) 60 | engine.dispose() 61 | return df 62 | 63 | 64 | # 获取股票通过代码 65 | def get_stock_by_code(self, code): 66 | engine = create_engine(self.__connectString__) 67 | df = pd.read_sql("select * from pick_time WHERE CODE='{}'".format(code), con=engine) 68 | engine.dispose() 69 | return df 70 | 71 | # 获取股票价格 72 | @classmethod 73 | def get_price(self, df, code): 74 | price = 0 75 | for i, row in df.iterrows(): 76 | if row.code == code: 77 | price = row.price 78 | return price 79 | 80 | # 条件删除数据 81 | def delete(self, symbols=[]): 82 | engine = create_engine(self.__connectString__) 83 | DBSession = sessionmaker(bind=engine) 84 | # 创建session对象 85 | session = DBSession() 86 | session.query(StockPool).filter(StockPool.code.in_(symbols)).delete() 87 | session.commit() 88 | engine.dispose() 89 | 90 | # 条件删除数据 91 | def delete_all(self): 92 | engine = create_engine(self.__connectString__) 93 | DBSession = sessionmaker(bind=engine) 94 | # 创建session对象 95 | session = DBSession() 96 | # session.query(PickTime).filter(Teacher.id > 4).delete() 97 | session.query(StockPool).delete() 98 | session.commit() 99 | engine.dispose() 100 | 101 | def update(self, code, js): 102 | engine = create_engine(self.__connectString__) 103 | DBSession = sessionmaker(bind=engine) 104 | session = DBSession() 105 | session.query(StockPool).filter(StockPool.code == code).update(js) 106 | session.commit() 107 | engine.dispose() 108 | 109 | # 批量插入股票池股票 110 | def batch_insert(self, df): 111 | engine = create_engine(self.__connectString__) 112 | df.to_sql(StockPool.__tablename__, engine, index=False, if_exists='append') 113 | engine.dispose() 114 | 115 | # 批量插入股票池股票 116 | def row_insert(self, row): 117 | engine = create_engine(self.__connectString__) 118 | row.to_sql(StockPool.__tablename__, engine, index=False, if_exists='append') 119 | engine.dispose() 120 | 121 | def get_picktime(self): 122 | engine = create_engine(self.__connectString__) 123 | df = pd.read_sql("select * from {} WHERE rating='{}'".format(self.__tablename__, "买入"), con=engine) 124 | engine.dispose() 125 | return df 126 | 127 | # 获取订单买入策略 128 | def get_symbols_strategy(self, symbols=[]): 129 | engine = create_engine(self.__connectString__) 130 | DBSession = sessionmaker(bind=engine) 131 | # 创建session对象 132 | session = DBSession() 133 | query = session.query(StockPool).filter(StockPool.symbol.in_(symbols)) 134 | df = pd.read_sql(query.statement, engine) 135 | session.commit() 136 | engine.dispose() 137 | df.sort_values(by='date') 138 | df.drop_duplicates(subset=['symbol'], keep='last', inplace=True) 139 | df.set_index('symbol', inplace=True) 140 | df['stock_type'] = 'buyin' 141 | df['open_price'] = 0 142 | df = df.loc[:, ['strategy', 'rating', 'stock_type', 'open_price']] 143 | return df 144 | 145 | 146 | if __name__ == '__main__': 147 | b = StockPool().get_symbols_strategy(['SZSE.000059']) 148 | print(b) 149 | -------------------------------------------------------------------------------- /quant/model/traderecord.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ############################################################################### 3 | # @author : vamed 4 | # @time :2021-02-02 5 | # @function:交易记录类 6 | # Copyright (C) 2021-2023 7 | ############################################################################### 8 | # 9 | 10 | from datetime import datetime 11 | from decimal import Decimal 12 | 13 | from sqlalchemy import Column, Integer, String, create_engine,DateTime,Float,DECIMAL 14 | from sqlalchemy.ext.declarative import declarative_base 15 | from sqlalchemy.orm import sessionmaker 16 | import pandas as pd 17 | 18 | from quant.logger import Logger 19 | from quant.util.configutil import get_config 20 | 21 | Base = declarative_base() 22 | # 定义映射类User,其继承上一步创建的Base 23 | class TradeRecord(Base): 24 | # 指定本类映射到users表 25 | __tablename__ = 'trade_record' 26 | __connectString__=get_config('DATABASE','tradedb') 27 | id = Column(Integer, primary_key=True, autoincrement=True) 28 | # 指定name映射到name字段; name字段为字符串类形, 29 | account_id = Column(String(50)) 30 | symbol = Column(String(20)) 31 | side = Column(Integer) 32 | price = Column(Float(10)) 33 | volume = Column(Integer) 34 | amount = Column(Float(20)) 35 | strategy=Column(String(50)) 36 | cl_ord_id = Column(String(50)) 37 | mode = Column(Integer) 38 | trade_date = Column(String(50)) 39 | trade_time = Column(DateTime,default=datetime.now()) 40 | record_time= Column(DateTime,default=datetime.now()) 41 | 42 | # __repr__方法用于输出该类的对象被print()时输出的字符串,如果不想写可以不写 43 | def __repr__(self): 44 | return "" % ( 45 | self.account_id,self.symbol,self.name,self.side,self.price,self.volume,self.amount,self.strategy,self.trade_date,self.trade_time) 46 | 47 | def insert(self): 48 | # 初始化数据库连接: 49 | engine = create_engine(self.__connectString__) 50 | # 创建DBSession类型: 51 | DBSession = sessionmaker(bind=engine) 52 | 53 | # 创建session对象: 54 | session = DBSession() 55 | # 创建新User对象: 56 | # 添加到session: 57 | session.add(self) 58 | # 提交即保存到数据库: 59 | session.commit() 60 | # 关闭session: 61 | session.close() 62 | 63 | def getRecord(self,start=None,end=None,acount='d28f4b30-4bf9-11ec-ad1b-00163e0a4100'): 64 | engine = create_engine(self.__connectString__) 65 | where='' 66 | if start!=None: 67 | where="trade_date>='{}'".format(start) 68 | if end!=None: 69 | if where!='': 70 | where=where+' and ' 71 | where=where+"trade_date<='{}'".format(end) 72 | if acount!="": 73 | if where != '': 74 | where = where + ' and ' 75 | where = where + " account_id='{}'".format(acount) 76 | if where!='': 77 | where=" where {}".format(where) 78 | df=pd.read_sql("select * from trade_record {}".format(where),con=engine) 79 | engine.dispose() 80 | return df 81 | # 获取单个股票交易记录 82 | def getStockRecord(self,symbol,start=None,end=None): 83 | engine = create_engine(self.__connectString__) 84 | where=" symbol='{}'".format(symbol) 85 | if start!=None: 86 | where=where+" and trade_time>='{}'".format(start) 87 | if end!=None: 88 | where=where+" and trade_time<='{}'".format(end) 89 | if where!='': 90 | where=' where {}'.format(where) 91 | df=pd.read_sql("select * from trade_record {}".format(where),con=engine) 92 | engine.dispose() 93 | return df 94 | # 获取单个股票交易记录 95 | def hasExistTradeRecord(self,symbol,trade_time,amount): 96 | ret=False 97 | engine = create_engine(self.__connectString__) 98 | where="where symbol='{}' and trade_time='{}' and amount={} ".format(symbol,trade_time,amount) 99 | df=pd.read_sql("select * from trade_record {}".format(where),con=engine) 100 | engine.dispose() 101 | if len(df)>0: 102 | ret=True 103 | return ret 104 | # 获取单个股票交易记录 105 | def getAccount(self): 106 | engine = create_engine(self.__connectString__) 107 | df = pd.read_sql("SELECT account_id,COUNT(*) AS cnt FROM trade_record GROUP BY account_id ", 108 | con=engine) 109 | engine.dispose() 110 | return df 111 | 112 | # 获取最多交易记录帐号 113 | def getAccountId(self): 114 | ret='' 115 | df=self.getAccount() 116 | if len(df)>0: 117 | df.sort_values(by=['cnt'],ascending=False,inplace=True) 118 | ret=df.at[0,'account_id'] 119 | return ret 120 | # 获取单个股票交易记录 121 | def getTradeRecord(self,symbol,acount,start=None,end=None): 122 | engine = create_engine(self.__connectString__) 123 | where=" symbol='{}' and account_id='{}'".format(symbol,acount) 124 | if start!=None: 125 | where=where+" and trade_time>='{}'".format(start) 126 | if end!=None: 127 | where=where+" and trade_time<='{}'".format(end) 128 | if where!='': 129 | where=' where {}'.format(where) 130 | df=pd.read_sql("select * from trade_record {}".format(where),con=engine) 131 | engine.dispose() 132 | df_buy=df.loc[df['side']==1,:] 133 | df_buy=df_buy.groupby(['trade_date']).sum(['amount', 'volume']) 134 | df_buy['buy']=round((df_buy['amount']/df_buy['volume']),2) 135 | 136 | df_sell = df.loc[df['side'] == -1, :] 137 | df_sell = df_sell.groupby(['trade_date']).sum(['amount', 'volume']) 138 | df_sell['sell'] = round((df_sell['amount'] / df_sell['volume']), 2) 139 | df_trade = df.groupby(['trade_date']).sum(['amount', 'volume']) 140 | df_trade['buy']=df_buy['buy'] 141 | df_trade['sell'] = df_sell['sell'] 142 | df_trade.index=pd.to_datetime(df_trade.index) 143 | # print(df_trade) 144 | return df_trade 145 | 146 | def batch_insert(self,df): 147 | for i,execrpt in df.iterrows(): 148 | if execrpt.exec_type == 15: 149 | trade_record = TradeRecord() 150 | trade_record.account_id = execrpt.account_id 151 | trade_record.symbol = execrpt.symbol 152 | if execrpt.side == 1: 153 | trade_record.side = 1 154 | elif execrpt.side == 2: 155 | trade_record.side = -1 156 | trade_record.price = execrpt.price 157 | trade_record.volume = execrpt.volume 158 | trade_record.cl_ord_id = execrpt.cl_ord_id 159 | trade_record.mode = execrpt.mode 160 | trade_record.amount =Decimal(execrpt.amount).quantize(Decimal('0.00')) 161 | trade_record.trade_date = execrpt.created_at.strftime('%Y-%m-%d') 162 | trade_record.record_time =datetime.now() 163 | trade_record.trade_time=pd.to_datetime(execrpt.created_at).strftime('%Y-%m-%d %H:%M:%S') 164 | if TradeRecord().hasExistTradeRecord(trade_record.symbol, trade_record.trade_time, 165 | trade_record.amount) == False: 166 | try: 167 | trade_record.insert() 168 | except Exception as e: 169 | Logger().logerror('交易记录保存错误:{}'.format(e)) 170 | if __name__ == '__main__': 171 | 172 | # StockPool().insert() 173 | # record=TradeRecord() 174 | # record.symbol='SHSE.600030' 175 | # record.side=1 176 | # record.price=11 177 | # record.volume=1000 178 | # record.amount=10000 179 | # record.trade_time=datetime.now() 180 | # record.record_time = datetime.now() 181 | # record.insert() 182 | print(TradeRecord().getAccountId()) 183 | df= TradeRecord().hasExistTradeRecord('SHSE.600743','2022-05-17 13:52:07.693056',33120) 184 | # df=TradeRecord().getAccount() 185 | print(df) 186 | # loginfo(TradeRecord().getRecord()) 187 | pass 188 | -------------------------------------------------------------------------------- /quant/picker.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ############################################################################### 3 | # @author : vamed 4 | # @time :2021-02-02 5 | # @function:选股类 6 | # Copyright (C) 2021-2023 7 | ############################################################################### 8 | # 9 | 10 | import sys,os 11 | import pandas as pd 12 | from quant.api.gmapi import GmApi 13 | from quant.enums.ratings import Ratings 14 | from quant.enums.strategys import Strategys 15 | from quant.util import stockutil 16 | 17 | base_dir=os.path.dirname(os.path.dirname(__file__))#获取pathtest的绝对路径 18 | sys.path.append(base_dir)#将pathtest的绝对路径加入到sys.path中 19 | from gm.enum import MODE_BACKTEST 20 | from quant.model.stockpool import StockPool 21 | # 择时 22 | class Picker(): 23 | # 获取择时股票列表 24 | def get_picktime(self,context,codes=[]): 25 | account_type=context.setting.account_type 26 | count=50-len(context.positions) 27 | if len(codes)==0: 28 | if context.mode==MODE_BACKTEST: 29 | df = StockPool().get_picktime() 30 | df = df.head(count) 31 | 32 | df.drop_duplicates(subset='code', keep='first', inplace=True) 33 | 34 | else: 35 | df=Picker().picktime_by_codes(codes) 36 | df.reset_index(drop=True,inplace=True) 37 | df = df.head(count) 38 | return df 39 | # 40 | def picktime_by_codes(self,codes=[]): 41 | df=pd.DataFrame(columns=['symbol','code','name','change','price','close', 'strategy', 'rating', 'date']) 42 | for code in codes: 43 | df_symbol= GmApi().getHistoryNKdata(symbol=stockutil.getGmSymbol(code),count=1) 44 | if len(df_symbol)>0: 45 | df_symbol['code'] = df_symbol['symbol'].apply(lambda x: stockutil.delSymbolPrefix(x)) 46 | df_symbol['name'] = '' 47 | df_symbol['change'] = 0 48 | df_symbol['price'] = df_symbol['close'] 49 | df_symbol['strategy'] = Strategys.MeanRevertingStrategy.value 50 | df_symbol['rating'] = Ratings.BuyIn.value 51 | df_symbol['date'] = df_symbol['eob'] 52 | df_symbol = df_symbol.loc[:, 53 | ['symbol', 'code', 'name', 'change', 'price', 'close', 'strategy', 'rating', 'date']] 54 | df=pd.concat([df,df_symbol],ignore_index=True,axis=0) 55 | 56 | df.drop_duplicates(subset=['symbol'],keep='first',inplace=True) 57 | return df 58 | 59 | if __name__=='__main__': 60 | df=Picker().picktime_by_codes(codes=['002281','002272']) 61 | print(df) 62 | pass -------------------------------------------------------------------------------- /quant/reporter.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ############################################################################### 3 | # @author : vamed 4 | # @time :2021-02-02 5 | # @function:盘后发送报告 6 | # Copyright (C) 2021-2023 7 | ############################################################################### 8 | # 9 | import sys,os 10 | base_dir=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取pathtest的绝对路径 11 | sys.path.append(base_dir)#将pathtest的绝对路径加入到sys.path中 12 | from quant.model.order import Order 13 | from datetime import datetime 14 | import pandas as pd 15 | from quant.model.position import Position 16 | from quant.model.setting import Setting 17 | 18 | # 盘后发送报告 19 | class Reporter(): 20 | 21 | def __init__(self): 22 | pass 23 | 24 | def get_report(self,date=datetime.now().strftime('%Y-%m-%d')): 25 | 26 | df_acnt=Setting().get_trade_account(date=date) 27 | trade_html="" 28 | for i,row in df_acnt.iterrows(): 29 | account_id= row['account_id'] 30 | account_name= row['name'] 31 | date= date 32 | df_dt=self.get_day_trade_record(account_id= row['account_id'],account_name= row['name'],date= date) 33 | if len(df_dt)>0: 34 | trade_html +="帐号:{},帐号名称:{},日期:{},交易记录:
{}
".format(account_id, account_name,date,df_dt.to_html()) 35 | 36 | trade_html +=self.get_position_report(account_id= row['account_id'],account_name= row['name']) 37 | 38 | htm="每天数据更新结果:

{}".format(trade_html) 39 | 40 | # 发送邮件 41 | # mailutil.send_mail(htm) 42 | print(htm) 43 | 44 | def get_day_trade_record(self,account_id='59ae65ad-c062-11ec-bde8-00163e0a4100',account_name='',date=datetime.now().strftime('%Y-%m-%d')): 45 | ret = Order().get_day_trade_record(account_id, date) 46 | return ret 47 | 48 | def get_day_trade_report(self,account_id='59ae65ad-c062-11ec-bde8-00163e0a4100',account_name='',date=datetime.now().strftime('%Y-%m-%d')): 49 | # 当天清仓盈利 50 | df_prof=Order().get_day_sellout_profit(account_id=account_id,date=date) 51 | if len(df_prof)==0: 52 | return '' 53 | df_profit=pd.DataFrame(data=df_prof.groupby("symbol").apply(lambda x:-(x['side']*x['price']*x['volume']).sum()),columns=['盈利']) 54 | # print(df_profit) 55 | # df_name=StockInfo().get_stock_by_symbols(df_profit.index.values) 56 | # df_profit['name']=df_name['name'] 57 | df_profit['name']='' 58 | df_factor=Order().get_day_sellout_orders(date=date,account_id=account_id) 59 | 60 | if len(df_factor)>0: 61 | df_buy = Order().get_order_by_symbols(df_factor.index.values, account_id=account_id) 62 | df_buy=df_buy.loc[df_buy['side']==1,:] 63 | df_buy.sort_values(by='trade_time',ascending=False,inplace=True) 64 | df_buy.drop_duplicates(subset='symbol',keep='first',inplace=True) 65 | df_buy.set_index('symbol',inplace=True) 66 | 67 | df_profit['buy_strategy'] = df_buy['strategy'] 68 | df_profit['buy_factor'] = df_buy['factor'] 69 | df_profit['buy_date'] = df_buy['trade_date'] 70 | df_profit['buy_price'] = df_buy['price'] 71 | 72 | df_profit['sell_strategy'] = df_factor['strategy'] 73 | df_profit['sell_factor'] = df_factor['factor'] 74 | df_profit['sell_price'] = df_factor['price'] 75 | df_profit['sell_date'] = df_factor['trade_date'] 76 | 77 | htm_profit= df_profit.to_html() 78 | profit=round(df_profit['盈利'].sum(),2) 79 | 80 | else: 81 | htm_profit="当天没有清仓的交易" 82 | profit=0 83 | 84 | # df_picktime=Picker().get_live_picktime(export_eastmoney=True) 85 | account = "{},ID:{}".format(account_name, account_id) 86 | 87 | ret="帐号:{}
每天清仓盈利(总计:{}元):
{}
".format(account,profit,htm_profit) 88 | return ret 89 | 90 | def get_position_report(self,account_id,account_name): 91 | # 当天持仓明细 92 | ret='' 93 | df_pos=Position().get_local_position(account_id=account_id) 94 | if len(df_pos)==0: 95 | return ret 96 | df_pos=df_pos.loc[:,['symbol','name','volume','open_price','price','amount','market_value','profit_amount','profit_rate','date']] 97 | pos_profit=round(df_pos['profit_amount'].sum(),2) 98 | htm_pos=df_pos.to_html() 99 | account="{},ID:{}".format(account_name,account_id) 100 | ret = "帐号:{}
每天持仓明细(持仓盈利总计:{}元):
{}
".format(account,pos_profit,htm_pos) 101 | return ret 102 | if __name__=="__main__": 103 | Reporter().get_report(date=datetime.now().strftime('%Y-%m-%d')) 104 | # Reporter().get_report(date='2023-01-10') -------------------------------------------------------------------------------- /quant/signalengine.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ############################################################################### 3 | # @author : vamed 4 | # @time :2021-02-02 5 | # @function:信号类 6 | # Copyright (C) 2021-2023 7 | ############################################################################### 8 | # 9 | 10 | import importlib 11 | from quant.util import stringutil 12 | 13 | class SignalEngine(): 14 | 15 | def __init__(self,signal_list): 16 | self.signal_list =signal_list 17 | self.signals=dict() 18 | for s in self.signal_list: 19 | s=s.strip() 20 | if s!='': 21 | self.signals[s]=self.init_signals(s) 22 | 23 | # 初始化信息对象 24 | def init_signals(self,signal_name): 25 | imp_class = getattr(importlib.import_module("quant.signals.{}".format(signal_name.lower())), 26 | signal_name) 27 | cls_obj = imp_class() 28 | return cls_obj 29 | 30 | # @classmethod 31 | def fit_buy(self, stock_indicator): 32 | ret = False 33 | fit_name=[] 34 | fit_indicator=[] 35 | for sig_name in self.signals: 36 | ret_fit=self.signals[sig_name].fit_buy(stock_indicator) 37 | if ret_fit[0]: 38 | fit_name.append("{}_fit_buy".format(ret_fit[1])) 39 | fit_indicator.append(ret_fit[2]) 40 | ret=True 41 | 42 | return (ret,stringutil.array_to_string(fit_name),stringutil.array_to_string(fit_indicator)) 43 | 44 | # @classmethod 45 | def fit_sell(self, stock_indicator): 46 | ret = False 47 | fit_name = [] 48 | fit_indicator = [] 49 | for sig_name in self.signals: 50 | ret_fit=self.signals[sig_name].fit_sell(stock_indicator) 51 | if ret_fit[0]: 52 | fit_name.append("{}_fit_sell".format(ret_fit[1])) 53 | fit_indicator.append(ret_fit[2]) 54 | ret=True 55 | return (ret,stringutil.array_to_string(fit_name),stringutil.array_to_string(fit_indicator)) 56 | 57 | if __name__=="__main__": 58 | pass 59 | # df = PickTime().getStock() 60 | # df.set_index('code',inplace=True) 61 | # if FactorEngine().fit(df.loc['600056'],10)==True: 62 | # print('fddddddddddddddd') 63 | # else: 64 | # print('111111111111111111') -------------------------------------------------------------------------------- /quant/signals.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ############################################################################### 3 | # @author : vamed 4 | # @time :2021-02-02 5 | # @function:交易信号类 6 | # Copyright (C) 2021-2023 7 | ############################################################################### 8 | # 9 | from quant.indicators import Indicator 10 | class Signal(): 11 | def __init__(self): 12 | pass 13 | # 止损 14 | @classmethod 15 | def buy_signal(self,df): 16 | df=self.kdj_signal(df) 17 | # df=self.boll_signal(df) 18 | return df 19 | 20 | # macd金叉 21 | @classmethod 22 | def macd_signal(self,df): 23 | df=Indicator.macd(df) 24 | df['dif_pre'] = df['dif'].shift(1) 25 | df["dif_pre"].fillna(method='bfill', inplace=True) 26 | df['dea_pre'] = df['dea'].shift(1) 27 | df["dea_pre"].fillna(method='bfill', inplace=True) 28 | df.loc[(df['dif'] >= df['dea']) & (df['dif_pre'] < df['dea_pre']), ['signal']] = 1 29 | df.loc[(df['dif'] < df['dea']) & (df['dif_pre'] >= df['dea_pre']), ['signal']] = 0 30 | df["signal"].fillna(method='ffill', inplace=True) 31 | return df 32 | 33 | # kdj金叉 34 | @classmethod 35 | def kdj_signal(self,df): 36 | df=Indicator.kdj(df) 37 | # print(df) 38 | df['k_pre'] = df['k'].shift(1) 39 | df["k_pre"].fillna(method='bfill', inplace=True) 40 | df['d_pre'] = df['d'].shift(1) 41 | df["d_pre"].fillna(method='bfill', inplace=True) 42 | df['j_pre'] = df['j'].shift(1) 43 | df["j_pre"].fillna(method='bfill', inplace=True) 44 | 45 | df.loc[(df['k'] >= df['d']) & (df['k_pre'] < df['d_pre']), ['signal']] = 1 46 | df.loc[(df['k'] < df['d']) & (df['k_pre'] >= df['d_pre']), ['signal']] = 0 47 | # df.loc[df['j']>100, ['signal']] = 0 48 | df["signal"].fillna(method='ffill', inplace=True) 49 | # print(df) 50 | return df 51 | 52 | if __name__=="__main__": 53 | 54 | df=Signal().bt(symbol='SHSE.601318', start = '2022-03-01',end = '2022-11-05') 55 | -------------------------------------------------------------------------------- /quant/signals/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/signals/__init__.py -------------------------------------------------------------------------------- /quant/signals/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/signals/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /quant/signals/__pycache__/kdjsignal.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/signals/__pycache__/kdjsignal.cpython-38.pyc -------------------------------------------------------------------------------- /quant/signals/__pycache__/macdsignal.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/signals/__pycache__/macdsignal.cpython-38.pyc -------------------------------------------------------------------------------- /quant/signals/__pycache__/signalbase.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/signals/__pycache__/signalbase.cpython-38.pyc -------------------------------------------------------------------------------- /quant/signals/kdjsignal.py: -------------------------------------------------------------------------------- 1 | from abc import abstractmethod 2 | from quant.signals.signalbase import SignalBase 3 | 4 | # kdj 5 | class KdjSignal(SignalBase): 6 | 7 | def __init__(self): 8 | self.name="KdjSignal" 9 | 10 | # 匹配买入信息 11 | # @classmethod 12 | def fit_buy(self, stock_indicator): 13 | ret = False 14 | indicator='' 15 | if (stock_indicator.k_pre < stock_indicator.d_pre) and (stock_indicator.k >= stock_indicator.d) : 16 | indicator="k_pre:{},d_pre:{},k:{},j:{}".format(stock_indicator.k_pre, 17 | stock_indicator.d_pre, stock_indicator.k, 18 | stock_indicator.d) 19 | ret=True 20 | return (ret,self.name,indicator) 21 | 22 | # 匹配卖出信息 23 | # @abstractmethod 24 | def fit_sell(self, stock_indicator): 25 | ret = False 26 | indicator = '' 27 | if(stock_indicator.k_pre > stock_indicator.d_pre) and (stock_indicator.k <= stock_indicator.d): 28 | indicator="k_pre:{},d_pre:{},j_pre:{},k:{},d:{},j:{}".format(stock_indicator.k_pre, 29 | stock_indicator.d_pre, stock_indicator.j_pre,stock_indicator.k, 30 | stock_indicator.d,stock_indicator.j) 31 | ret = True 32 | return (ret,self.name,indicator) 33 | -------------------------------------------------------------------------------- /quant/signals/macdsignal.py: -------------------------------------------------------------------------------- 1 | from abc import abstractmethod 2 | from quant.signals.signalbase import SignalBase 3 | 4 | # macd 5 | class MacdSignal(SignalBase): 6 | 7 | def __init__(self): 8 | self.name="MacdSignal" 9 | 10 | # 匹配买入信息 11 | # @classmethod 12 | def fit_buy(self, stock_indicator): 13 | ret = False 14 | indicator='' 15 | 16 | if (stock_indicator.pre_dif < stock_indicator.pre_dea) and (stock_indicator.dif >= stock_indicator.dea): 17 | indicator="pre_dif:{},pre_dif:{},dif:{},dea:{}".format(stock_indicator.pre_dif, 18 | stock_indicator.pre_dea, stock_indicator.dif, 19 | stock_indicator.dea) 20 | ret=True 21 | return (ret,self.name,indicator) 22 | 23 | # 匹配卖出信息 24 | # @abstractmethod 25 | def fit_sell(self, stock_indicator): 26 | ret = False 27 | indicator = '' 28 | if(stock_indicator.pre_dif > stock_indicator.pre_dea) and (stock_indicator.dif <= stock_indicator.dea): 29 | indicator="pre_dif:{},pre_dea:{},dif:{},dea:{}".format(stock_indicator.pre_dif, 30 | stock_indicator.pre_dea, stock_indicator.dif, 31 | stock_indicator.dea) 32 | ret = True 33 | return (ret,self.name,indicator) 34 | -------------------------------------------------------------------------------- /quant/signals/signalbase.py: -------------------------------------------------------------------------------- 1 | from abc import abstractmethod 2 | # 信号基类 3 | class SignalBase(): 4 | 5 | def __init__(self): 6 | pass 7 | 8 | # 匹配买入信息 9 | @abstractmethod 10 | def fit_buy(self, stock_indicator): 11 | ret = False 12 | return ret 13 | 14 | # 匹配卖出信息 15 | @abstractmethod 16 | def fit_sell(self, stock_indicator): 17 | ret = False 18 | return ret 19 | -------------------------------------------------------------------------------- /quant/startengine.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # from __future__ import print_function, absolute_import 3 | # -*- encoding: utf-8 -*- 4 | ############################################################################### 5 | # @author : vamed 6 | # @time :2021-02-02 7 | # @function:入口 8 | # Copyright (C) 2021-2023 9 | ############################################################################### 10 | # 11 | import sys,os 12 | base_dir=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取pathtest的绝对路径 13 | sys.path.append(base_dir)#将pathtest的绝对路径加入到sys.path中 14 | 15 | import datetime 16 | from quant.logger import Logger 17 | from quant.util.configutil import get_config 18 | from gm.api import * 19 | from quant.quantengine import QuantEngine 20 | from quant.model.backtestrecord import BacktestRecord 21 | from quant.util import globalvars 22 | 23 | # 回测帐号ID 24 | 25 | # 策略中必须有init方法 26 | def init(context): 27 | subscribe(symbols='SHSE.000001', frequency='60s', count=1) 28 | # subscribe(symbols='SHSE.600030,SHSE.600050,SHSE.600007', frequency='60s', count=200) 29 | # 设置回测模式帐号ID 30 | print(globalvars.hand_trade) 31 | if (context.mode==MODE_BACKTEST): 32 | context.backtest_id=globalvars.backtest_id 33 | context.backtest_account_id='' 34 | if globalvars.backtest_account_id!='': 35 | context.backtest_account_id = globalvars.backtest_account_id 36 | 37 | context.quantengine=QuantEngine(context) 38 | 39 | def on_bar(context, bars): 40 | # print(bars) 41 | context.quantengine.on_bar(context,bars) 42 | 43 | def on_tick(context, tick): 44 | # print(tick) 45 | # logdebug(tick) 46 | context.quantengine.on_tick(context,tick) 47 | 48 | # 响应委托状态更新事情,下单后及委托状态更新时被触发。 49 | def on_order_status(context, order): 50 | context.quantengine.on_order_status(context, order) 51 | 52 | # 响应委托被执行事件,委托成交或者撤单拒绝后被触发。 53 | def on_execution_report(context, execrpt): 54 | context.quantengine.on_execution_report(context, execrpt) 55 | 56 | # 响应交易账户状态更新事件,交易账户状态变化时被触发。 57 | def on_account_status(context, account): 58 | # Logger().logdebug(account) 59 | pass 60 | 61 | #当发生异常情况,比如断网时、终端服务崩溃是会触发 62 | def on_error(context, code, info): 63 | # print(context.now) 64 | # Logger().loginfo(code) 65 | Logger().logerror(info) 66 | 67 | # 在回测模式下,回测结束后会触发该事件,并返回回测得到的绩效指标对象 68 | def on_backtest_finished(context, indicator): 69 | BacktestRecord().update_backtest_record(account_id=context.backtest_account_id,date=context.now.strftime("%Y-%m-%d %H:%M:%S"), status="finished") 70 | Logger().loginfo(indicator) 71 | 72 | def run_engine(mode): 73 | # globalvars.hand_trade=ht 74 | if mode=='1': 75 | strategy_id = get_config('ACCOUNT', 'strategy_id') 76 | run_mode=MODE_LIVE 77 | Logger().loginfo("mode运行模式, 实时模式:MODE_LIVE:1") 78 | else: 79 | strategy_id = get_config('ACCOUNT', 'backtest_strategy_id') 80 | run_mode = MODE_BACKTEST 81 | Logger().loginfo("mode运行模式, 回测模式:MODE_BACKTEST:2") 82 | 83 | # backtest_start_time = '2022-03-03 08:00:00' 84 | #backtest_start_time = '2022-05-27 08:00:00' 85 | #backtest_end_time = '2023-02-27 16:00:00' 86 | # 掘金只提供最近一个月的免费数据,默认回测时间为最近20天 87 | current_date = datetime.datetime.now().date() 88 | backtest_start_time = (current_date + datetime.timedelta(days=-20)).strftime("%Y-%m-%d %H:%M:%S") 89 | backtest_end_time = current_date.strftime("%Y-%m-%d %H:%M:%S") 90 | 91 | if run_mode==MODE_BACKTEST and mode=='2': 92 | br=BacktestRecord().get_suspend_record() 93 | if br== None: 94 | # 回测模式帐号ID 95 | globalvars.backtest_account_id = "{}{}".format("back_test", globalvars.backtest_id) 96 | br=BacktestRecord() 97 | br.account_id=globalvars.backtest_account_id 98 | br.start=backtest_start_time 99 | br.current = backtest_start_time 100 | br.end = backtest_end_time 101 | br.date=datetime.datetime.now().strftime("%Y-%m-%d") 102 | br.status="start" 103 | br.symbols="" 104 | br.insert() 105 | else: 106 | globalvars.backtest_account_id=br.account_id 107 | backtest_start_time=br.current 108 | # pass 109 | try: 110 | run(strategy_id=strategy_id, 111 | filename='startengine.py', 112 | mode=run_mode, 113 | token=get_config('TOKEN','gmtoken'), 114 | backtest_start_time=backtest_start_time, 115 | backtest_end_time=backtest_end_time, 116 | backtest_adjust=ADJUST_PREV, 117 | backtest_initial_cash=1000000, 118 | backtest_commission_ratio=0.0001, 119 | backtest_slippage_ratio=0.0001) 120 | except Exception as e: 121 | Logger().loginfo(e) 122 | Logger().logerror(e) 123 | 124 | 125 | # if __name__ == '__main__': 126 | # 127 | # # start_up(["--mode=3"]) 128 | # # start_up(["--mode=2","--ht=1"]) 129 | # # run_engine(["--mode=1"]) 130 | # # start_up() 131 | # pass 132 | 133 | -------------------------------------------------------------------------------- /quant/startengine_bt.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # from __future__ import print_function, absolute_import 3 | # -*- encoding: utf-8 -*- 4 | ############################################################################### 5 | # @author : vamed 6 | # @time :2021-02-02 7 | # @function:回测入口,支持断点回测 8 | # Copyright (C) 2021-2023 9 | ############################################################################### 10 | # 11 | import multiprocessing 12 | import sys,os 13 | base_dir=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取pathtest的绝对路径 14 | sys.path.append(base_dir)#将pathtest的绝对路径加入到sys.path中 15 | 16 | import time 17 | import argparse 18 | import ctypes 19 | import datetime 20 | import inspect 21 | from quant.logger import Logger 22 | from quant.util.putil import check_run_gm 23 | from gm.api import * 24 | from quant.startengine import run_engine 25 | 26 | # 强制杀死线程 27 | def _async_raise(tid, exctype): 28 | """raises the exception, performs cleanup if needed""" 29 | tid = ctypes.c_long(tid) 30 | if not inspect.isclass(exctype): 31 | exctype = type(exctype) 32 | res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype)) 33 | if res == 0: 34 | raise ValueError("invalid thread id") 35 | elif res != 1: 36 | # """if it returns a number greater than one, you're in trouble, 37 | # and you should call it again with exc=NULL to revert the effect""" 38 | ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None) 39 | raise SystemError("PyThreadState_SetAsyncExc failed") 40 | 41 | # 停止线程 42 | def stop_thread(thread): 43 | if thread.ident: 44 | _async_raise(thread.ident, SystemExit) 45 | 46 | # 解析参数 47 | def parse_args(pargs=None): 48 | parser = argparse.ArgumentParser( 49 | formatter_class=argparse.ArgumentDefaultsHelpFormatter, 50 | description=( 51 | 'Sample Skeleton' 52 | ) 53 | ) 54 | 55 | parser.add_argument('--mode', default='2', 56 | required=False, help='mode运行模式, 实时模式:MODE_LIVE:1回测模式:MODE_BACKTEST:2') 57 | 58 | 59 | return parser.parse_args(pargs) 60 | 61 | # 启动交易程序 62 | def start_up(args=None): 63 | args = parse_args(args) 64 | Logger().logerror(args.mode) 65 | # run_strategy(args.mode) 66 | if args.mode=='2': 67 | check_run_gm() 68 | Logger().loginfo("-------------start--------------------") 69 | Logger().loginfo("-------------正在检测掘金客户端是否打开--------------------") 70 | time.sleep(30) 71 | stoped="stoped" 72 | running="running" 73 | status=stoped 74 | show_tip=True 75 | process_run_engine = multiprocessing.Process(target=run_engine,args=(args.mode,)) 76 | while True: 77 | now=datetime.datetime.now() 78 | # 8:50和20:50掘金服务器暂停 79 | if (now.hour<8) or ((now.hour>8) and (now.hour<20)) or (now.hour>20): 80 | if status!=running: 81 | Logger().loginfo(datetime.datetime.now()) 82 | process_run_engine = multiprocessing.Process(target=run_engine,args=(args.mode,)) 83 | Logger().loginfo("-------------策略正在初始化-------------") 84 | process_run_engine.start() 85 | status=running 86 | Logger().loginfo("-------------策略正在运行-------------") 87 | elif (now.hour==8) or ((now.hour==20)): 88 | if show_tip: 89 | Logger().loginfo("-------------当前时间服务有问题,回测程序停止-----------------") 90 | show_tip=False 91 | # if status!=stoped: 92 | else: 93 | # stop() 94 | stop_thread(process_run_engine) 95 | status=stoped 96 | Logger().loginfo("-------------策略已停止-------------") 97 | for i in range(10): 98 | print(i) 99 | time.sleep(1) 100 | sys.exit() 101 | time.sleep(1) 102 | 103 | elif args.mode == '4': 104 | run_engine('2') 105 | else: 106 | process_run_engine = multiprocessing.Process(target=run_engine,args=(args.mode,)) 107 | check_run_gm() 108 | Logger().loginfo("-------------start--------------------") 109 | Logger().loginfo("-------------正在检测掘金客户端是否打开--------------------") 110 | # time.sleep(30) 111 | stoped="stoped" 112 | running="running" 113 | status=stoped 114 | show_tip=True 115 | 116 | while True: 117 | now=datetime.datetime.now() 118 | if (now.hour>=9) and (now.hour<16): 119 | if status!=running: 120 | Logger().loginfo(datetime.datetime.now()) 121 | process_run_engine = multiprocessing.Process(target=run_engine,args=(args.mode,)) 122 | Logger().loginfo("-------------策略正在初始化-------------") 123 | process_run_engine.start() 124 | status=running 125 | Logger().loginfo("-------------策略正在运行-------------") 126 | else: 127 | if show_tip: 128 | Logger().loginfo("-------------当前非交易时间,交易程序停止-----------------") 129 | show_tip=False 130 | if status!=stoped: 131 | # stop() 132 | stop_thread(process_run_engine) 133 | status=stoped 134 | Logger().loginfo("-------------策略已停止-------------") 135 | for i in range(10): 136 | print(i) 137 | time.sleep(1) 138 | sys.exit() 139 | time.sleep(1) 140 | 141 | if __name__ == '__main__': 142 | 143 | start_up(["--mode=4"]) 144 | # start_up(["--mode=2"]) 145 | # start_up(["--mode=1"]) 146 | # start_up() 147 | 148 | -------------------------------------------------------------------------------- /quant/startengine_live.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # from __future__ import print_function, absolute_import 3 | # -*- encoding: utf-8 -*- 4 | ############################################################################### 5 | # @author : vamed 6 | # @time :2021-02-02 7 | # @function:实盘入口 8 | # Copyright (C) 2021-2023 9 | ############################################################################### 10 | # 11 | import multiprocessing 12 | import sys,os 13 | base_dir=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取pathtest的绝对路径 14 | sys.path.append(base_dir)#将pathtest的绝对路径加入到sys.path中 15 | 16 | import time 17 | import argparse 18 | import ctypes 19 | import datetime 20 | import inspect 21 | from quant.logger import Logger 22 | from quant.util.putil import check_run_gm 23 | from gm.api import * 24 | from quant.startengine import run_engine 25 | 26 | # 强制杀死线程 27 | def _async_raise(tid, exctype): 28 | """raises the exception, performs cleanup if needed""" 29 | tid = ctypes.c_long(tid) 30 | if not inspect.isclass(exctype): 31 | exctype = type(exctype) 32 | res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype)) 33 | if res == 0: 34 | raise ValueError("invalid thread id") 35 | elif res != 1: 36 | # """if it returns a number greater than one, you're in trouble, 37 | # and you should call it again with exc=NULL to revert the effect""" 38 | ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None) 39 | raise SystemError("PyThreadState_SetAsyncExc failed") 40 | 41 | # 停止线程 42 | def stop_thread(thread): 43 | if thread.ident: 44 | _async_raise(thread.ident, SystemExit) 45 | 46 | # 解析参数 47 | def parse_args(pargs=None): 48 | parser = argparse.ArgumentParser( 49 | formatter_class=argparse.ArgumentDefaultsHelpFormatter, 50 | description=( 51 | 'Sample Skeleton' 52 | ) 53 | ) 54 | 55 | parser.add_argument('--mode', default='2', 56 | required=False, help='mode运行模式, 实时模式:MODE_LIVE:1回测模式:MODE_BACKTEST:2') 57 | 58 | 59 | return parser.parse_args(pargs) 60 | 61 | # 启动交易程序 62 | def start_up(args=None): 63 | args = parse_args(args) 64 | Logger().logerror(args.mode) 65 | # run_strategy(args.mode) 66 | if args.mode=='2': 67 | check_run_gm() 68 | Logger().loginfo("-------------start--------------------") 69 | Logger().loginfo("-------------正在检测掘金客户端是否打开--------------------") 70 | time.sleep(30) 71 | stoped="stoped" 72 | running="running" 73 | status=stoped 74 | show_tip=True 75 | process_run_engine = multiprocessing.Process(target=run_engine,args=(args.mode,)) 76 | while True: 77 | now=datetime.datetime.now() 78 | # 8:50和20:50掘金服务器暂停 79 | if (now.hour<8) or ((now.hour>8) and (now.hour<20)) or (now.hour>20): 80 | if status!=running: 81 | Logger().loginfo(datetime.datetime.now()) 82 | process_run_engine = multiprocessing.Process(target=run_engine,args=(args.mode,)) 83 | Logger().loginfo("-------------策略正在初始化-------------") 84 | process_run_engine.start() 85 | status=running 86 | Logger().loginfo("-------------策略正在运行-------------") 87 | elif (now.hour==8) or ((now.hour==20)): 88 | if show_tip: 89 | Logger().loginfo("-------------当前时间服务有问题,回测程序停止-----------------") 90 | show_tip=False 91 | # if status!=stoped: 92 | else: 93 | # stop() 94 | stop_thread(process_run_engine) 95 | status=stoped 96 | Logger().loginfo("-------------策略已停止-------------") 97 | for i in range(10): 98 | print(i) 99 | time.sleep(1) 100 | sys.exit() 101 | time.sleep(1) 102 | 103 | elif args.mode == '3': 104 | run_engine('3') 105 | 106 | else: 107 | process_run_engine = multiprocessing.Process(target=run_engine,args=(args.mode,)) 108 | check_run_gm() 109 | Logger().loginfo("-------------start--------------------") 110 | Logger().loginfo("-------------正在检测掘金客户端是否打开--------------------") 111 | time.sleep(30) 112 | stoped="stoped" 113 | running="running" 114 | status=stoped 115 | show_tip=True 116 | 117 | while True: 118 | now=datetime.datetime.now() 119 | if (now.hour>=9) and (now.hour<16): 120 | if status!=running: 121 | Logger().loginfo(datetime.datetime.now()) 122 | process_run_engine = multiprocessing.Process(target=run_engine,args=(args.mode,)) 123 | Logger().loginfo("-------------策略正在初始化-------------") 124 | process_run_engine.start() 125 | status=running 126 | Logger().loginfo("-------------策略正在运行-------------") 127 | else: 128 | if show_tip: 129 | Logger().loginfo("-------------当前非交易时间,交易程序停止-----------------") 130 | show_tip=False 131 | if status!=stoped: 132 | # stop() 133 | stop_thread(process_run_engine) 134 | status=stoped 135 | Logger().loginfo("-------------策略已停止-------------") 136 | for i in range(10): 137 | print(i) 138 | time.sleep(1) 139 | sys.exit() 140 | time.sleep(1) 141 | 142 | if __name__ == '__main__': 143 | 144 | # start_up(["--mode=3"]) 145 | # start_up(["--mode=2"]) 146 | start_up(["--mode=1"]) 147 | # start_up() 148 | 149 | -------------------------------------------------------------------------------- /quant/strategyengine.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ############################################################################### 3 | # @author : vamed 4 | # @time :2021-02-02 5 | # @function:策略调用引擎 6 | # Copyright (C) 2021-2023 7 | ############################################################################### 8 | # 9 | from __future__ import print_function, absolute_import 10 | import sys,os 11 | base_dir=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取pathtest的绝对路径 12 | sys.path.append(base_dir)#将pathtest的绝对路径加入到sys.path中 13 | from quant.enums.strategytype import StrategyType 14 | 15 | import importlib 16 | 17 | # 策略引擎 18 | class StrategyEngine(): 19 | 20 | def __init__(self,context,strategy_list,broker,send_order_event_handler): 21 | # strategy_names= enumutil.getItems(Strategys) 22 | self.strategy_list =strategy_list 23 | self.context=context 24 | # strategy_names = ['MeanRevertingStrategy', 'RiskStrategy', 'TrendFollowingStrategy', 'DayTradingStrategy'] 25 | # strategy_names = ['RiskStrategy'] 26 | self.strategys=dict() 27 | for s in self.strategy_list: 28 | s=s.strip() 29 | if s!='': 30 | self.strategys[s]=self.init_strategy(s,context,broker,send_order_event_handler) 31 | 32 | 33 | def init_strategy(self,strategy_name,context,broker,send_order_event_handler): 34 | imp_class = getattr(importlib.import_module("quant.strategys.{}".format(strategy_name.lower())), 35 | strategy_name) 36 | cls_obj = imp_class(context,broker,send_order_event_handler) 37 | return cls_obj 38 | 39 | def run(self,context,stock_indicator,bar=None,tick=None): 40 | if stock_indicator.strategy!="": 41 | # 运行股票选定策略 42 | if stock_indicator.strategy in self.strategys.keys(): 43 | if bar and self.strategys[stock_indicator.strategy].drive_type == StrategyType.bar.value: 44 | self.strategys[stock_indicator.strategy].run(context, bar, stock_indicator) 45 | elif tick and (self.strategys[stock_indicator.strategy].drive_type==StrategyType.tick.value): 46 | self.strategys[stock_indicator.strategy].run(context, tick, stock_indicator) 47 | 48 | # 运行全域策略 49 | for key in self.strategys: 50 | if key!=stock_indicator.strategy: 51 | if bar and (self.strategys[key].drive_type == StrategyType.bar.value): 52 | self.strategys[key].run(context, bar, stock_indicator) 53 | elif tick and (self.strategys[key].drive_type == StrategyType.tick.value): 54 | self.strategys[key].run(context, tick, stock_indicator) 55 | else: 56 | for key in self.strategys: 57 | if bar and (self.strategys[key].drive_type==StrategyType.bar.value): 58 | self.strategys[key].run(context,bar,stock_indicator) 59 | elif tick and (self.strategys[key].drive_type==StrategyType.tick.value): 60 | self.strategys[key].run(context, tick, stock_indicator) 61 | 62 | # 运行定时策略 63 | def run_schedule(self): 64 | for key in self.strategys: 65 | if self.strategys[key].drive_type == StrategyType.schedule.value: 66 | self.strategys[key].run_schedule() 67 | 68 | def update_order_symbols(self): 69 | for key in self.strategys: 70 | self.strategys[key].update_order_symbols() 71 | 72 | if __name__=="__main__": 73 | 74 | # df = StockPool().get_picktime() 75 | 76 | # stock_indicator = get_obj_from_df(df, StockIndicator) 77 | # StrategyEngine().run('','',stock_indicator) 78 | pass 79 | -------------------------------------------------------------------------------- /quant/strategys/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/strategys/__init__.py -------------------------------------------------------------------------------- /quant/strategys/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/strategys/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /quant/strategys/__pycache__/gridstrategy.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/strategys/__pycache__/gridstrategy.cpython-38.pyc -------------------------------------------------------------------------------- /quant/strategys/__pycache__/meanrevertingstrategy.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/strategys/__pycache__/meanrevertingstrategy.cpython-38.pyc -------------------------------------------------------------------------------- /quant/strategys/__pycache__/strategybase.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/strategys/__pycache__/strategybase.cpython-38.pyc -------------------------------------------------------------------------------- /quant/strategys/gridstrategy.py: -------------------------------------------------------------------------------- 1 | from quant.enums.scopes import Scopes 2 | from quant.enums.strategytype import StrategyType 3 | from quant.logger import Logger 4 | from quant.model.position import Position 5 | from quant.strategys.strategybase import StrategyBase 6 | from quant.util import stockutil 7 | 8 | # 网格交易策略 9 | class GridStrategy(StrategyBase): 10 | 11 | def __init__(self,context,broker,send_order_event_handler=None): 12 | # 初始化父类 13 | super().__init__(context,broker,send_order_event_handler) 14 | # 策略名称 15 | self.strategy_name='GridStrategy' 16 | # 行情推送数据类型bar,tick 17 | self.drive_type = StrategyType.bar.value 18 | self.scope = Scopes.universal 19 | # 获取当天已交易订单信息 20 | self.get_order_symbols() 21 | #日内交易已卖出股票代码数组 22 | self.daytrading_sell_symbols=self.strategy_sell_symbols 23 | # 日内交易已买入股票代码数组 24 | self.daytrading_buy_symbols=self.strategy_buy_symbols 25 | # 偏离分时均价阀值 26 | self.threshold=0.02 27 | # 日内交易截止时间 28 | self.daytradeing_time="14:55" 29 | 30 | # 从最后买单中找出当前股票的订单 31 | def get_last_order(self, context,symbol): 32 | return context.last_orders[context.last_orders['symbol']==symbol] 33 | 34 | # context.last_orders.at[context.last_orders[context.last_orders['symbol'] == symbol].index[0], 'price' 35 | # 执行买入操作 36 | def do_buy(self, context, tick, stock_indicator): 37 | # ma_price=stock_indicator.map 38 | symbol=tick['symbol'] 39 | price=tick['close'] 40 | factor = "" 41 | time_now = context.now.strftime('%H:%M') 42 | 43 | df=self.get_last_order(context,symbol) 44 | if len(df)==0: 45 | return 46 | last_price=df.at[df.index[0], 'price'] 47 | # 如果在日内交易时间设置前,则进行日内交易 48 | if (time_now =m_kdj[4])): 52 | if (last_price-price)/last_price > self.threshold : 53 | factor = "fit_buy_gridstrategy" 54 | 55 | # 如果在日内交易时间设置后,如果有卖出交易,格价在分时均线之后,买回 56 | else: 57 | # if (symbol in context.daytrading_sell_symbols) and (symbol not in context.daytrading_buy_symbols): 58 | # factor="DayTradingStrategy_buy" 59 | pass 60 | if factor!="": 61 | 62 | if symbol not in self.daytrading_buy_symbols: 63 | print("------------------buy-------------------") 64 | self.daytrading_buy_symbols.append(symbol) 65 | self.trader.buy(symbol= symbol,price= price,factor= factor,strategy=self.strategy_name) 66 | 67 | # 执行卖出操作 68 | def do_sell(self,context,tick,stock_indicator): 69 | # ma_price=stock_indicator.map 70 | symbol = tick['symbol'] 71 | price = tick['close'] 72 | if (symbol not in context.holding_symbols): 73 | return 74 | 75 | 76 | pos=Position().get_cache_position(context.positions,symbol) 77 | # print(obj_to_series(pos)) 78 | # 如果没有持仓,则终止 79 | if pos.can_use_volume==0: 80 | return 81 | 82 | volume=stockutil.value_to_volume(context.setting.per_buy_amount,price) 83 | 84 | if volume>pos.can_use_volume: 85 | volume=pos.can_use_volume 86 | factor="" 87 | 88 | # 如果在日内交易时间设置前,则进行日内交易 89 | time_now = context.now.strftime('%H:%M') 90 | df=self.get_last_order(context,symbol) 91 | if len(df)==0: 92 | return 93 | last_price=df.at[df.index[0], 'price'] 94 | if (time_now self.threshold and (symbol not in self.daytrading_sell_symbols): 96 | factor = "GridStrategy_sell" 97 | # 如果在日内交易时间设置后,如果有买入交易,格价在分时均线之上,卖出 98 | # elif (symbol in self.daytrading_buy_symbols) and (symbol not in self.daytrading_sell_symbols): 99 | # order=self.get_symbol_orders(symbol,1) 100 | # if order[0].price0): 49 | price = bar['close'] 50 | fit_signal = self.signals.fit_sell(stock_indicator) 51 | if fit_signal[0]: 52 | factor = fit_signal[1] 53 | indicator = fit_signal[2] 54 | if symbol not in StrategyBase.sell_symbols: 55 | print("------------------sell-------------------") 56 | StrategyBase.sell_symbols.append(symbol) 57 | self.trader.sell_out(symbol,price,factor=factor,strategy=self.strategy_name,indicator=indicator) 58 | 59 | def run(self,context,bar,stock_indicator): 60 | Logger().logdebug('正在运行策略:{}'.format(self.strategy_name)) 61 | symbol=bar['symbol'] 62 | # self.fit_money_flow(stock_indicator) 63 | if symbol in context.holding_symbols: 64 | self.do_sell(context,bar,stock_indicator) 65 | 66 | if symbol in context.buy_symbols: 67 | self.do_buy(context, bar, stock_indicator) 68 | 69 | #更新当天订单股票代码 70 | def update_order_symbols(self): 71 | # 获取当天已交易订单信息 72 | self.get_order_symbols() 73 | # # 日内交易已卖出股票代码数组 74 | StrategyBase.sell_symbols= [] 75 | # # 日内交易已买入股票代码数组 76 | StrategyBase.buy_symbols= [] 77 | # 日内交易已卖出股票代码数组 78 | self.meanreverting_sell_symbols = [] 79 | # 日内交易已买入股票代码数组 80 | self.meanreverting_buy_symbols = [] -------------------------------------------------------------------------------- /quant/strategys/strategybase.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from quant.trader import Trader 3 | 4 | # 策略基类 5 | class StrategyBase(): 6 | # 日内交易已卖出股票代码数组 7 | sell_symbols = [] 8 | # 日内交易已买入股票代码数组 9 | buy_symbols = [] 10 | 11 | def __init__(self, context, broker, send_order_event_handler=None): 12 | self.context = context 13 | # 经纪商 14 | self.broker = broker 15 | # 订单回调函数 16 | self.broker.send_order_event_handler = send_order_event_handler 17 | # 订单交易对象 18 | self.trader = Trader(context, broker) 19 | # 策略名称 20 | self.strategy_name = '' 21 | 22 | # 策略交易已卖出股票代码数组 23 | self.strategy_sell_symbols = [] 24 | # 策略交易已买入股票代码数组 25 | self.strategy_buy_symbols = [] 26 | # self.get_order_symbols() 27 | # 上次检查资金流时间 28 | self.money_flow_time = datetime.datetime.strptime( 29 | "{} {}".format(datetime.datetime.now().strftime("%Y-%m-%d"), "09:30:00"), "%Y-%m-%d %H:%M:%S") 30 | 31 | # # 获取缓存持仓信息 32 | def get_cache_position(self, positions, symbol): 33 | ret = None 34 | for pos in positions: 35 | if pos.symbol == symbol: 36 | ret = pos 37 | break 38 | return ret 39 | 40 | # 获取订单股票代码 41 | def get_order_symbols(self): 42 | for o in self.context.orders: 43 | # 所有交易代码 44 | if o.status == 5: 45 | continue 46 | if o.side == 1: 47 | if o.symbol not in self.buy_symbols: 48 | self.buy_symbols.append(o.symbol) 49 | else: 50 | if o.symbol not in self.sell_symbols: 51 | self.sell_symbols.append(o.symbol) 52 | 53 | # 略策交易代码 54 | if o.strategy == self.strategy_name: 55 | if o.side == 1: 56 | if o.symbol not in self.strategy_buy_symbols: 57 | self.strategy_buy_symbols.append(o.symbol) 58 | else: 59 | if o.symbol not in self.strategy_sell_symbols: 60 | self.strategy_sell_symbols.append(o.symbol) 61 | 62 | 63 | # # 获取股票代码的订单 64 | def get_symbol_orders(self,symbol,side): 65 | orders=[] 66 | for o in self.context.orders: 67 | if (o.symbol==symbol) and (o.side==side): 68 | orders.append(o) 69 | 70 | return orders -------------------------------------------------------------------------------- /quant/trader.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | ############################################################################### 3 | # @author : vamed 4 | # @time :2021-02-02 5 | # @function: 交易类 6 | # Copyright (C) 2015-2020 7 | ############################################################################### 8 | 9 | from gm.api import * 10 | from quant.account import Account 11 | from quant.logger import Logger 12 | from quant.util import stockutil 13 | 14 | # 交易类 15 | class Trader(): 16 | def __init__(self,context,broker): 17 | self.trade_records = dict() 18 | self.context=context 19 | self.account=Account(self.context) 20 | self.broker=broker 21 | 22 | def buy(self,symbol,price,value=None,factor='',strategy= 'strategy',indicator='',checkPos=True): 23 | ret=None 24 | if self.broker.hasPosition() == False: 25 | Logger().loginfo('已经超过设置最大仓位') 26 | return 27 | if (self.broker.isUnderMaxStockNumber() == False) and checkPos: 28 | Logger().loginfo('已经超过设置最大持仓股数据') 29 | return 30 | if (self.broker.isUnderMaxStockPosition(symbol) == False): 31 | Logger().loginfo('已经超过设置单股最大持仓金额') 32 | return 33 | if value==None: 34 | value=self.account.PER_BUY_AMOUNT 35 | if self.has_unfinished_orders(symbol)==False: 36 | self.trade_records[symbol] = factor 37 | volume=stockutil.value_to_volume(value,price) 38 | ret=self.broker.buy(symbol=symbol, volume=volume, price=price,factor=factor,indicator=indicator,strategy= strategy) 39 | Logger().loginfo('----------buy--------') 40 | return ret 41 | 42 | 43 | def order_target_percent(self,symbol,percent,factor='',strategy= 'strategy',indicator='',checkPos=True): 44 | ret = self.broker.order_target_percent(symbol=symbol, percent=percent, factor=factor, indicator=indicator, 45 | strategy=strategy) 46 | Logger().loginfo(ret) 47 | return ret 48 | # 清仓股票 49 | def sell_out(self,symbol,price,factor='',indicator='',strategy= 'strategy'): 50 | self.broker.sell_out(symbol,price,factor=factor,indicator=indicator,strategy= strategy) 51 | self.trade_records[symbol] =factor 52 | 53 | #卖出股票 54 | def sell(self,symbol,price,volume,factor='',indicator='',strategy= 'strategy'): 55 | self.broker.sell(symbol,volume,price,factor=factor,indicator=indicator,strategy= strategy) 56 | self.trade_records[symbol] =factor 57 | 58 | # 清仓所有仓位 59 | def close_all(self): 60 | self.broker.close_all() 61 | # 止损 62 | def stop_lost(self,symbol,cost,price): 63 | if cost*0.9>price: 64 | self.sell_target_volume(symbol, price, 0, "stop_lost") 65 | print("stop_lost") 66 | 67 | def has_unfinished_orders(self,code): 68 | ret=False 69 | orders = get_unfinished_orders() 70 | for i,o in enumerate(orders): 71 | if o.symbol==code: 72 | ret=True 73 | break 74 | return ret 75 | 76 | # 订单取消 77 | def cancel_order(self,code,side): 78 | self.broker.cancel_order(code,side) 79 | -------------------------------------------------------------------------------- /quant/updateposition.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # -*- encoding: utf-8 -*- 3 | ############################################################################### 4 | # @author : vamed 5 | # @time :2021-02-02 6 | # @function:盘后更新持仓数据到本地数据库 7 | # Copyright (C) 2021-2023 8 | ############################################################################### 9 | # 10 | from __future__ import print_function, absolute_import 11 | 12 | import datetime 13 | import sys,os 14 | import time 15 | 16 | base_dir=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取pathtest的绝对路径 17 | sys.path.append(base_dir)#将pathtest的绝对路径加入到sys.path中 18 | 19 | from quant.stockdata import StockData 20 | from quant.brokers.myquantbroker import MyquantBroker 21 | from quant.enums.brokers import Brokers 22 | from quant.model.setting import Setting 23 | 24 | from quant.util.configutil import get_config 25 | 26 | from gm.api import * 27 | 28 | # 盘后更新持仓数据到本地数据库 29 | def init(context): 30 | # 订阅浦发银行, bar频率为一天和一分钟 31 | # 订阅订阅多个频率的数据,可多次调用subscribe 32 | # self.context = context 33 | # 获取当前setting帐号id 34 | if len(context.accounts) == 0: 35 | print("当然设置的帐号交易配置文件错误:略策文件没有挂接交易帐号") 36 | stop() 37 | 38 | # 获取交易帐号设置参数 39 | setting_account_id = Setting.get_setting_accountid(context) 40 | setting = Setting().getdata(setting_account_id) 41 | if setting == None: 42 | print( 43 | "当然设置的帐号:{},交易配置文件错误:数据库setting表没有配置交易设置信息".format(context.setting_account_id)) 44 | stop() 45 | else: 46 | context.setting = setting 47 | 48 | # context.backtest_id="{}{}".format(context.setting.account_id, context.backtest_id) 49 | # # 设置当然帐号对象 50 | # self.account = Account(self.context) 51 | # 52 | # # 显示输出交易帐号设置参数 53 | # self.account.display_run_param() 54 | 55 | # 设定经纪商 56 | # broker = get_borker(context=context, broker_type=setting.broker) 57 | # # 打印显示当前帐号资金信息 58 | # broker.display_account_asset_info() 59 | # 60 | # # 获取持仓信息并打印输出 61 | # positions = broker.getPositions(display=True) 62 | # print(positions) 63 | broker=get_borker(context,setting.broker) 64 | StockData.update_position(broker) 65 | # GmApi().update_execution_reports() 66 | # pos= Account(context).getPositions() 67 | # print(pos) 68 | print("-------------end update_position--------------------") 69 | print(datetime.datetime.now()) 70 | time.sleep(30) 71 | stop() 72 | print("-------------stop--------------------") 73 | print(datetime.datetime.now()) 74 | 75 | #获取经纪商 76 | def get_borker(context,broker_type): 77 | if broker_type==Brokers.xtquant.value: 78 | # broker=XtBroker(context=context) 79 | pass 80 | else: 81 | broker = MyquantBroker(context=context) 82 | return broker 83 | 84 | 85 | def run_start(): 86 | # check_run_gm() 87 | print("-------------start--------------------") 88 | print(datetime.datetime.now()) 89 | # time.sleep(30) 90 | print(datetime.datetime.now()) 91 | run_context() 92 | print("-------------end update_position--------------------") 93 | 94 | def run_context(): 95 | run( 96 | strategy_id=get_config('ACCOUNT','strategy_id'), 97 | # filename="{}\\quant\\{}".format(base_dir,'updateposition.py'), 98 | filename='updateposition.py', 99 | mode=MODE_LIVE, 100 | token=get_config('TOKEN','gmtoken'), 101 | # backtest_start_time='2020-11-01 08:00:00', 102 | # backtest_end_time='2020-11-10 16:00:00', 103 | # backtest_adjust=ADJUST_PREV, 104 | # backtest_initial_cash=10000000, 105 | # backtest_commission_ratio=0.0001, 106 | # backtest_slippage_ratio=0.0001 107 | ) 108 | 109 | if __name__ == '__main__': 110 | run_start() 111 | 112 | -------------------------------------------------------------------------------- /quant/util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/util/__init__.py -------------------------------------------------------------------------------- /quant/util/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/util/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /quant/util/__pycache__/configutil.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/util/__pycache__/configutil.cpython-38.pyc -------------------------------------------------------------------------------- /quant/util/__pycache__/datetimeutil.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/util/__pycache__/datetimeutil.cpython-38.pyc -------------------------------------------------------------------------------- /quant/util/__pycache__/fileutil.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/util/__pycache__/fileutil.cpython-38.pyc -------------------------------------------------------------------------------- /quant/util/__pycache__/globalvars.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/util/__pycache__/globalvars.cpython-38.pyc -------------------------------------------------------------------------------- /quant/util/__pycache__/numberutil.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/util/__pycache__/numberutil.cpython-38.pyc -------------------------------------------------------------------------------- /quant/util/__pycache__/pdutil.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/util/__pycache__/pdutil.cpython-38.pyc -------------------------------------------------------------------------------- /quant/util/__pycache__/putil.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/util/__pycache__/putil.cpython-38.pyc -------------------------------------------------------------------------------- /quant/util/__pycache__/stockutil.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/util/__pycache__/stockutil.cpython-38.pyc -------------------------------------------------------------------------------- /quant/util/__pycache__/stringutil.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vamed/starquant/90390f1496821eeedb42ace62582c31fbe158b6d/quant/util/__pycache__/stringutil.cpython-38.pyc -------------------------------------------------------------------------------- /quant/util/configutil.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | import sys,os 3 | base_dir=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取pathtest的绝对路径 4 | sys.path.append(base_dir)#将pathtest的绝对路径加入到sys.path中 5 | # loginfo(base_dir) 6 | def get_config(section,key): 7 | config = configparser.ConfigParser() 8 | # loginfo('{}/{}'.format(base_dir,"config.ini")) 9 | config.read('{}/{}'.format(base_dir,"config.ini")) 10 | return config[section][key] 11 | 12 | def set_config(file,section,key,value): 13 | # coding=utf-8 14 | import sys 15 | cf = configparser.ConfigParser() 16 | cf.read(file) 17 | cf.set(section, key, value) 18 | cf.write(open(file, "w")) 19 | pass 20 | if __name__=='__main__': 21 | # str=get_config('TOKEN','gmtoken') 22 | set_config("E:/temp/StockwayStock.ini", "\SelfSelect", "择时", "0.000333,0.002248,0.000905,1.600030,1.600050,") 23 | # loginfo(str) -------------------------------------------------------------------------------- /quant/util/datetimeutil.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | base_dir=os.path.dirname(os.path.dirname(__file__))#获取pathtest的绝对路径 5 | sys.path.append(base_dir)#将pathtest的绝对路径加入到sys.path中 6 | # 时间比较 格式hh:mm 7 | import datetime 8 | 9 | # 日期大小比较 10 | def time_compare(time1,timestr): 11 | ret=False 12 | t1=float(time_compare(get_time_string(time1).replace(':','.'))) 13 | t2 = float(timestr.replace(':', '.')) 14 | if t1>=t2 : 15 | ret=True 16 | return ret 17 | # 格式化日期输出时间部分'%H:%M' 18 | def get_time_string(date): 19 | return date.strftime('%H:%M') 20 | 21 | # 计算交易时间过去的分钟数 22 | def get_pass_minutes(date): 23 | hour=date.hour 24 | minute=date.minute 25 | ret=0 26 | if hour<9: 27 | ret=0 28 | elif hour>=15: 29 | ret=240 30 | elif hour >=9 and hour<12: 31 | ret = (hour-9)*60+minute-30 32 | elif hour == 12: 33 | ret = 120 34 | elif hour >12 and hour<15: 35 | ret = (hour-11)*60+minute 36 | return ret-1 37 | pass 38 | 39 | def add_minutes(stime,mins): 40 | time= stime.split(':') 41 | hour= int(time[0]) 42 | minute=int(time[1]) 43 | minute=minute + mins 44 | if minute>=60: 45 | minute=minute%60 46 | hour=hour+1 47 | if hour>=24: 48 | hour=hour%24 49 | return "{}:{}".format(str(hour).rjust(2, '0'),str(minute).rjust(2, '0')) 50 | 51 | def count_differ_days(time_a, time_b): 52 | """ 53 | 计算日期相差天数 54 | """ 55 | # 因为得到的是UTC时间,所以需要UTC时间+8 56 | time_a = time_a + datetime.timedelta(hours=8) 57 | time_b = time_b + datetime.timedelta(hours=8) 58 | 59 | d1 = datetime.date(time_a.year, time_a.month, time_a.day) 60 | d2 = datetime.date(time_b.year, time_b.month, time_b.day) 61 | 62 | return (d1 - d2).days 63 | 64 | if __name__=='__main__': 65 | print(add_minutes("23:55",5)) -------------------------------------------------------------------------------- /quant/util/enumutil.py: -------------------------------------------------------------------------------- 1 | 2 | def getItems(Enum): 3 | items=[''] 4 | for enum in Enum: 5 | items.append(enum.value) 6 | return items 7 | 8 | 9 | if __name__=="__main__": 10 | # print(getItems(Factors)) 11 | pass -------------------------------------------------------------------------------- /quant/util/fileutil.py: -------------------------------------------------------------------------------- 1 | import sys,os 2 | base_dir=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))#获取pathtest的绝对路径 3 | sys.path.append(base_dir)#将pathtest的绝对路径加入到sys.path中 4 | # loginfo(base_dir) 5 | import configparser 6 | import subprocess 7 | 8 | def get_config(file=None,section=None,key=None): 9 | config = configparser.ConfigParser() 10 | if file==None: 11 | file='{}/{}'.format(base_dir,"config.ini") 12 | try: 13 | config.read(file,encoding=check_charset(file)) 14 | except: 15 | config.read(file) 16 | return config[section][key] 17 | def check_charset(file_path): 18 | import chardet 19 | with open(file_path, "rb") as f: 20 | data = f.read(4) 21 | charset = chardet.detect(data)['encoding'] 22 | return charset 23 | 24 | def set_config(file,section,key,value): 25 | # coding=utf-8 26 | cf = configparser.ConfigParser() 27 | try: 28 | cf.read(file,encoding=check_charset(file)) 29 | except: 30 | cf.read(file) 31 | # cf.read(file) 32 | cf.set(section, key, value) 33 | cf.write(open(file, "w")) 34 | pass 35 | 36 | def runfile(file): 37 | if os.path.exists(file): 38 | rc, out = subprocess.getstatusoutput(file) 39 | # loginfo(rc) 40 | # loginfo('*' * 10) 41 | # loginfo(out) 42 | # pass 43 | 44 | if __name__=='__main__': 45 | 46 | # file=get_config(section='GOLDMINER',key='path') 47 | # runfile(file) 48 | 49 | file=get_config(section='FUTU',key='path') 50 | runfile(file) 51 | # file="E:/temp/StockwayStock.ini" 52 | # loginfo(file) 53 | # str=get_config(file,'\SelfSelect','自选股') 54 | # # set_config("E:/temp/StockwayStock.ini", "\SelfSelect", "择时", "0.000333,0.002248,0.000905,1.600030,1.600050,") 55 | # loginfo(str) -------------------------------------------------------------------------------- /quant/util/globalvars.py: -------------------------------------------------------------------------------- 1 | # 全局变量 2 | import datetime 3 | 4 | backtest_account_id="" 5 | backtest_id=datetime.datetime.now().strftime("%Y%m%d%H%M%S") 6 | # backtest_id='20230206164646' 7 | hand_trade=1 -------------------------------------------------------------------------------- /quant/util/numberutil.py: -------------------------------------------------------------------------------- 1 | import math 2 | import numpy as np 3 | 4 | def get_ticks(max,n): 5 | pos=0 6 | if max<0: 7 | pos=-1 8 | max=abs(max) 9 | step = max / n 10 | st_n = int(math.log10(step)) 11 | if st_n>0: 12 | step = round((step / math.pow(10, st_n)), 0) * math.pow(10, st_n) 13 | else: 14 | step =round(round((step / math.pow(10, st_n)),1) * math.pow(10, st_n),1-st_n) 15 | end=step*n 16 | if (max>end): 17 | n=n+1 18 | end=step*n 19 | if pos==0: 20 | end = end + step 21 | ret=np.arange(0,end,step) 22 | else: 23 | ret=np.arange(-end,0,step) 24 | return ret 25 | 26 | 27 | def get_step(max): 28 | max_m=math.log10(max) 29 | st=max/2 30 | st_n=int(math.log10(st)) 31 | st=int(st/math.pow(10, st_n))*10 32 | # st=math.pow(10, zero_m-1)*5 33 | 34 | return st 35 | 36 | # 浮点数转换 37 | def parse_float(number): 38 | ret=number 39 | if number!=None: 40 | if np.isnan(number): 41 | ret=None 42 | return ret 43 | 44 | #取整百 45 | def get_neat(i): 46 | ret=0 47 | 48 | if i<100: 49 | ret=0 50 | else: 51 | p=pow(10,len(str(i))-1) 52 | ret=int(i/p)*p 53 | return ret 54 | 55 | #取整百 56 | def get_n_number(d,i): 57 | ret=d 58 | if d>pow(10,i+1): 59 | log=np.log10(d) 60 | ret=d/pow(10,int(int(log)))*pow(10,i) 61 | return int(ret) 62 | 63 | if __name__=="__main__": 64 | # print(get_neat(239487)) 65 | # d=len(search("\.(0*)", "5.00060030").group(1)) 66 | # dist = int(math.log10(abs(0.000060030))) 67 | # s= get_ticks(100,2) 68 | # s2 = get_ticks(-0.03, 2) 69 | # s1 = get_ticks(-110,2) 70 | # # s = get_ticks(310, 2) 71 | # 72 | # s3 = get_ticks(0.15,2) 73 | # s4 = get_ticks(0.0006,4) 74 | # s5 = get_ticks(6,3) 75 | # s5 = get_ticks(4,2) 76 | # s5 = get_ticks(9,2) 77 | # s5 = get_ticks(20,2) 78 | # s5 = get_ticks(60,2) 79 | 80 | # p= np.log10(23000) 81 | # print(p) 82 | # print(int(p)) 83 | # 84 | # p10=pow(10,int(p)) 85 | # print(p10) 86 | p=get_n_number(999999,4) 87 | print(p) 88 | pass 89 | # findzero(0.004) 90 | -------------------------------------------------------------------------------- /quant/util/pdutil.py: -------------------------------------------------------------------------------- 1 | import sys,os 2 | import pandas as pd 3 | base_dir=os.path.dirname(os.path.dirname(os.path.dirname(__file__)))#获取pathtest的绝对路径 4 | sys.path.append(base_dir)#将pathtest的绝对路径加入到sys.path中 5 | 6 | from quant.logger import Logger 7 | # 单步插入数据 8 | def to_sql_by_step(df, tablename,engine): 9 | df.reset_index(drop=True,inplace=True) 10 | for i, r in df.iterrows(): 11 | row = df.loc[i:i, :] 12 | try: 13 | row.to_sql(tablename, engine, index=False, if_exists='append') 14 | except Exception as e: 15 | # logerror('to_sql_by_step(self,df,tablename):{}'.format(e)) 16 | Logger().logerror(e) 17 | 18 | 19 | # 保存数据到本地文件 20 | def to_csv(df,filename): 21 | try: 22 | file="{}/data/{}".format(base_dir,filename) 23 | df.to_csv(file) 24 | except Exception as e: 25 | Logger().logerror('获取北向个股资金流:{}'.format(e)) 26 | 27 | # 生成数据文件名 28 | def get_filename(table_name,date,squence=None): 29 | if squence==None: 30 | file="{}-{}.csv".format(table_name,date) 31 | else: 32 | file="{}-{}-{}.csv".format(table_name,squence,date) 33 | return file 34 | 35 | # 添加对象实例到dataframe 36 | def append_object_instance(df,obj): 37 | ls = dir(obj) 38 | vals = [] 39 | keys = [] 40 | for attr in ls: 41 | if attr[0:2] != '__': 42 | keys.append(attr) 43 | vals.append(getattr(obj, attr)) 44 | srs = pd.Series(data=vals, index=keys) 45 | df=df.append(srs, ignore_index=True) 46 | return df 47 | 48 | # 添加对象实例到dataframe 49 | def append_from_dic(df,dict): 50 | df_dict=pd.DataFrame(dict,index=[0]) 51 | df=pd.concat([df,df_dict],ignore_index=True) 52 | return df 53 | 54 | # 添加对象实例到dataframe 55 | def obj_to_series(obj): 56 | ls = dir(obj) 57 | vals = [] 58 | keys = [] 59 | for attr in ls: 60 | if (attr[0:2] != '__') and (attr !='data'): 61 | keys.append(attr) 62 | vals.append(getattr(obj, attr)) 63 | series = pd.Series(data=vals, index=keys) 64 | return series 65 | # 获取某列第一个值 66 | def get_df_col_value(df,col_judge,value,column): 67 | ret=0 68 | for i,row in df.iterrows(): 69 | if row[col_judge]==value: 70 | ret=row[column] 71 | break 72 | return ret 73 | 74 | # dict转类对象 75 | def dict_to_object(cls,dict): 76 | obj=cls() 77 | obj.__dict__=dict 78 | return obj 79 | 80 | # 获取 df中某行记录并转实类实例 81 | def get_obj_from_df(df,cls): 82 | # df=df.loc[df[col]==val,:] 83 | records=df.to_dict(orient ='records') 84 | obj=dict_to_object(cls,records[0]) 85 | return obj 86 | 87 | def list_to_df(v): 88 | if isinstance(v, list): 89 | return pd.DataFrame(data=v) 90 | elif isinstance(v, dict): 91 | return pd.DataFrame(data=[v, ]) 92 | else: 93 | return pd.DataFrame(data=[{'value': v}]) 94 | 95 | # if __name__=="__main__": 96 | # to_csv("","") -------------------------------------------------------------------------------- /quant/util/putil.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from threading import Thread 3 | import psutil 4 | import subprocess 5 | import os 6 | 7 | # 运行程序 8 | from quant.util import fileutil 9 | 10 | def asyn(f): 11 | def wrapper(*args, **kwargs): 12 | thr = Thread(target=f, args=args, kwargs=kwargs) 13 | thr.start() 14 | 15 | return wrapper 16 | @asyn 17 | def run(file): 18 | if os.path.exists(file): 19 | rc,out= subprocess.getstatusoutput(file) 20 | 21 | # 检测某程序是否已运行 22 | def check_process(name): 23 | pl = psutil.pids() 24 | ret=0 25 | pname="" 26 | for pid in pl: 27 | if psutil.pid_exists(pid): 28 | try: 29 | pname=psutil.Process(pid).name() 30 | except: 31 | pass 32 | if pname == name: 33 | ret=pid 34 | # print(pid) 35 | break 36 | return ret 37 | 38 | def check_run_gm(): 39 | ret=0 40 | file = fileutil.get_config(section='GOLDMINER', key='path') 41 | fname=file.split('/')[-1] 42 | if check_process(fname)==0: 43 | run(file) 44 | else: 45 | ret=1 46 | return ret 47 | 48 | 49 | if __name__=='__main__': 50 | check_run_gm() -------------------------------------------------------------------------------- /quant/util/redisutil.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from redis import StrictRedis 4 | 5 | # 数据库连接方式 因为就算我自己使用的,所有没有设置密码 6 | def getredis(): 7 | redis = StrictRedis(host='localhost', port=6379, db=0, password='foobared') 8 | return redis 9 | 10 | rd=getredis() 11 | # rd.set('logging_level',logging.DEBUG) 12 | rd.set('logging_level',logging.INFO) 13 | dd=rd.get('logging_level') 14 | # loginfo(dd) -------------------------------------------------------------------------------- /quant/util/stockutil.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from quant.util import numberutil 5 | 6 | base_dir=os.path.dirname(os.path.dirname(__file__))#获取pathtest的绝对路径 7 | sys.path.append(base_dir)#将pathtest的绝对路径加入到sys.path中 8 | # 转换东方财富网数据的股票代码为掘金代码格式 9 | from datetime import datetime 10 | import pandas as pd 11 | # 交易日 12 | trade_dates =['2022-01-04', '2022-01-05', '2022-01-06', '2022-01-07', '2022-01-10', '2022-01-11', '2022-01-12', '2022-01-13', '2022-01-14', '2022-01-17', '2022-01-18', '2022-01-19', '2022-01-20', '2022-01-21', '2022-01-24', '2022-01-25', '2022-01-26', '2022-01-27', '2022-01-28', '2022-02-07', '2022-02-08', '2022-02-09', '2022-02-10', '2022-02-11', '2022-02-14', '2022-02-15', '2022-02-16', '2022-02-17', '2022-02-18', '2022-02-21', '2022-02-22', '2022-02-23', '2022-02-24', '2022-02-25', '2022-02-28', '2022-03-01', '2022-03-02', '2022-03-03', '2022-03-04', '2022-03-07', '2022-03-08', '2022-03-09', '2022-03-10', '2022-03-11', '2022-03-14', '2022-03-15', '2022-03-16', '2022-03-17', '2022-03-18', '2022-03-21', '2022-03-22', '2022-03-23', '2022-03-24', '2022-03-25', '2022-03-28', '2022-03-29', '2022-03-30', '2022-03-31', '2022-04-01', '2022-04-06', '2022-04-07', '2022-04-08', '2022-04-11', '2022-04-12', '2022-04-13', '2022-04-14', '2022-04-15', '2022-04-18', '2022-04-19', '2022-04-20', '2022-04-21', '2022-04-22', '2022-04-25', '2022-04-26', '2022-04-27', '2022-04-28', '2022-04-29', '2022-05-05', '2022-05-06', '2022-05-09', '2022-05-10', '2022-05-11', '2022-05-12', '2022-05-13', '2022-05-16', '2022-05-17', '2022-05-18', '2022-05-19', '2022-05-20', '2022-05-23', '2022-05-24', '2022-05-25', '2022-05-26', '2022-05-27', '2022-05-30', '2022-05-31', '2022-06-01', '2022-06-02', '2022-06-06', '2022-06-07', '2022-06-08', '2022-06-09', '2022-06-10', '2022-06-13', '2022-06-14', '2022-06-15', '2022-06-16', '2022-06-17', '2022-06-20', '2022-06-21', '2022-06-22', '2022-06-23', '2022-06-24', '2022-06-27', '2022-06-28', '2022-06-29', '2022-06-30', '2022-07-01', '2022-07-04', '2022-07-05', '2022-07-06', '2022-07-07', '2022-07-08', '2022-07-11', '2022-07-12', '2022-07-13', '2022-07-14', '2022-07-15', '2022-07-18', '2022-07-19', '2022-07-20', '2022-07-21', '2022-07-22', '2022-07-25', '2022-07-26', '2022-07-27', '2022-07-28', '2022-07-29', '2022-08-01', '2022-08-02', '2022-08-03', '2022-08-04', '2022-08-05', '2022-08-08', '2022-08-09', '2022-08-10', '2022-08-11', '2022-08-12', '2022-08-15', '2022-08-16', '2022-08-17', '2022-08-18', '2022-08-19', '2022-08-22', '2022-08-23', '2022-08-24', '2022-08-25', '2022-08-26', '2022-08-29', '2022-08-30', '2022-08-31', '2022-09-01', '2022-09-02', '2022-09-05', '2022-09-06', '2022-09-07', '2022-09-08', '2022-09-09', '2022-09-13', '2022-09-14', '2022-09-15', '2022-09-16', '2022-09-19', '2022-09-20', '2022-09-21', '2022-09-22', '2022-09-23', '2022-09-26', '2022-09-27', '2022-09-28', '2022-09-29', '2022-09-30', '2022-10-10', '2022-10-11', '2022-10-12', '2022-10-13', '2022-10-14', '2022-10-17', '2022-10-18', '2022-10-19', '2022-10-20', '2022-10-21', '2022-10-24', '2022-10-25', '2022-10-26', '2022-10-27', '2022-10-28', '2022-10-31', '2022-11-01', '2022-11-02', '2022-11-03', '2022-11-04', '2022-11-07', '2022-11-08', '2022-11-09', '2022-11-10', '2022-11-11', '2022-11-14', '2022-11-15', '2022-11-16', '2022-11-17', '2022-11-18', '2022-11-21', '2022-11-22', '2022-11-23', '2022-11-24', '2022-11-25', '2022-11-28', '2022-11-29', '2022-11-30', '2022-12-01', '2022-12-02', '2022-12-05', '2022-12-06', '2022-12-07', '2022-12-08', '2022-12-09', '2022-12-12', '2022-12-13', '2022-12-14', '2022-12-15', '2022-12-16', '2022-12-19', '2022-12-20', '2022-12-21', '2022-12-22', '2022-12-23', '2022-12-26', '2022-12-27', '2022-12-28', '2022-12-29', '2022-12-30', '2023-01-03', '2023-01-04', '2023-01-05', '2023-01-06', '2023-01-09', '2023-01-10', '2023-01-11', '2023-01-12', '2023-01-13', '2023-01-16', '2023-01-17', '2023-01-18', '2023-01-19', '2023-01-20', '2023-01-30', '2023-01-31', '2023-02-01', '2023-02-02', '2023-02-03', '2023-02-06', '2023-02-07', '2023-02-08', '2023-02-09', '2023-02-10', '2023-02-13', '2023-02-14', '2023-02-15', '2023-02-16', '2023-02-17', '2023-02-20', '2023-02-21', '2023-02-22', '2023-02-23', '2023-02-24', '2023-02-27', '2023-02-28', '2023-03-01', '2023-03-02', '2023-03-03', '2023-03-06', '2023-03-07', '2023-03-08', '2023-03-09', '2023-03-10', '2023-03-13', '2023-03-14', '2023-03-15', '2023-03-16', '2023-03-17', '2023-03-20', '2023-03-21', '2023-03-22', '2023-03-23', '2023-03-24', '2023-03-27', '2023-03-28', '2023-03-29', '2023-03-30', '2023-03-31', '2023-04-03', '2023-04-04', '2023-04-06', '2023-04-07', '2023-04-10', '2023-04-11', '2023-04-12', '2023-04-13', '2023-04-14', '2023-04-17', '2023-04-18', '2023-04-19', '2023-04-20', '2023-04-21', '2023-04-24', '2023-04-25', '2023-04-26', '2023-04-27', '2023-04-28', '2023-05-04', '2023-05-05', '2023-05-08', '2023-05-09', '2023-05-10', '2023-05-11', '2023-05-12', '2023-05-15', '2023-05-16', '2023-05-17', '2023-05-18', '2023-05-19', '2023-05-22', '2023-05-23', '2023-05-24', '2023-05-25', '2023-05-26', '2023-05-29', '2023-05-30', '2023-05-31', '2023-06-01', '2023-06-02', '2023-06-05', '2023-06-06', '2023-06-07', '2023-06-08', '2023-06-09', '2023-06-12', '2023-06-13', '2023-06-14', '2023-06-15', '2023-06-16', '2023-06-19', '2023-06-20', '2023-06-21', '2023-06-26', '2023-06-27', '2023-06-28', '2023-06-29', '2023-06-30', '2023-07-03', '2023-07-04', '2023-07-05', '2023-07-06', '2023-07-07', '2023-07-10', '2023-07-11', '2023-07-12', '2023-07-13', '2023-07-14', '2023-07-17', '2023-07-18', '2023-07-19', '2023-07-20', '2023-07-21', '2023-07-24', '2023-07-25', '2023-07-26', '2023-07-27', '2023-07-28', '2023-07-31', '2023-08-01', '2023-08-02', '2023-08-03', '2023-08-04', '2023-08-07', '2023-08-08', '2023-08-09', '2023-08-10', '2023-08-11', '2023-08-14', '2023-08-15', '2023-08-16', '2023-08-17', '2023-08-18', '2023-08-21', '2023-08-22', '2023-08-23', '2023-08-24', '2023-08-25', '2023-08-28', '2023-08-29', '2023-08-30', '2023-08-31', '2023-09-01', '2023-09-04', '2023-09-05', '2023-09-06', '2023-09-07', '2023-09-08', '2023-09-11', '2023-09-12', '2023-09-13', '2023-09-14', '2023-09-15', '2023-09-18', '2023-09-19', '2023-09-20', '2023-09-21', '2023-09-22', '2023-09-25', '2023-09-26', '2023-09-27', '2023-09-28', '2023-10-09', '2023-10-10', '2023-10-11', '2023-10-12', '2023-10-13', '2023-10-16', '2023-10-17', '2023-10-18', '2023-10-19', '2023-10-20', '2023-10-23', '2023-10-24', '2023-10-25', '2023-10-26', '2023-10-27', '2023-10-30', '2023-10-31', '2023-11-01', '2023-11-02', '2023-11-03', '2023-11-06', '2023-11-07', '2023-11-08', '2023-11-09', '2023-11-10', '2023-11-13', '2023-11-14', '2023-11-15', '2023-11-16', '2023-11-17', '2023-11-20', '2023-11-21', '2023-11-22', '2023-11-23', '2023-11-24', '2023-11-27', '2023-11-28', '2023-11-29', '2023-11-30', '2023-12-01', '2023-12-04', '2023-12-05', '2023-12-06', '2023-12-07', '2023-12-08', '2023-12-11', '2023-12-12', '2023-12-13', '2023-12-14', '2023-12-15', '2023-12-18', '2023-12-19', '2023-12-20', '2023-12-21', '2023-12-22', '2023-12-25', '2023-12-26', '2023-12-27', '2023-12-28', '2023-12-29'] 13 | 14 | # 从东方财富通代码转到掘金代码 15 | def getSymbolFromEastMoney(code): 16 | ret = '' 17 | prec = code[0:2] 18 | if prec == '60': 19 | ret = '{0}.{1}'.format('SHSE', code) 20 | elif prec == '30' or prec == '00': 21 | ret = '{0}.{1}'.format('SZSE', code) 22 | return ret 23 | 24 | # 从复合股票代码分割出股票代码和市场代码 25 | def getSymbol(code,pos): 26 | ret='' 27 | if pos==0: 28 | ret = code.split('.')[pos] 29 | if ret == 'SHSE': 30 | ret = 'sh' 31 | else: 32 | ret = 'sz' 33 | elif pos==1: 34 | ret = code.split('.')[pos] 35 | return ret 36 | # pass 37 | 38 | # 拼奏掘金格式股票代码包括市场代码 39 | def getGmStyleSymbol(code,market): 40 | pref='SHSE' 41 | if market=='sz': 42 | pref='SZSE' 43 | return '{0}.{1}'.format(pref,code ) 44 | 45 | # 讯投代码转掘金 46 | def xtToGmSymbol(code): 47 | symbol=code.split('.')[0] 48 | symbol=getGmSymbol(symbol) 49 | return symbol 50 | 51 | # 掘金代码转讯投 52 | def GmToXtSymbol(code): 53 | symbol=code.split('.')[1] 54 | symbol=getXtSymbol(symbol) 55 | return symbol 56 | 57 | # 拼奏掘金格式股票代码包括市场代码 58 | def getGmSymbol(code): 59 | if len(code)<6: 60 | code= code.zfill(6) 61 | ret = '' 62 | prec = code[0:1] 63 | if prec == '6' or prec == '5': 64 | ret = '{0}.{1}'.format('SHSE', code) 65 | # elif prec == '30' or prec == '00': 66 | else: 67 | ret = '{0}.{1}'.format('SZSE', code) 68 | return ret 69 | 70 | # 拼奏掘金格式股票代码包括市场代码 71 | def getXtSymbol(code): 72 | if len(code)<6: 73 | code= code.zfill(6) 74 | ret = '' 75 | prec = code[0:1] 76 | if prec == '6': 77 | ret = '{0}.{1}'.format(code,'SH') 78 | # elif prec == '30' or prec == '00': 79 | else: 80 | ret = '{0}.{1}'.format(code,'SZ') 81 | return ret 82 | # 传入普通代码数组生成掘金代码字符串 83 | def getGmSymbolStr(codes): 84 | ret='' 85 | i=0 86 | for code in codes: 87 | if i>0: 88 | ret=ret+',' 89 | ret=ret+getGmSymbol(code) 90 | i=i+1 91 | return ret 92 | 93 | # 删除股票代码前缀 94 | def delSymbolPrefix(symbol): 95 | ret=symbol 96 | if symbol.find('.')>=0: 97 | ret=symbol.split('.')[1] 98 | return ret 99 | 100 | # 从股票代码获取市场代码{适用东方财富网获取数据} 101 | def getMarketFromGmSymbol(code): 102 | pref=code.split('.')[0] 103 | ret=0 104 | if pref=="SHSE": 105 | ret=1 106 | elif pref=="SZSE": 107 | ret=0 108 | return ret 109 | 110 | # 掘金格式转普通格式 111 | def gmToNormalCode(symbols): 112 | ret=[] 113 | for s in symbols: 114 | ret.append(delSymbolPrefix(s)) 115 | return ret 116 | # 拼奏掘金格式股票代码包括市场代码 117 | def getGmToEastmoneySymbol(code): 118 | code=code.replace('SHSE',"1") 119 | code = code.replace('SZSE', "0") 120 | return code 121 | 122 | # 拼奏富途格式股票代码包括市场代码 123 | def getFutuSymbol(code): 124 | if len(code)<6: 125 | code= code.zfill(6) 126 | ret = '' 127 | prec = code[0:1] 128 | if prec == '6': 129 | ret = '{0}.{1}'.format('SH', code) 130 | # elif prec == '30' or prec == '00': 131 | else: 132 | ret = '{0}.{1}'.format('SZ', code) 133 | return ret 134 | 135 | # 拼奏富途格式股票代码包括市场代码 136 | def getFutuSymbols(codes): 137 | ret=[] 138 | for i, v in codes.items(): 139 | ret.append(getFutuSymbol(v)) 140 | return ret 141 | 142 | # 判断是否交易日 143 | def isTradeDate(date): 144 | ret=False 145 | if date in trade_dates: 146 | ret=True 147 | return ret 148 | 149 | # 获取最近的交易日期 150 | def getLatelyTradeDate(): 151 | dates = pd.Series(trade_dates) 152 | df= pd.DataFrame(dates,columns=['date']) 153 | df['date']=pd.to_datetime(df['date']) 154 | df=df.loc[df['date']<=datetime.now(),:] 155 | date=df.at[len(df)-1,'date'] 156 | return date 157 | 158 | #获取上一交易日 159 | def get_last_trade_date(): 160 | dates = pd.Series(trade_dates) 161 | df= pd.DataFrame(dates,columns=['date']) 162 | df['date']=pd.to_datetime(df['date']) 163 | now=datetime.strptime(datetime.now().strftime('%Y-%m-%d'), '%Y-%m-%d') 164 | df=df.loc[df['date']=begin) & (df['date']<=end),:] 203 | return df 204 | 205 | # 获取报表日期 206 | def get_reporter_date(): 207 | rpt_dates=["2023-12-31","2023-09-30","2023-06-30","2023-03-31","2022-12-31","2022-09-30","2022-06-30","2022-03-31","2021-12-31","2021-09-30","2021-06-30","2021-03-31"] 208 | dates = pd.Series(rpt_dates) 209 | df = pd.DataFrame(dates, columns=['date']) 210 | df['date'] = pd.to_datetime(df['date']) 211 | now = datetime.strptime(datetime.now().strftime('%Y-%m-%d'), '%Y-%m-%d') 212 | df = df.loc[df['date'] < now, :] 213 | df.reset_index(drop=True,inplace=True) 214 | date = df.at[0, 'date'] 215 | return date.strftime('%Y-%m-%d') 216 | # date=GmApi().get_last_trade_date() 217 | # return pd.to_datetime(date) 218 | # loginfo(date) 219 | 220 | def value_to_volume(value,price): 221 | volume = int(value / price) 222 | volume = numberutil.get_neat(volume) 223 | return volume 224 | 225 | if __name__=='__main__': 226 | if isTradeDate('2023-01-08'): 227 | print('yes') 228 | else: 229 | print('no') 230 | # getLatelyTradeDate() 231 | # date=get_reporter_date() 232 | # code=gmToNormalCode(['SHSE.600031','SHSE.600032','SZSE.000031']) 233 | # loginfo(code) 234 | # df=get_period_trade_date('2021-10-19','2022-02-22') 235 | # date= get_date_pren_trade(5) 236 | # code=GmToXtSymbol("SZSE.600222") 237 | # print(code) 238 | date=get_date_pren_trade(20) 239 | print(date) -------------------------------------------------------------------------------- /quant/util/stringutil.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import ast 3 | import re 4 | 5 | # 数组转字符串 6 | def array_to_string(array): 7 | ret='' 8 | i=0 9 | for s in array: 10 | if i>0: 11 | ret=ret+',' 12 | ret=ret+str(s) 13 | i=i+1 14 | return ret 15 | 16 | # 字符串转数组 17 | 18 | # arr = [1,2,3,4,5,6] 19 | # #求方差 20 | # arr_var = np.var(arr) 21 | # #求标准差 22 | # arr_std = np.std(arr,ddof=1) 23 | # print("方差为:%f" % arr_var) 24 | # print("标准差为:%f" % arr_std) 25 | 26 | def string_to_nparray(text): 27 | # 将,替换为空格 28 | text = text.replace(",", " ") 29 | # 去除换行 30 | text = text.replace('\n', '') 31 | text = re.sub('\[\s+', '[', text) 32 | text = re.sub('\s+]', ']', text) 33 | # 添加 ',' 34 | xs = re.sub('\s+', ',', text) 35 | # 转换回numpy.array 36 | return np.array(ast.literal_eval(xs)) 37 | 38 | 39 | if __name__=='__main__': 40 | a= string_to_nparray('[ 16.6206 16.8443 16.9422 16.9282 16.7814 16.3409 16.0193 15.8375 16.9213 16.6486 16.3619 16.1451 16.1941 16.25 17.194 16.6835 16.6556 16.1032 16.1171 ]') 41 | print(a) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy==1.22.0 2 | gm==3.0.159 3 | matplotlib==3.5.1 4 | statsmodels==0.13.1 5 | scipy==1.7.3 6 | requests==2.27.0 7 | demjson==1.6 8 | mysql-connector==2.2.9 9 | typing==3.7.4.3 10 | mpl_finance==0.10.1 11 | SQLAlchemy==1.4.27 12 | psutil 13 | urllib3==1.26.4 14 | mplfinance==0.12.9b1 15 | pytdx==1.72 16 | --------------------------------------------------------------------------------