├── .gitignore ├── LICENSE ├── Logo ├── setting1.png ├── setting2.png ├── setting3.png ├── setting4.png ├── setting5.png ├── setting6.png ├── setting7.png └── trading.png ├── PyAutoTrading.pyw ├── README.md ├── pyautotrading.ini └── winguiauto.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /Logo/setting1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesley1001/pyautotrade_tdx-1/5545ce3f55fea626a3e056adebbaeb3d98579436/Logo/setting1.png -------------------------------------------------------------------------------- /Logo/setting2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesley1001/pyautotrade_tdx-1/5545ce3f55fea626a3e056adebbaeb3d98579436/Logo/setting2.png -------------------------------------------------------------------------------- /Logo/setting3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesley1001/pyautotrade_tdx-1/5545ce3f55fea626a3e056adebbaeb3d98579436/Logo/setting3.png -------------------------------------------------------------------------------- /Logo/setting4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesley1001/pyautotrade_tdx-1/5545ce3f55fea626a3e056adebbaeb3d98579436/Logo/setting4.png -------------------------------------------------------------------------------- /Logo/setting5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesley1001/pyautotrade_tdx-1/5545ce3f55fea626a3e056adebbaeb3d98579436/Logo/setting5.png -------------------------------------------------------------------------------- /Logo/setting6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesley1001/pyautotrade_tdx-1/5545ce3f55fea626a3e056adebbaeb3d98579436/Logo/setting6.png -------------------------------------------------------------------------------- /Logo/setting7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesley1001/pyautotrade_tdx-1/5545ce3f55fea626a3e056adebbaeb3d98579436/Logo/setting7.png -------------------------------------------------------------------------------- /Logo/trading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesley1001/pyautotrade_tdx-1/5545ce3f55fea626a3e056adebbaeb3d98579436/Logo/trading.png -------------------------------------------------------------------------------- /PyAutoTrading.pyw: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf8 -*- 2 | # QQ群: 486224275 3 | __author__ = '人在江湖' 4 | 5 | 6 | import tkinter.messagebox 7 | from tkinter import * 8 | from tkinter.ttk import * 9 | import datetime 10 | import threading 11 | import pickle 12 | import configparser 13 | import time 14 | import tushare as ts 15 | from winguiauto import * 16 | 17 | is_start = False 18 | is_monitor = True 19 | set_stock_info = [] 20 | order_msg = [] 21 | actual_stock_info = [] 22 | is_ordered = [1] * 5 # 1:未下单 0:已下单 23 | 24 | 25 | def getConfigData(): 26 | ''' 27 | 读取配置文件参数 28 | :return:双向委托界面下,控件的数量 29 | ''' 30 | cp = configparser.ConfigParser() 31 | cp.read('pyautotrading.ini') 32 | numChildWindows = cp.getint('tradeVersion', 'numChildWindows') 33 | return numChildWindows 34 | 35 | 36 | # def pickHwndOfControls(top_hwnd, num_child_windows): 37 | # cleaned_hwnd_controls = [] 38 | # hwnd_controls = findSpecifiedWindows(top_hwnd, num_child_windows) 39 | # for Hwnd, text_name, class_name in hwnd_controls: 40 | # if class_name in ('Button', 'Edit'): 41 | # cleaned_hwnd_controls.append((Hwnd, text_name, class_name)) 42 | # return cleaned_hwnd_controls 43 | 44 | 45 | def getRunningMoney(sub_hwnds): 46 | ''' 47 | :param sub_hwnds: 双向委托操作界面下的控件句柄列表 48 | :return:可用资金 49 | ''' 50 | return getWindowText(sub_hwnds[12][1]) 51 | 52 | 53 | def buy(sub_hwnds, code, stop_price, quantity): 54 | ''' 55 | 买函数,自动填写3个Edit控件,及点击买入按钮。 56 | :param sub_hwnds: 双向委托操作界面下的控件句柄列表 57 | :param code: 股票代码,字符串 58 | :param stop_price: 涨停市价, 字符串 59 | :param quantity: 买入股票 数量,字符串 60 | :return: 61 | ''' 62 | setEditText(sub_hwnds[0][0], code) 63 | setEditText(sub_hwnds[1][0], stop_price) 64 | setEditText(sub_hwnds[3][0], quantity) 65 | time.sleep(0.3) 66 | click(sub_hwnds[5][0]) 67 | time.sleep(0.3) 68 | 69 | 70 | def sell(sub_hwnds, code, stop_price, quantity): 71 | ''' 72 | 买函数,自动填写3个Edit控件,及点击卖出按钮。 73 | :param sub_hwnds: 双向委托操作界面下的控件句柄列表 74 | :param code: 股票代码,字符串 75 | :param stop_price: 涨停市价, 字符串 76 | :param quantity: 卖出股票 数量,字符串 77 | :return: 78 | ''' 79 | setEditText(sub_hwnds[24][0], code) 80 | setEditText(sub_hwnds[25][0], stop_price) 81 | setEditText(sub_hwnds[27][0], quantity) 82 | time.sleep(0.3) 83 | click(sub_hwnds[29][0]) 84 | time.sleep(0.3) 85 | 86 | 87 | def order(top_hwnd, sub_hwnds, code, stop_prices, quantity, direction): 88 | ''' 89 | 买卖函数 90 | :param top_hwnd: 顶层窗口句柄 91 | :param sub_hwnds: 双向委托操作界面下的控件句柄列表 92 | :param code: 股票代码, 字符串 93 | :param stop_prices: 涨跌停价格,字符串 94 | :param quantity: 买卖数量,字符串 95 | :param direction: 交易方向,字符串 96 | :return: 97 | ''' 98 | if direction == 'B': 99 | buy(sub_hwnds, code, stop_prices[0], quantity) 100 | if direction == 'S': 101 | sell(sub_hwnds, code, stop_prices[1], quantity) 102 | 103 | 104 | def tradingInit(): 105 | ''' 106 | 获得交易软件句柄 107 | :return:顶层窗口句柄,双向委托操作界面下的控件句柄列表 108 | ''' 109 | top_hwnd = findSpecifiedTopWindow(wantedClass='TdxW_MainFrame_Class') 110 | if top_hwnd == 0: 111 | tkinter.messagebox.showerror('错误', '请先打开交易软件,再运行本软件') 112 | return top_hwnd, [] 113 | else: 114 | sub_hwnds = findSpecifiedWindows(top_hwnd, getConfigData()) 115 | return top_hwnd, sub_hwnds 116 | 117 | 118 | def pickCodeFromItems(items_info): 119 | ''' 120 | 提取股票代码 121 | :param items_info: UI下各项输入信息 122 | :return:股票代码列表 123 | ''' 124 | stock_codes = [] 125 | for item in items_info: 126 | stock_codes.append(item[0]) 127 | return stock_codes 128 | 129 | 130 | def getStockData(items_info): 131 | ''' 132 | 获取股票实时数据 133 | :param items_info:UI下各项输入信息 134 | :return:股票实时数据 135 | ''' 136 | code_name_price = [] 137 | stock_codes = pickCodeFromItems(items_info) 138 | try: 139 | df = ts.get_realtime_quotes(stock_codes) 140 | df_len = len(df) 141 | for stock_code in stock_codes: 142 | is_found = False 143 | for i in range(df_len): 144 | actual_code = df['code'][i] 145 | if stock_code == actual_code: 146 | actual_name = df['name'][i] 147 | pre_close = float(df['pre_close'][i]) 148 | if 'ST' in actual_name: 149 | highest = str(round(pre_close * 1.05, 2)) 150 | lowest = str(round(pre_close * 0.95, 2)) 151 | code_name_price.append((actual_code, actual_name, df['price'][i], (highest, lowest))) 152 | else: 153 | highest = str(round(pre_close * 1.1, 2)) 154 | lowest = str(round(pre_close * 0.9, 2)) 155 | code_name_price.append((actual_code, actual_name, df['price'][i], (highest, lowest))) 156 | is_found = True 157 | break 158 | if is_found is False: 159 | code_name_price.append(('', '', '', ('', ''))) 160 | except: 161 | code_name_price = [('', '', '', ('', ''))] * 5 # 网络不行,返回空 162 | return code_name_price 163 | 164 | 165 | def monitor(): 166 | ''' 167 | 实时监控函数 168 | :return: 169 | ''' 170 | global actual_stock_info, order_msg, is_ordered, set_stock_info 171 | count = 0 172 | top_hwnd, sub_hwnds = tradingInit() 173 | # 如果top_hwnd为零,直接终止循环 174 | while is_monitor and top_hwnd: 175 | if count % 100 == 0: 176 | # clickButton(sub_hwnd[12][0]) # 点击刷新按钮 177 | time.sleep(1) 178 | time.sleep(3) 179 | count += 1 180 | if is_start: 181 | actual_stock_info = getStockData(set_stock_info) 182 | # print('actual_stock_info', actual_stock_info) 183 | for row, (actual_code, actual_name, actual_price, stop_prices) in enumerate(actual_stock_info): 184 | if is_start and actual_code and is_ordered[row] == 1 \ 185 | and set_stock_info[row][1] and set_stock_info[row][2] > 0 \ 186 | and set_stock_info[row][3] and set_stock_info[row][4] \ 187 | and datetime.datetime.now().time() > set_stock_info[row][5]: 188 | if is_start and set_stock_info[row][1] == '>' and float(actual_price) > set_stock_info[row][2]: 189 | dt = datetime.datetime.now() 190 | order(top_hwnd, sub_hwnds, actual_code, stop_prices, 191 | set_stock_info[row][4], set_stock_info[row][3]) 192 | closePopupWindows(top_hwnd) 193 | order_msg.append( 194 | (dt.strftime('%x'), dt.strftime('%X'), actual_code, 195 | actual_name, set_stock_info[row][3], 196 | actual_price, set_stock_info[row][4], '已下单')) 197 | is_ordered[row] = 0 198 | 199 | if is_start and set_stock_info[row][1] == '<' and float(actual_price) < set_stock_info[row][2]: 200 | dt = datetime.datetime.now() 201 | order(top_hwnd, sub_hwnds, actual_code, stop_prices, 202 | set_stock_info[row][4], set_stock_info[row][3]) 203 | closePopupWindows(top_hwnd) 204 | order_msg.append( 205 | (dt.strftime('%x'), dt.strftime('%X'), actual_code, 206 | actual_name, set_stock_info[row][3], 207 | actual_price, set_stock_info[row][4], '已下单')) 208 | is_ordered[row] = 0 209 | 210 | 211 | class StockGui: 212 | def __init__(self): 213 | self.window = Tk() 214 | self.window.title("自动化股票交易") 215 | self.window.resizable(0, 0) 216 | 217 | frame1 = Frame(self.window) 218 | frame1.pack(padx=10, pady=10) 219 | 220 | Label(frame1, text="股票代码", width=8, justify=CENTER).grid( 221 | row=1, column=1, padx=5, pady=5) 222 | Label(frame1, text="股票名称", width=8, justify=CENTER).grid( 223 | row=1, column=2, padx=5, pady=5) 224 | Label(frame1, text="当前价格", width=8, justify=CENTER).grid( 225 | row=1, column=3, padx=5, pady=5) 226 | Label(frame1, text="关系", width=4, justify=CENTER).grid( 227 | row=1, column=4, padx=5, pady=5) 228 | Label(frame1, text="价格", width=8, justify=CENTER).grid( 229 | row=1, column=5, padx=5, pady=5) 230 | Label(frame1, text="方向", width=4, justify=CENTER).grid( 231 | row=1, column=6, padx=5, pady=5) 232 | Label(frame1, text="数量", width=8, justify=CENTER).grid( 233 | row=1, column=7, padx=5, pady=5) 234 | Label(frame1, text="时间可选", width=8, justify=CENTER).grid( 235 | row=1, column=8, padx=5, pady=5) 236 | Label(frame1, text="状态", width=4, justify=CENTER).grid( 237 | row=1, column=9, padx=5, pady=5) 238 | 239 | self.rows = 5 240 | self.cols = 9 241 | 242 | self.variable = [] 243 | for row in range(self.rows): 244 | self.variable.append([]) 245 | for col in range(self.cols): 246 | temp = StringVar() 247 | self.variable[row].append(temp) 248 | 249 | for row in range(self.rows): 250 | Entry(frame1, textvariable=self.variable[row][0], 251 | width=8).grid(row=row + 2, column=1, padx=5, pady=5) 252 | Entry(frame1, textvariable=self.variable[row][1], state=DISABLED, 253 | width=8).grid(row=row + 2, column=2, padx=5, pady=5) 254 | Entry(frame1, textvariable=self.variable[row][2], state=DISABLED, 255 | width=8).grid(row=row + 2, column=3, padx=5, pady=5) 256 | Combobox(frame1, values=('<', '>'), textvariable=self.variable[row][3], 257 | width=2).grid(row=row + 2, column=4, padx=5, pady=5) 258 | Spinbox(frame1, from_=0, to=1000, textvariable=self.variable[row][4], 259 | increment=0.01, width=6).grid(row=row + 2, column=5, padx=5, pady=5) 260 | Combobox(frame1, values=('B', 'S'), textvariable=self.variable[row][5], 261 | width=2).grid(row=row + 2, column=6, padx=5, pady=5) 262 | Spinbox(frame1, from_=0, to=100000, textvariable=self.variable[row][6], 263 | increment=100, width=6).grid(row=row + 2, column=7, padx=5, pady=5) 264 | Entry(frame1, textvariable=self.variable[row][7], 265 | width=8).grid(row=row + 2, column=8, padx=5, pady=5) 266 | Entry(frame1, textvariable=self.variable[row][8], state=DISABLED, 267 | width=5).grid(row=row + 2, column=9, padx=5, pady=5) 268 | 269 | frame3 = Frame(self.window) 270 | frame3.pack(padx=10, pady=10) 271 | self.start_bt = Button(frame3, text="开始", command=self.start) 272 | self.start_bt.pack(side=LEFT) 273 | self.set_bt = Button(frame3, text='重置买卖', command=self.setFlags) 274 | self.set_bt.pack(side=LEFT) 275 | Button(frame3, text="历史记录", command=self.displayHisRecords).pack(side=LEFT) 276 | Button(frame3, text='保存', command=self.save).pack(side=LEFT) 277 | self.load_bt = Button(frame3, text='载入', command=self.load) 278 | self.load_bt.pack(side=LEFT) 279 | 280 | self.window.protocol(name="WM_DELETE_WINDOW", func=self.close) 281 | self.window.after(100, self.updateControls) 282 | self.window.mainloop() 283 | 284 | def displayHisRecords(self): 285 | ''' 286 | 显示历史信息 287 | :return: 288 | ''' 289 | global order_msg 290 | tp = Toplevel() 291 | tp.title('历史记录') 292 | tp.resizable(0, 1) 293 | scrollbar = Scrollbar(tp) 294 | scrollbar.pack(side=RIGHT, fill=Y) 295 | col_name = ['日期', '时间', '证券代码', '证券名称', '方向', '价格', '数量', '备注'] 296 | tree = Treeview( 297 | tp, show='headings', columns=col_name, height=30, yscrollcommand=scrollbar.set) 298 | tree.pack(expand=1, fill=Y) 299 | scrollbar.config(command=tree.yview) 300 | for name in col_name: 301 | tree.heading(name, text=name) 302 | tree.column(name, width=70, anchor=CENTER) 303 | 304 | for msg in order_msg: 305 | tree.insert('', 0, values=msg) 306 | 307 | def save(self): 308 | ''' 309 | 保存设置 310 | :return: 311 | ''' 312 | global set_stock_info, order_msg, actual_stock_info 313 | self.getItems() 314 | with open('stockInfo.dat', 'wb') as fp: 315 | pickle.dump(set_stock_info, fp) 316 | # pickle.dump(actual_stock_info, fp) 317 | pickle.dump(order_msg, fp) 318 | 319 | def load(self): 320 | ''' 321 | 载入设置 322 | :return: 323 | ''' 324 | global set_stock_info, order_msg, actual_stock_info 325 | with open('stockInfo.dat', 'rb') as fp: 326 | set_stock_info = pickle.load(fp) 327 | # actual_stock_info = pickle.load(fp) 328 | order_msg = pickle.load(fp) 329 | for row in range(self.rows): 330 | for col in range(self.cols): 331 | if col == 0: 332 | self.variable[row][col].set(set_stock_info[row][0]) 333 | elif col == 3: 334 | self.variable[row][col].set(set_stock_info[row][1]) 335 | elif col == 4: 336 | self.variable[row][col].set(set_stock_info[row][2]) 337 | elif col == 5: 338 | self.variable[row][col].set(set_stock_info[row][3]) 339 | elif col == 6: 340 | self.variable[row][col].set(set_stock_info[row][4]) 341 | elif col == 7: 342 | temp = set_stock_info[row][5].strftime('%X') 343 | if temp == '01:00:00': 344 | self.variable[row][col].set('') 345 | else: 346 | self.variable[row][col].set(temp) 347 | 348 | def setFlags(self): 349 | ''' 350 | 重置买卖标志 351 | :return: 352 | ''' 353 | global is_start, is_ordered 354 | if is_start is False: 355 | is_ordered = [1] * 5 356 | 357 | def updateControls(self): 358 | ''' 359 | 实时股票名称、价格、状态信息 360 | :return: 361 | ''' 362 | global set_stock_info, actual_stock_info, is_start 363 | if is_start: 364 | print('actual_stock_info', actual_stock_info) 365 | for row, (actual_code, actual_name, actual_price, _) in enumerate(actual_stock_info): 366 | self.variable[row][1].set(actual_name) 367 | self.variable[row][2].set(str(actual_price)) 368 | if actual_code: 369 | if is_ordered[row] == 1: 370 | self.variable[row][8].set('监控中') 371 | elif is_ordered[row] == 0: 372 | self.variable[row][8].set('已下单') 373 | else: 374 | self.variable[row][8].set('') 375 | 376 | self.window.after(3000, self.updateControls) 377 | 378 | def start(self): 379 | ''' 380 | 启动停止 381 | :return: 382 | ''' 383 | global is_start 384 | if is_start is False: 385 | is_start = True 386 | else: 387 | is_start = False 388 | 389 | if is_start: 390 | self.getItems() 391 | # print(set_stock_info) 392 | self.start_bt['text'] = '停止' 393 | self.set_bt['state'] = DISABLED 394 | self.load_bt['state'] = DISABLED 395 | else: 396 | self.start_bt['text'] = '开始' 397 | self.set_bt['state'] = NORMAL 398 | self.load_bt['state'] = NORMAL 399 | 400 | def close(self): 401 | ''' 402 | 关闭程序时,停止monitor线程 403 | :return: 404 | ''' 405 | global is_monitor 406 | is_monitor = False 407 | self.window.quit() 408 | 409 | def getItems(self): 410 | ''' 411 | 获取UI上用户输入的各项数据, 412 | ''' 413 | global set_stock_info 414 | set_stock_info = [] 415 | 416 | # 获取买卖价格数量输入项等 417 | for row in range(self.rows): 418 | set_stock_info.append([]) 419 | for col in range(self.cols): 420 | temp = self.variable[row][col].get().strip() 421 | if col == 0: 422 | if len(temp) == 6 and temp.isdigit(): # 判断股票代码是否为6位数 423 | set_stock_info[row].append(temp) 424 | else: 425 | set_stock_info[row].append('') 426 | elif col == 3: 427 | if temp in ('>', '<'): 428 | set_stock_info[row].append(temp) 429 | else: 430 | set_stock_info[row].append('') 431 | elif col == 4: 432 | try: 433 | price = float(temp) 434 | if price > 0: 435 | set_stock_info[row].append(price) # 把价格转为数字 436 | else: 437 | set_stock_info[row].append(0) 438 | except ValueError: 439 | set_stock_info[row].append(0) 440 | elif col == 5: 441 | if temp in ('B', 'S'): 442 | set_stock_info[row].append(temp) 443 | else: 444 | set_stock_info[row].append('') 445 | elif col == 6: 446 | if temp.isdigit() and int(temp) >= 100: 447 | set_stock_info[row].append(str(int(temp) // 100 * 100)) 448 | else: 449 | set_stock_info[row].append('') 450 | elif col == 7: 451 | try: 452 | set_stock_info[row].append(datetime.datetime.strptime(temp, '%H:%M:%S').time()) 453 | except ValueError: 454 | set_stock_info[row].append(datetime.datetime.strptime('1:00:00', '%H:%M:%S').time()) 455 | 456 | 457 | if __name__ == '__main__': 458 | t1 = threading.Thread(target=StockGui) 459 | t2 = threading.Thread(target=monitor) 460 | t1.start() 461 | t2.start() 462 | t1.join() 463 | t2.join() 464 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyAutoTrading 2 | 股票交易软件辅助工具 3 | 4 | ## 简介 5 | 用于华泰证券通达信版(须有`双向委托`功能)。软件可以一次监控5只股票,根据条件下单。每次下单耗时小于1s,目前软件只能知道委托是否成功。如果有疑问,或是建议,可以发邮件联系。QQ群:486224275。 6 | 7 | ## 注意事项 8 | * 开发环境是win10 64bit, python3 64bit、pywin32、tushare。 以前是用python 32bit开发的,现在好像python 64bit的也能用。 9 | * 软件共有4个文件,pyautotrading.ini配置文件,PyAutoTrading.pyw主程序,stockInfo.dat存盘文件,winguiauto.py是封装的winapi函数。 10 | * 交易软件启动后,按F6,进入双向委托界面,启动本程序后,不要再切换到其它界面。切换到其他界面后即使再切换回来,有些情况会导致不能正常获取句柄。 11 | * 程序启动有点慢,初始化工作比较多,python的多线程问题。 12 | * 不写时间条件单,默认时间为凌晨1点。如果只想要时间条件单而忽略价格条件单,可以写个始终满足条件的价格。其它地方不写,这行将不做处理。 13 | * 股票数量最好为100的倍数,小于100股的不会交易,大于100、非整数倍的将取整, 比如150股将作为100股。 14 | * 时间为24小时制,形式为 “时:分:秒”, 每项都必须写, 后面的写法是错误的: “13:30” 。 15 | 16 | ## 版本 17 | * v 0.01 修正了股票价格实时显示问题。 18 | * v 0.02 重构了交易软件接口,目前在最小化状态下也可以下单,下单速度加快,增加委托日志。 19 | * v 0.03 重新布局了控件,修改委托日志控件。修复了少许Bug。 20 | * v 0.04 重新布局了控件,重构了monitor函数。现在一次可以下4个条件单。 21 | * v 0.05 加入时间条件单。 22 | * v 0.06 交易软件接口函数单独放winguiauto文件。 23 | * v 0.07 时间条件单和价格条件单相结合,添加保存和载入功能,存档和主文件在同一目录下,名为stockInfo.dat,是个二进制文件。 24 | * v 0.08 代码清理,添加了注释。现在可以同时监控5只股票。 25 | * v 0.09 增加配置文件pyautotrading.ini。加入自动刷新功能,每隔5分钟刷新一次,防止软件进入待机状态。 26 | * v0.10 修改了几个bug,买卖价格改由python计算,加快了下单速度(1.5s),稳定性增加了不少。需更改交易软件设置,请看图。 27 | 28 | 29 | ----------------------------------- 30 | ![image](https://github.com/drongh/pyautotrade_tdx/raw/master/Logo/setting1.png) 31 | ![image](https://github.com/drongh/pyautotrade_tdx/raw/master/Logo/setting2.png) 32 | ![image](https://github.com/drongh/pyautotrade_tdx/raw/master/Logo/setting3.png) 33 | ![image](https://github.com/drongh/pyautotrade_tdx/raw/master/Logo/setting4.png) 34 | ![image](https://github.com/drongh/pyautotrade_tdx/raw/master/Logo/setting5.png) 35 | ![image](https://github.com/drongh/pyautotrade_tdx/raw/master/Logo/setting6.png) 36 | ![image](https://github.com/drongh/pyautotrade_tdx/raw/master/Logo/trading.png) -------------------------------------------------------------------------------- /pyautotrading.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesley1001/pyautotrade_tdx-1/5545ce3f55fea626a3e056adebbaeb3d98579436/pyautotrading.ini -------------------------------------------------------------------------------- /winguiauto.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf8 -*- 2 | 3 | # Module : winGuiAuto.py 4 | # Synopsis : Windows GUI automation utilities 5 | # Programmer : Simon Brunning - simon@brunningonline.net 6 | # Date : 25 June 2003 7 | # Version : 1.0 pre-alpha 2 8 | # Copyright : Released to the public domain. Provided as-is, with no warranty. 9 | # Notes : Requires Python 2.3, win32all and ctypes 10 | '''Windows GUI automation utilities. 11 | 12 | Until I get around to writing some docs and examples, the tests at the foot of 13 | this module should serve to get you started. 14 | ''' 15 | import time 16 | import struct 17 | import win32api 18 | import win32gui 19 | import win32con 20 | 21 | 22 | def findSpecifiedTopWindow(wantedText=None, wantedClass=None): 23 | ''' 24 | :param wantedText: 标题名字 25 | :param wantedClass: 窗口类名 26 | :return: 返回顶层窗口的句柄 27 | ''' 28 | return win32gui.FindWindow(wantedClass, wantedText) 29 | 30 | 31 | def findPopupWindow(hwnd): 32 | ''' 33 | :param hwnd: 父窗口句柄 34 | :return: 返回弹出式窗口的句柄 35 | ''' 36 | return win32gui.GetWindow(hwnd, win32con.GW_ENABLEDPOPUP) 37 | 38 | 39 | def findTopWindow(wantedText=None, wantedClass=None, selectionFunction=None): 40 | '''Find the hwnd of a top level window. 41 | You can identify windows using captions, classes, a custom selection 42 | function, or any combination of these. (Multiple selection criteria are 43 | ANDed. If this isn't what's wanted, use a selection function.) 44 | 45 | Parameters 46 | ---------- 47 | wantedText 48 | Text which the required window's captions must contain. 49 | wantedClass 50 | Class to which the required window must belong. 51 | selectionFunction 52 | Window selection function. Reference to a function 53 | should be passed here. The function should take hwnd as 54 | an argument, and should return True when passed the 55 | hwnd of a desired window. 56 | 57 | Raises 58 | ------ 59 | WinGuiAutoError 60 | When no window found. 61 | 62 | Usage example:: 63 | 64 | optDialog = findTopWindow(wantedText="Options") 65 | ''' 66 | topWindows = findTopWindows(wantedText, wantedClass, selectionFunction) 67 | if topWindows: 68 | return topWindows[0] 69 | else: 70 | raise WinGuiAutoError("No top level window found for wantedText=" + 71 | repr(wantedText) + 72 | ", wantedClass=" + 73 | repr(wantedClass) + 74 | ", selectionFunction=" + 75 | repr(selectionFunction)) 76 | 77 | 78 | def findTopWindows(wantedText=None, wantedClass=None, selectionFunction=None): 79 | '''Find the hwnd of top level windows. 80 | 81 | You can identify windows using captions, classes, a custom selection 82 | function, or any combination of these. (Multiple selection criteria are 83 | ANDed. If this isn't what's wanted, use a selection function.) 84 | 85 | Parameters 86 | ---------- 87 | wantedText 88 | Text which required windows' captions must contain. 89 | wantedClass 90 | Class to which required windows must belong. 91 | selectionFunction 92 | Window selection function. Reference to a function 93 | should be passed here. The function should take hwnd as 94 | an argument, and should return True when passed the 95 | hwnd of a desired window. 96 | 97 | Returns 98 | ------- 99 | A list containing the window handles of all top level 100 | windows matching the supplied selection criteria. 101 | 102 | Usage example:: 103 | 104 | optDialogs = findTopWindows(wantedText="Options") 105 | ''' 106 | results = [] 107 | topWindows = [] 108 | win32gui.EnumWindows(_windowEnumerationHandler, topWindows) 109 | for hwnd, windowText, windowClass in topWindows: 110 | if wantedText and not _normaliseText(wantedText) in _normaliseText(windowText): 111 | continue 112 | if wantedClass and not windowClass == wantedClass: 113 | continue 114 | if selectionFunction and not selectionFunction(hwnd): 115 | continue 116 | results.append(hwnd) 117 | return results 118 | 119 | 120 | def dumpSpecifiedWindow(hwnd, wantedText=None, wantedClass=None): 121 | ''' 122 | :param hwnd: 父窗口句柄 123 | :param wantedText: 指定子窗口名 124 | :param wantedClass: 指定子窗口类名 125 | :return: 返回父窗口下所有子窗体的句柄 126 | ''' 127 | windows = [] 128 | hwndChild = None 129 | while True: 130 | hwndChild = win32gui.FindWindowEx(hwnd, hwndChild, wantedClass, wantedText) 131 | if hwndChild: 132 | textName = win32gui.GetWindowText(hwndChild) 133 | className = win32gui.GetClassName(hwndChild) 134 | windows.append((hwndChild, textName, className)) 135 | else: 136 | return windows 137 | 138 | def findSpecifiedWindows(top_hwnd, numChildWindows=70): 139 | ''' 140 | 查找某一窗口下指定数量的子窗口 141 | :param top_hwnd: 主窗口句柄 142 | :param numChildWindows: 子窗口数量 143 | :return:子窗口列表,包括子窗口hwnd, title, className 144 | ''' 145 | windows = [] 146 | try: 147 | win32gui.EnumChildWindows(top_hwnd, _windowEnumerationHandler, windows) 148 | except win32gui.error: 149 | # No child windows 150 | return 151 | for window in windows: 152 | childHwnd, windowText, windowClass = window 153 | windowContent = dumpSpecifiedWindow(childHwnd) 154 | if len(windowContent) == numChildWindows: 155 | return windowContent 156 | # 没有指定数量的句柄 157 | return 158 | 159 | def dumpWindow(hwnd): 160 | '''Dump all controls from a window into a nested list 161 | 162 | Useful during development, allowing to you discover the structure of the 163 | contents of a window, showing the text and class of all contained controls. 164 | 165 | Parameters 166 | ---------- 167 | hwnd 168 | The window handle of the top level window to dump. 169 | 170 | Returns 171 | ------- 172 | A nested list of controls. Each entry consists of the 173 | control's hwnd, its text, its class, and its sub-controls, if any. 174 | 175 | Usage example:: 176 | 177 | replaceDialog = findTopWindow(wantedText='Replace') 178 | pprint.pprint(dumpWindow(replaceDialog)) 179 | ''' 180 | windows = [] 181 | try: 182 | win32gui.EnumChildWindows(hwnd, _windowEnumerationHandler, windows) 183 | except win32gui.error: 184 | # No child windows 185 | return 186 | windows = [list(window) for window in windows] 187 | for window in windows: 188 | childHwnd, windowText, windowClass = window 189 | window_content = dumpWindow(childHwnd) 190 | if window_content: 191 | window.append(window_content) 192 | return windows 193 | 194 | 195 | def _closePopupWindow(top_hwnd, wantedText=None, wantedClass=None): 196 | ''' 197 | 关闭一个弹窗。 198 | :param top_hwnd: 主窗口句柄 199 | :param wantedText: 弹出对话框上的按钮文本 200 | :param wantedClass: 弹出对话框上的按钮类名 201 | :return: 如果有弹出式对话框,返回True,否则返回False 202 | ''' 203 | hwnd_popup = findPopupWindow(top_hwnd) 204 | if hwnd_popup: 205 | hwnd_control = findControl(hwnd_popup, wantedText, wantedClass) 206 | clickButton(hwnd_control) 207 | return True 208 | return False 209 | 210 | 211 | def closePopupWindows(top_hwnd): 212 | ''' 213 | 连续关闭多个弹出式对话框,直到没有弹窗 214 | :param top_hwnd: 主窗口句柄 215 | :return: 216 | ''' 217 | while _closePopupWindow(top_hwnd): 218 | time.sleep(0.3) 219 | 220 | 221 | def findControl(topHwnd, 222 | wantedText=None, 223 | wantedClass=None, 224 | selectionFunction=None): 225 | '''Find a control. 226 | 227 | You can identify a control using caption, classe, a custom selection 228 | function, or any combination of these. (Multiple selection criteria are 229 | ANDed. If this isn't what's wanted, use a selection function.) 230 | 231 | Parameters 232 | ---------- 233 | topHwnd 234 | The window handle of the top level window in which the 235 | required controls reside. 236 | wantedText 237 | Text which the required control's captions must contain. 238 | wantedClass 239 | Class to which the required control must belong. 240 | selectionFunction 241 | Control selection function. Reference to a function 242 | should be passed here. The function should take hwnd as 243 | an argument, and should return True when passed the 244 | hwnd of the desired control. 245 | 246 | Returns 247 | ------- 248 | The window handle of the first control matching the 249 | supplied selection criteria. 250 | 251 | Raises 252 | ------ 253 | WinGuiAutoError, when no control found. 254 | 255 | Usage example:: 256 | 257 | optDialog = findTopWindow(wantedText="Options") 258 | okButton = findControl(optDialog, 259 | wantedClass="Button", 260 | wantedText="OK") 261 | ''' 262 | controls = findControls(topHwnd, 263 | wantedText=wantedText, 264 | wantedClass=wantedClass, 265 | selectionFunction=selectionFunction) 266 | if controls: 267 | return controls[0] 268 | else: 269 | raise WinGuiAutoError("No control found for topHwnd=" + 270 | repr(topHwnd) + 271 | ", wantedText=" + 272 | repr(wantedText) + 273 | ", wantedClass=" + 274 | repr(wantedClass) + 275 | ", selectionFunction=" + 276 | repr(selectionFunction)) 277 | 278 | 279 | def findControls(topHwnd, 280 | wantedText=None, 281 | wantedClass=None, 282 | selectionFunction=None): 283 | '''Find controls. 284 | 285 | You can identify controls using captions, classes, a custom selection 286 | function, or any combination of these. (Multiple selection criteria are 287 | ANDed. If this isn't what's wanted, use a selection function.) 288 | 289 | Parameters 290 | ---------- 291 | topHwnd 292 | The window handle of the top level window in which the 293 | required controls reside. 294 | wantedText 295 | Text which the required controls' captions must contain. 296 | wantedClass 297 | Class to which the required controls must belong. 298 | selectionFunction 299 | Control selection function. Reference to a function 300 | should be passed here. The function should take hwnd as 301 | an argument, and should return True when passed the 302 | hwnd of a desired control. 303 | 304 | Returns 305 | ------- 306 | The window handles of the controls matching the 307 | supplied selection criteria. 308 | 309 | Usage example:: 310 | 311 | optDialog = findTopWindow(wantedText="Options") 312 | def findButtons(hwnd, windowText, windowClass): 313 | return windowClass == "Button" 314 | buttons = findControl(optDialog, wantedText="Button") 315 | ''' 316 | 317 | def searchChildWindows(currentHwnd): 318 | results = [] 319 | childWindows = [] 320 | try: 321 | win32gui.EnumChildWindows(currentHwnd, 322 | _windowEnumerationHandler, 323 | childWindows) 324 | except win32gui.error: 325 | # This seems to mean that the control *cannot* have child windows, 326 | # i.e. not a container. 327 | return 328 | for childHwnd, windowText, windowClass in childWindows: 329 | descendentMatchingHwnds = searchChildWindows(childHwnd) 330 | if descendentMatchingHwnds: 331 | results += descendentMatchingHwnds 332 | 333 | if wantedText and \ 334 | not _normaliseText(wantedText) in _normaliseText(windowText): 335 | continue 336 | if wantedClass and \ 337 | not windowClass == wantedClass: 338 | continue 339 | if selectionFunction and \ 340 | not selectionFunction(childHwnd): 341 | continue 342 | results.append(childHwnd) 343 | return results 344 | 345 | return searchChildWindows(topHwnd) 346 | 347 | 348 | def clickButton(hwnd): 349 | '''Simulates a single mouse click on a button 350 | 351 | Parameters 352 | ---------- 353 | hwnd 354 | Window handle of the required button. 355 | 356 | Usage example:: 357 | 358 | okButton = findControl(fontDialog, 359 | wantedClass="Button", 360 | wantedText="OK") 361 | clickButton(okButton) 362 | ''' 363 | _sendNotifyMessage(hwnd, win32con.BN_CLICKED) 364 | 365 | 366 | def click(hwnd): 367 | ''' 368 | 模拟鼠标左键单击 369 | :param hwnd: 要单击的控件、窗体句柄 370 | :return: 371 | ''' 372 | win32gui.PostMessage(hwnd, win32con.WM_LBUTTONDOWN, None, None) 373 | time.sleep(.2) 374 | win32gui.PostMessage(hwnd, win32con.WM_LBUTTONUP, None, None) 375 | 376 | 377 | def focusWindow(hwnd): 378 | ''' 379 | 捕捉窗口焦点 380 | :param hwnd: 窗体句柄 381 | :return: 382 | ''' 383 | win32gui.ShowWindow(hwnd, win32con.SW_SHOWMAXIMIZED) 384 | win32gui.SetForegroundWindow(hwnd) 385 | 386 | 387 | def sendKey(hwnd, key_code): 388 | ''' 389 | 模拟按键 390 | :param hwnd: 窗体句柄 391 | :param key_code: 按键码,在win32con下,比如win32con.VK_F1 392 | :return: 393 | ''' 394 | win32gui.PostMessage(hwnd, win32con.WM_KEYDOWN, key_code, 0) # 消息键盘 395 | time.sleep(.2) 396 | win32gui.PostMessage(hwnd, win32con.WM_KEYUP, key_code, 0) 397 | 398 | 399 | def clickStatic(hwnd): 400 | '''Simulates a single mouse click on a static 401 | 402 | Parameters 403 | ---------- 404 | hwnd 405 | Window handle of the required static. 406 | 407 | Usage example: TODO 408 | ''' 409 | _sendNotifyMessage(hwnd, win32con.STN_CLICKED) 410 | 411 | 412 | def doubleClickStatic(hwnd): 413 | '''Simulates a double mouse click on a static 414 | 415 | Parameters 416 | ---------- 417 | hwnd 418 | Window handle of the required static. 419 | 420 | Usage example: TODO 421 | ''' 422 | _sendNotifyMessage(hwnd, win32con.STN_DBLCLK) 423 | 424 | 425 | # def getEditText(hwnd): 426 | # bufLen = win32gui.SendMessage(hwnd, win32con.WM_GETTEXTLENGTH, 0, 0) + 1 427 | # print(bufLen) 428 | # buffer = win32gui.PyMakeBuffer(bufLen) 429 | # win32gui.SendMessage(hwnd, win32con.WM_GETTEXT, bufLen, buffer) 430 | # 431 | # text = buffer[:bufLen] 432 | # return text 433 | 434 | 435 | def getWindowText(hwnd): 436 | return win32gui.GetWindowText(hwnd) 437 | 438 | 439 | def setEditText(hwnd, text): 440 | ''' 441 | 设置Edit控件的文本,这个只能是单行文本 442 | :param hwnd: Edit控件句柄 443 | :param text: 要设置的文本 444 | :return: 445 | ''' 446 | win32gui.SendMessage(hwnd, win32con.WM_SETTEXT, None, text) 447 | 448 | 449 | # def setEditText(hwnd, text, append=False): 450 | # '''Set an edit control's text. 451 | # 452 | # Parameters 453 | # ---------- 454 | # hwnd 455 | # The edit control's hwnd. 456 | # text 457 | # The text to send to the control. This can be a single 458 | # string, or a sequence of strings. If the latter, each will 459 | # be become a a seperate line in the control. 460 | # append 461 | # Should the new text be appended to the existing text? 462 | # Defaults to False, meaning that any existing text will be 463 | # replaced. If True, the new text will be appended to the end 464 | # of the existing text. 465 | # Note that the first line of the new text will be directly 466 | # appended to the end of the last line of the existing text. 467 | # If appending lines of text, you may wish to pass in an 468 | # empty string as the 1st element of the 'text' argument. 469 | # 470 | # Usage example:: 471 | # 472 | # print "Enter various bits of text." 473 | # setEditText(editArea, "Hello, again!") 474 | # time.sleep(.5) 475 | # setEditText(editArea, "You still there?") 476 | # time.sleep(.5) 477 | # setEditText(editArea, ["Here come", "two lines!"]) 478 | # time.sleep(.5) 479 | # 480 | # print "Add some..." 481 | # setEditText(editArea, ["", "And a 3rd one!"], append=True) 482 | # time.sleep(.5) 483 | # ''' 484 | 485 | # Ensure that text is a list 486 | # try: 487 | # text + '' 488 | # text = [text] 489 | # except TypeError: 490 | # pass 491 | # 492 | # # Set the current selection range, depending on append flag 493 | # if append: 494 | # win32gui.SendMessage(hwnd, 495 | # win32con.EM_SETSEL, 496 | # -1, 497 | # 0) 498 | # else: 499 | # win32gui.SendMessage(hwnd, 500 | # win32con.EM_SETSEL, 501 | # 0, 502 | # -1) 503 | # 504 | # # Send the text 505 | # win32gui.SendMessage(hwnd, 506 | # win32con.EM_REPLACESEL, 507 | # True, 508 | # os.linesep.join(text)) 509 | 510 | 511 | def _windowEnumerationHandler(hwnd, resultList): 512 | '''Pass to win32gui.EnumWindows() to generate list of window handle, 513 | window text, window class tuples.''' 514 | resultList.append((hwnd, 515 | win32gui.GetWindowText(hwnd), 516 | win32gui.GetClassName(hwnd))) 517 | 518 | 519 | def _buildWinLong(high, low): 520 | '''Build a windows long parameter from high and low words. 521 | See http://support.microsoft.com/support/kb/articles/q189/1/70.asp 522 | ''' 523 | # return ((high << 16) | low) 524 | return int(struct.unpack('>L', 525 | struct.pack('>2H', 526 | high, 527 | low))[0]) 528 | 529 | 530 | def _sendNotifyMessage(hwnd, nofifyMessage): 531 | '''Send a notify message to a control.''' 532 | win32gui.SendMessage(win32gui.GetParent(hwnd), 533 | win32con.WM_COMMAND, 534 | _buildWinLong(nofifyMessage, 535 | win32api.GetWindowLong(hwnd, 536 | win32con.GWL_ID)), 537 | hwnd) 538 | 539 | 540 | def _normaliseText(controlText): 541 | '''Remove '&' characters, and lower case. 542 | Useful for matching control text.''' 543 | return controlText.lower().replace('&', '') 544 | 545 | 546 | class Bunch(object): 547 | '''See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52308''' 548 | 549 | def __init__(self, **kwds): 550 | self.__dict__.update(kwds) 551 | 552 | def __str__(self): 553 | state = ["%s=%r" % (attribute, value) 554 | for (attribute, value) 555 | in list(self.__dict__.items())] 556 | return '\n'.join(state) 557 | 558 | 559 | class WinGuiAutoError(Exception): 560 | pass 561 | --------------------------------------------------------------------------------