├── .gitignore ├── KLine.png ├── KLineTool.png ├── LICENSE ├── README.md ├── css.qss ├── ctaFunction ├── .visFunction.py.un~ ├── __init__.py ├── calcFunction.py ├── visFunction.py └── visFunction.py~ ├── data.csv ├── datasig.csv ├── func-button ├── klBacktest.py ├── klClearSig.py ├── klHeatmap.py ├── klLoad.py ├── klShowdown.py ├── klShowmain.py └── klSigmode.py ├── json ├── uiKLine_button.json └── uiKLine_input.json ├── uiBasicIO.py ├── uiCrosshair.py ├── uiKLine.py ├── uiKLineTool.py └── 启动界面.cmd /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | -------------------------------------------------------------------------------- /KLine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonnejs/uiKLine/08646956bd1d729c88d5d2617bf0599eb3efb3d1/KLine.png -------------------------------------------------------------------------------- /KLineTool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonnejs/uiKLine/08646956bd1d729c88d5d2617bf0599eb3efb3d1/KLineTool.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # uiKLine 2 | ### 用pyqtgraph做的K线工具,依赖 PyQt4,numpy,pandas,pyqtgraph 3 | ### 点击 [pyqtgraph](http://www.pyqtgraph.org/) 下载安装 4 | ### 运行uiKLine.py,查看K线 5 | 6 | ![](https://raw.githubusercontent.com/moonnejs/uiKLine/master/KLine.png?sanitize=true) 7 | 8 | ### 运行uiKLineTool.py,查看回测K线工具 9 | ![](https://raw.githubusercontent.com/moonnejs/uiKLine/master/KLineTool.png?sanitize=true) 10 | -------------------------------------------------------------------------------- /css.qss: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) <2013-2014> 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | QToolTip 25 | { 26 | border: 1px solid #76797C; 27 | background-color: rgb(90, 102, 117);; 28 | color: white; 29 | padding: 5px; 30 | opacity: 200; 31 | } 32 | 33 | QWidget 34 | { 35 | color: #eff0f1; 36 | background-color: #31363b; 37 | selection-background-color:#3daee9; 38 | selection-color: #eff0f1; 39 | background-clip: border; 40 | border-image: none; 41 | border: 0px transparent black; 42 | outline: 0; 43 | } 44 | 45 | QWidget:item:hover 46 | { 47 | background-color: #3daee9; 48 | color: #eff0f1; 49 | } 50 | 51 | QWidget:item:selected 52 | { 53 | background-color: #3daee9; 54 | } 55 | 56 | QCheckBox 57 | { 58 | spacing: 5px; 59 | outline: none; 60 | color: #eff0f1; 61 | margin-bottom: 2px; 62 | } 63 | 64 | QCheckBox:disabled 65 | { 66 | color: #76797C; 67 | } 68 | 69 | QCheckBox::indicator, 70 | QGroupBox::indicator 71 | { 72 | width: 18px; 73 | height: 18px; 74 | } 75 | QGroupBox::indicator 76 | { 77 | margin-left: 2px; 78 | } 79 | 80 | QCheckBox::indicator:unchecked 81 | { 82 | image: url(:/qss_icons/rc/checkbox_unchecked.png); 83 | } 84 | 85 | QCheckBox::indicator:unchecked:hover, 86 | QCheckBox::indicator:unchecked:focus, 87 | QCheckBox::indicator:unchecked:pressed, 88 | QGroupBox::indicator:unchecked:hover, 89 | QGroupBox::indicator:unchecked:focus, 90 | QGroupBox::indicator:unchecked:pressed 91 | { 92 | border: none; 93 | image: url(:/qss_icons/rc/checkbox_unchecked_focus.png); 94 | } 95 | 96 | QCheckBox::indicator:checked 97 | { 98 | image: url(:/qss_icons/rc/checkbox_checked.png); 99 | } 100 | 101 | QCheckBox::indicator:checked:hover, 102 | QCheckBox::indicator:checked:focus, 103 | QCheckBox::indicator:checked:pressed, 104 | QGroupBox::indicator:checked:hover, 105 | QGroupBox::indicator:checked:focus, 106 | QGroupBox::indicator:checked:pressed 107 | { 108 | border: none; 109 | image: url(:/qss_icons/rc/checkbox_checked_focus.png); 110 | } 111 | 112 | 113 | QCheckBox::indicator:indeterminate 114 | { 115 | image: url(:/qss_icons/rc/checkbox_indeterminate.png); 116 | } 117 | 118 | QCheckBox::indicator:indeterminate:focus, 119 | QCheckBox::indicator:indeterminate:hover, 120 | QCheckBox::indicator:indeterminate:pressed 121 | { 122 | image: url(:/qss_icons/rc/checkbox_indeterminate_focus.png); 123 | } 124 | 125 | QCheckBox::indicator:checked:disabled, 126 | QGroupBox::indicator:checked:disabled 127 | { 128 | image: url(:/qss_icons/rc/checkbox_checked_disabled.png); 129 | } 130 | 131 | QCheckBox::indicator:unchecked:disabled, 132 | QGroupBox::indicator:unchecked:disabled 133 | { 134 | image: url(:/qss_icons/rc/checkbox_unchecked_disabled.png); 135 | } 136 | 137 | QRadioButton 138 | { 139 | spacing: 5px; 140 | outline: none; 141 | color: #eff0f1; 142 | margin-bottom: 2px; 143 | } 144 | 145 | QRadioButton:disabled 146 | { 147 | color: #76797C; 148 | } 149 | QRadioButton::indicator 150 | { 151 | width: 21px; 152 | height: 21px; 153 | } 154 | 155 | QRadioButton::indicator:unchecked 156 | { 157 | image: url(:/qss_icons/rc/radio_unchecked.png); 158 | } 159 | 160 | 161 | QRadioButton::indicator:unchecked:hover, 162 | QRadioButton::indicator:unchecked:focus, 163 | QRadioButton::indicator:unchecked:pressed 164 | { 165 | border: none; 166 | outline: none; 167 | image: url(:/qss_icons/rc/radio_unchecked_focus.png); 168 | } 169 | 170 | QRadioButton::indicator:checked 171 | { 172 | border: none; 173 | outline: none; 174 | image: url(:/qss_icons/rc/radio_checked.png); 175 | } 176 | 177 | QRadioButton::indicator:checked:hover, 178 | QRadioButton::indicator:checked:focus, 179 | QRadioButton::indicator:checked:pressed 180 | { 181 | border: none; 182 | outline: none; 183 | image: url(:/qss_icons/rc/radio_checked_focus.png); 184 | } 185 | 186 | QRadioButton::indicator:checked:disabled 187 | { 188 | outline: none; 189 | image: url(:/qss_icons/rc/radio_checked_disabled.png); 190 | } 191 | 192 | QRadioButton::indicator:unchecked:disabled 193 | { 194 | image: url(:/qss_icons/rc/radio_unchecked_disabled.png); 195 | } 196 | 197 | 198 | QMenuBar 199 | { 200 | background-color: #31363b; 201 | color: #eff0f1; 202 | } 203 | 204 | QMenuBar::item 205 | { 206 | background: transparent; 207 | } 208 | 209 | QMenuBar::item:selected 210 | { 211 | background: transparent; 212 | border: 1px solid #76797C; 213 | } 214 | 215 | QMenuBar::item:pressed 216 | { 217 | border: 1px solid #76797C; 218 | background-color: #3daee9; 219 | color: #eff0f1; 220 | margin-bottom:-1px; 221 | padding-bottom:1px; 222 | } 223 | 224 | QMenu 225 | { 226 | border: 1px solid #76797C; 227 | color: #eff0f1; 228 | margin: 2px; 229 | } 230 | 231 | QMenu::icon 232 | { 233 | margin: 5px; 234 | } 235 | 236 | QMenu::item 237 | { 238 | padding: 5px 30px 5px 30px; 239 | margin-left: 5px; 240 | border: 1px solid transparent; /* reserve space for selection border */ 241 | } 242 | 243 | QMenu::item:selected 244 | { 245 | color: #eff0f1; 246 | } 247 | 248 | QMenu::separator { 249 | height: 2px; 250 | background: lightblue; 251 | margin-left: 10px; 252 | margin-right: 5px; 253 | } 254 | 255 | QMenu::indicator { 256 | width: 18px; 257 | height: 18px; 258 | } 259 | 260 | /* non-exclusive indicator = check box style indicator 261 | (see QActionGroup::setExclusive) */ 262 | QMenu::indicator:non-exclusive:unchecked { 263 | image: url(:/qss_icons/rc/checkbox_unchecked.png); 264 | } 265 | 266 | QMenu::indicator:non-exclusive:unchecked:selected { 267 | image: url(:/qss_icons/rc/checkbox_unchecked_disabled.png); 268 | } 269 | 270 | QMenu::indicator:non-exclusive:checked { 271 | image: url(:/qss_icons/rc/checkbox_checked.png); 272 | } 273 | 274 | QMenu::indicator:non-exclusive:checked:selected { 275 | image: url(:/qss_icons/rc/checkbox_checked_disabled.png); 276 | } 277 | 278 | /* exclusive indicator = radio button style indicator (see QActionGroup::setExclusive) */ 279 | QMenu::indicator:exclusive:unchecked { 280 | image: url(:/qss_icons/rc/radio_unchecked.png); 281 | } 282 | 283 | QMenu::indicator:exclusive:unchecked:selected { 284 | image: url(:/qss_icons/rc/radio_unchecked_disabled.png); 285 | } 286 | 287 | QMenu::indicator:exclusive:checked { 288 | image: url(:/qss_icons/rc/radio_checked.png); 289 | } 290 | 291 | QMenu::indicator:exclusive:checked:selected { 292 | image: url(:/qss_icons/rc/radio_checked_disabled.png); 293 | } 294 | 295 | QMenu::right-arrow { 296 | margin: 5px; 297 | image: url(:/qss_icons/rc/right_arrow.png) 298 | } 299 | 300 | 301 | QWidget:disabled 302 | { 303 | color: #454545; 304 | background-color: #31363b; 305 | } 306 | 307 | QAbstractItemView 308 | { 309 | alternate-background-color: #31363b; 310 | color: #eff0f1; 311 | border: 1px solid 3A3939; 312 | border-radius: 2px; 313 | } 314 | 315 | QWidget:focus, QMenuBar:focus 316 | { 317 | border: 1px solid #3daee9; 318 | } 319 | 320 | QTabWidget:focus, QCheckBox:focus, QRadioButton:focus, QSlider:focus 321 | { 322 | border: none; 323 | } 324 | 325 | QLineEdit 326 | { 327 | background-color: #232629; 328 | padding: 5px; 329 | border-style: solid; 330 | border: 1px solid #76797C; 331 | border-radius: 2px; 332 | color: #eff0f1; 333 | } 334 | 335 | QGroupBox { 336 | border:1px solid #76797C; 337 | border-radius: 2px; 338 | margin-top: 20px; 339 | font-size:18px; 340 | font-weight:bold; 341 | } 342 | 343 | QGroupBox::title { 344 | subcontrol-origin: margin; 345 | subcontrol-position: top center; 346 | padding-left: 10px; 347 | padding-right: 10px; 348 | padding-top: 10px; 349 | font-size:18px; 350 | font-weight:bold; 351 | } 352 | 353 | QAbstractScrollArea 354 | { 355 | border-radius: 2px; 356 | border: 1px solid #76797C; 357 | background-color: transparent; 358 | } 359 | 360 | QScrollBar:horizontal 361 | { 362 | height: 15px; 363 | margin: 3px 15px 3px 15px; 364 | border: 1px transparent #2A2929; 365 | border-radius: 4px; 366 | background-color: #2A2929; 367 | } 368 | 369 | QScrollBar::handle:horizontal 370 | { 371 | background-color: #605F5F; 372 | min-width: 5px; 373 | border-radius: 4px; 374 | } 375 | 376 | QScrollBar::add-line:horizontal 377 | { 378 | margin: 0px 3px 0px 3px; 379 | border-image: url(:/qss_icons/rc/right_arrow_disabled.png); 380 | width: 10px; 381 | height: 10px; 382 | subcontrol-position: right; 383 | subcontrol-origin: margin; 384 | } 385 | 386 | QScrollBar::sub-line:horizontal 387 | { 388 | margin: 0px 3px 0px 3px; 389 | border-image: url(:/qss_icons/rc/left_arrow_disabled.png); 390 | height: 10px; 391 | width: 10px; 392 | subcontrol-position: left; 393 | subcontrol-origin: margin; 394 | } 395 | 396 | QScrollBar::add-line:horizontal:hover,QScrollBar::add-line:horizontal:on 397 | { 398 | border-image: url(:/qss_icons/rc/right_arrow.png); 399 | height: 10px; 400 | width: 10px; 401 | subcontrol-position: right; 402 | subcontrol-origin: margin; 403 | } 404 | 405 | 406 | QScrollBar::sub-line:horizontal:hover, QScrollBar::sub-line:horizontal:on 407 | { 408 | border-image: url(:/qss_icons/rc/left_arrow.png); 409 | height: 10px; 410 | width: 10px; 411 | subcontrol-position: left; 412 | subcontrol-origin: margin; 413 | } 414 | 415 | QScrollBar::up-arrow:horizontal, QScrollBar::down-arrow:horizontal 416 | { 417 | background: none; 418 | } 419 | 420 | 421 | QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal 422 | { 423 | background: none; 424 | } 425 | 426 | QScrollBar:vertical 427 | { 428 | background-color: #2A2929; 429 | width: 15px; 430 | margin: 15px 3px 15px 3px; 431 | border: 1px transparent #2A2929; 432 | border-radius: 4px; 433 | } 434 | 435 | QScrollBar::handle:vertical 436 | { 437 | background-color: #605F5F; 438 | min-height: 5px; 439 | border-radius: 4px; 440 | } 441 | 442 | QScrollBar::sub-line:vertical 443 | { 444 | margin: 3px 0px 3px 0px; 445 | border-image: url(:/qss_icons/rc/up_arrow_disabled.png); 446 | height: 10px; 447 | width: 10px; 448 | subcontrol-position: top; 449 | subcontrol-origin: margin; 450 | } 451 | 452 | QScrollBar::add-line:vertical 453 | { 454 | margin: 3px 0px 3px 0px; 455 | border-image: url(:/qss_icons/rc/down_arrow_disabled.png); 456 | height: 10px; 457 | width: 10px; 458 | subcontrol-position: bottom; 459 | subcontrol-origin: margin; 460 | } 461 | 462 | QScrollBar::sub-line:vertical:hover,QScrollBar::sub-line:vertical:on 463 | { 464 | 465 | border-image: url(:/qss_icons/rc/up_arrow.png); 466 | height: 10px; 467 | width: 10px; 468 | subcontrol-position: top; 469 | subcontrol-origin: margin; 470 | } 471 | 472 | 473 | QScrollBar::add-line:vertical:hover, QScrollBar::add-line:vertical:on 474 | { 475 | border-image: url(:/qss_icons/rc/down_arrow.png); 476 | height: 10px; 477 | width: 10px; 478 | subcontrol-position: bottom; 479 | subcontrol-origin: margin; 480 | } 481 | 482 | QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical 483 | { 484 | background: none; 485 | } 486 | 487 | 488 | QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical 489 | { 490 | background: none; 491 | } 492 | 493 | QTextEdit 494 | { 495 | background-color: #232629; 496 | color: #eff0f1; 497 | border: 1px solid #76797C; 498 | } 499 | 500 | QPlainTextEdit 501 | { 502 | background-color: #232629;; 503 | color: #eff0f1; 504 | border-radius: 2px; 505 | border: 1px solid #76797C; 506 | } 507 | 508 | QHeaderView::section 509 | { 510 | background-color: #76797C; 511 | color: #eff0f1; 512 | padding: 5px; 513 | border: 1px solid #76797C; 514 | } 515 | 516 | QSizeGrip { 517 | image: url(:/qss_icons/rc/sizegrip.png); 518 | width: 12px; 519 | height: 12px; 520 | } 521 | 522 | 523 | QMainWindow::separator 524 | { 525 | background-color: #31363b; 526 | color: white; 527 | padding-left: 4px; 528 | spacing: 2px; 529 | border: 1px dashed #76797C; 530 | } 531 | 532 | QMainWindow::separator:hover 533 | { 534 | 535 | background-color: #787876; 536 | color: white; 537 | padding-left: 4px; 538 | border: 1px solid #76797C; 539 | spacing: 2px; 540 | } 541 | 542 | 543 | QMenu::separator 544 | { 545 | height: 1px; 546 | background-color: #76797C; 547 | color: white; 548 | padding-left: 4px; 549 | margin-left: 10px; 550 | margin-right: 5px; 551 | } 552 | 553 | 554 | QFrame 555 | { 556 | border-radius: 2px; 557 | border: 1px solid #76797C; 558 | } 559 | 560 | QFrame[frameShape="0"] 561 | { 562 | border-radius: 2px; 563 | border: 1px transparent #76797C; 564 | } 565 | 566 | QStackedWidget 567 | { 568 | border: 1px transparent black; 569 | } 570 | 571 | QToolBar { 572 | border: 1px transparent #393838; 573 | background: 1px solid #31363b; 574 | font-weight: bold; 575 | } 576 | 577 | QToolBar::handle:horizontal { 578 | image: url(:/qss_icons/rc/Hmovetoolbar.png); 579 | } 580 | QToolBar::handle:vertical { 581 | image: url(:/qss_icons/rc/Vmovetoolbar.png); 582 | } 583 | QToolBar::separator:horizontal { 584 | image: url(:/qss_icons/rc/Hsepartoolbar.png); 585 | } 586 | QToolBar::separator:vertical { 587 | image: url(:/qss_icons/rc/Vsepartoolbar.png); 588 | } 589 | QToolButton#qt_toolbar_ext_button { 590 | background: #58595a 591 | } 592 | 593 | QPushButton 594 | { 595 | color: #eff0f1; 596 | background-color: #31363b; 597 | border-width: 1px; 598 | border-color: #76797C; 599 | border-style: solid; 600 | padding: 5px; 601 | border-radius: 2px; 602 | outline: none; 603 | } 604 | 605 | QPushButton:disabled 606 | { 607 | background-color: #31363b; 608 | border-width: 1px; 609 | border-color: #454545; 610 | border-style: solid; 611 | padding-top: 5px; 612 | padding-bottom: 5px; 613 | padding-left: 10px; 614 | padding-right: 10px; 615 | border-radius: 2px; 616 | color: #454545; 617 | } 618 | 619 | QPushButton:focus { 620 | background-color: #3daee9; 621 | color: white; 622 | } 623 | 624 | QPushButton:pressed 625 | { 626 | background-color: #3daee9; 627 | padding-top: -15px; 628 | padding-bottom: -17px; 629 | } 630 | 631 | QPushButton#blueButton{ 632 | border-radius: 4px; 633 | border: 2px solid rgb(41, 41, 41); 634 | background-color: rgb(0, 112, 193); 635 | } 636 | QPushButton#blueButton:hover{ 637 | border-color: rgb(45, 45, 45); 638 | } 639 | QPushButton#blueButton:pressed, QPushButton#buleButton:checked{ 640 | border-color: rgb(0, 160, 230); 641 | } 642 | 643 | 644 | QPushButton#redButton{ 645 | border-radius: 4px; 646 | border: 2px solid rgb(41, 41, 41); 647 | background-color: rgb(114, 26, 20); 648 | } 649 | QPushButton#redButton:hover{ 650 | border-color: rgb(45, 45, 45); 651 | } 652 | QPushButton#redButton:pressed, QPushButton#buleButton:checked{ 653 | border-color: rgb(0, 160, 230); 654 | } 655 | 656 | QPushButton#greenButton{ 657 | border-radius: 4px; 658 | border: 2px solid rgb(41, 41, 41); 659 | background-color: rgb(118, 197, 126); 660 | } 661 | QPushButton#greenButton:hover{ 662 | border-color: rgb(45, 45, 45); 663 | } 664 | QPushButton#greenButton:pressed, QPushButton#buleButton:checked{ 665 | border-color: rgb(0, 160, 230); 666 | } 667 | 668 | 669 | QComboBox 670 | { 671 | selection-background-color: #3daee9; 672 | border-style: solid; 673 | border: 1px solid #76797C; 674 | border-radius: 2px; 675 | padding: 5px; 676 | min-width: 75px; 677 | } 678 | 679 | QPushButton:checked{ 680 | background-color: #76797C; 681 | border-color: #6A6969; 682 | } 683 | 684 | QComboBox:hover,QPushButton:hover,QAbstractSpinBox:hover,QLineEdit:hover,QTextEdit:hover,QPlainTextEdit:hover,QAbstractView:h 685 | over,QTreeView:hover 686 | { 687 | border: 1px solid #3daee9; 688 | color: #eff0f1; 689 | } 690 | 691 | QComboBox:on 692 | { 693 | padding-top: 3px; 694 | padding-left: 4px; 695 | selection-background-color: #4a4a4a; 696 | } 697 | 698 | QComboBox QAbstractItemView 699 | { 700 | background-color: #232629; 701 | border-radius: 2px; 702 | border: 1px solid #76797C; 703 | selection-background-color: #3daee9; 704 | } 705 | 706 | QComboBox::drop-down 707 | { 708 | subcontrol-origin: padding; 709 | subcontrol-position: top right; 710 | width: 15px; 711 | 712 | border-left-width: 0px; 713 | border-left-color: darkgray; 714 | border-left-style: solid; 715 | border-top-right-radius: 3px; 716 | border-bottom-right-radius: 3px; 717 | } 718 | 719 | QComboBox::down-arrow 720 | { 721 | image: url(:/qss_icons/rc/down_arrow_disabled.png); 722 | } 723 | 724 | QComboBox::down-arrow:on, QComboBox::down-arrow:hover, 725 | QComboBox::down-arrow:focus 726 | { 727 | image: url(:/qss_icons/rc/down_arrow.png); 728 | } 729 | 730 | QAbstractSpinBox { 731 | padding: 5px; 732 | border: 1px solid #76797C; 733 | background-color: #232629; 734 | color: #eff0f1; 735 | border-radius: 2px; 736 | min-width: 75px; 737 | } 738 | 739 | QAbstractSpinBox:up-button 740 | { 741 | background-color: transparent; 742 | subcontrol-origin: border; 743 | subcontrol-position: center right; 744 | } 745 | 746 | QAbstractSpinBox:down-button 747 | { 748 | background-color: transparent; 749 | subcontrol-origin: border; 750 | subcontrol-position: center left; 751 | } 752 | 753 | QAbstractSpinBox::up-arrow,QAbstractSpinBox::up-arrow:disabled,QAbstractSpinBox::up-arrow:off { 754 | image: url(:/qss_icons/rc/up_arrow_disabled.png); 755 | width: 10px; 756 | height: 10px; 757 | } 758 | QAbstractSpinBox::up-arrow:hover 759 | { 760 | image: url(:/qss_icons/rc/up_arrow.png); 761 | } 762 | 763 | 764 | QAbstractSpinBox::down-arrow,QAbstractSpinBox::down-arrow:disabled,QAbstractSpinBox::down-arrow:off 765 | { 766 | image: url(:/qss_icons/rc/down_arrow_disabled.png); 767 | width: 10px; 768 | height: 10px; 769 | } 770 | QAbstractSpinBox::down-arrow:hover 771 | { 772 | image: url(:/qss_icons/rc/down_arrow.png); 773 | } 774 | 775 | 776 | QLabel 777 | { 778 | border: 0px solid black; 779 | } 780 | 781 | QLabel#whiteLabel 782 | { 783 | border: 0px solid black; 784 | background:white; 785 | color:rgb(100,100,100,250); 786 | font-size:15px; 787 | font-weight:bold; 788 | font-family:Roman times; 789 | } 790 | 791 | QLabel#whiteLabel:hover 792 | { 793 | color:rgb(100,100,100,120); 794 | } 795 | 796 | QLabel#blueLabel 797 | { 798 | border: 0px solid black; 799 | background:rgb(0, 112, 193); 800 | font-size:15px; 801 | font-weight:bold; 802 | font-family:Roman times; 803 | } 804 | 805 | QLabel#blueLabel:hover 806 | { 807 | color:rgb(100,100,100,120); 808 | } 809 | 810 | QLabel#redLabel 811 | { 812 | border: 0px solid black; 813 | background: rgb(114, 26, 20); 814 | font-size:15px; 815 | font-weight:bold; 816 | font-family:Roman times; 817 | } 818 | 819 | QLabel#redLabel:hover 820 | { 821 | color:rgb(100,100,100,120); 822 | } 823 | 824 | QLabel#greenLabel 825 | { 826 | border: 0px solid black; 827 | background: rgb(118, 197, 126); 828 | font-size:15px; 829 | font-weight:bold; 830 | font-family:Roman times; 831 | } 832 | 833 | QLabel#greenLabel:hover 834 | { 835 | color:rgb(100,100,100,120); 836 | } 837 | 838 | QLabel#orangeLabel 839 | { 840 | border: 0px solid black; 841 | background: rgb(255, 121, 0); 842 | font-size:15px; 843 | font-weight:bold; 844 | font-family:Roman times; 845 | } 846 | 847 | QLabel#orangeLabel:hover 848 | { 849 | color:rgb(100,100,100,120); 850 | } 851 | 852 | QLabel#purseLabel 853 | { 854 | border: 0px solid black; 855 | background: rgb(98, 43, 98); 856 | font-size:15px; 857 | font-weight:bold; 858 | font-family:Roman times; 859 | } 860 | 861 | QLabel#purseLabel:hover 862 | { 863 | color:rgb(100,100,100,120); 864 | } 865 | 866 | QTabWidget{ 867 | border: 0px transparent black; 868 | } 869 | 870 | QTabWidget::pane { 871 | border: 1px solid #76797C; 872 | padding: 5px; 873 | margin: 0px; 874 | } 875 | 876 | QTabBar 877 | { 878 | qproperty-drawBase: 0; 879 | left: 5px; /* move to the right by 5px */ 880 | border-radius: 3px; 881 | } 882 | 883 | QTabBar:focus 884 | { 885 | border: 0px transparent black; 886 | } 887 | 888 | QTabBar::close-button { 889 | image: url(:/qss_icons/rc/close.png); 890 | background: transparent; 891 | } 892 | 893 | QTabBar::close-button:hover 894 | { 895 | image: url(:/qss_icons/rc/close-hover.png); 896 | background: transparent; 897 | } 898 | 899 | QTabBar::close-button:pressed { 900 | image: url(:/qss_icons/rc/close-pressed.png); 901 | background: transparent; 902 | } 903 | 904 | /* TOP TABS */ 905 | QTabBar::tab:top { 906 | color: #eff0f1; 907 | border: 1px solid #76797C; 908 | border-bottom: 1px transparent black; 909 | background-color: #31363b; 910 | padding: 5px; 911 | min-width: 50px; 912 | border-top-left-radius: 2px; 913 | border-top-right-radius: 2px; 914 | } 915 | 916 | QTabBar::tab:top:!selected 917 | { 918 | color: #eff0f1; 919 | background-color: #54575B; 920 | border: 1px solid #76797C; 921 | border-bottom: 1px transparent black; 922 | border-top-left-radius: 2px; 923 | border-top-right-radius: 2px; 924 | } 925 | 926 | QTabBar::tab:top:!selected:hover { 927 | background-color: #3daee9; 928 | } 929 | 930 | /* BOTTOM TABS */ 931 | QTabBar::tab:bottom { 932 | color: #eff0f1; 933 | border: 1px solid #76797C; 934 | border-top: 1px transparent black; 935 | background-color: #31363b; 936 | padding: 5px; 937 | border-bottom-left-radius: 2px; 938 | border-bottom-right-radius: 2px; 939 | min-width: 50px; 940 | } 941 | 942 | QTabBar::tab:bottom:!selected 943 | { 944 | color: #eff0f1; 945 | background-color: #54575B; 946 | border: 1px solid #76797C; 947 | border-top: 1px transparent black; 948 | border-bottom-left-radius: 2px; 949 | border-bottom-right-radius: 2px; 950 | } 951 | 952 | QTabBar::tab:bottom:!selected:hover { 953 | background-color: #3daee9; 954 | } 955 | 956 | /* LEFT TABS */ 957 | QTabBar::tab:left { 958 | color: #eff0f1; 959 | border: 1px solid #76797C; 960 | border-left: 1px transparent black; 961 | background-color: #31363b; 962 | padding: 5px; 963 | border-top-right-radius: 2px; 964 | border-bottom-right-radius: 2px; 965 | min-height: 50px; 966 | } 967 | 968 | QTabBar::tab:left:!selected 969 | { 970 | color: #eff0f1; 971 | background-color: #54575B; 972 | border: 1px solid #76797C; 973 | border-left: 1px transparent black; 974 | border-top-right-radius: 2px; 975 | border-bottom-right-radius: 2px; 976 | } 977 | 978 | QTabBar::tab:left:!selected:hover { 979 | background-color: #3daee9; 980 | } 981 | 982 | 983 | /* RIGHT TABS */ 984 | QTabBar::tab:right { 985 | color: #eff0f1; 986 | border: 1px solid #76797C; 987 | border-right: 1px transparent black; 988 | background-color: #31363b; 989 | padding: 5px; 990 | border-top-left-radius: 2px; 991 | border-bottom-left-radius: 2px; 992 | min-height: 50px; 993 | } 994 | 995 | QTabBar::tab:right:!selected 996 | { 997 | color: #eff0f1; 998 | background-color: #54575B; 999 | border: 1px solid #76797C; 1000 | border-right: 1px transparent black; 1001 | border-top-left-radius: 2px; 1002 | border-bottom-left-radius: 2px; 1003 | } 1004 | 1005 | QTabBar::tab:right:!selected:hover { 1006 | background-color: #3daee9; 1007 | } 1008 | 1009 | QTabBar QToolButton::right-arrow:enabled { 1010 | image: url(:/qss_icons/rc/right_arrow.png); 1011 | } 1012 | 1013 | QTabBar QToolButton::left-arrow:enabled { 1014 | image: url(:/qss_icons/rc/left_arrow.png); 1015 | } 1016 | 1017 | QTabBar QToolButton::right-arrow:disabled { 1018 | image: url(:/qss_icons/rc/right_arrow_disabled.png); 1019 | } 1020 | 1021 | QTabBar QToolButton::left-arrow:disabled { 1022 | image: url(:/qss_icons/rc/left_arrow_disabled.png); 1023 | } 1024 | 1025 | 1026 | QDockWidget { 1027 | background: #31363b; 1028 | border: 1px solid #403F3F; 1029 | titlebar-close-icon: url(:/qss_icons/rc/close.png); 1030 | titlebar-normal-icon: url(:/qss_icons/rc/undock.png); 1031 | } 1032 | 1033 | QDockWidget::close-button, QDockWidget::float-button { 1034 | border: 1px solid transparent; 1035 | border-radius: 2px; 1036 | background: transparent; 1037 | } 1038 | 1039 | QDockWidget::close-button:hover, QDockWidget::float-button:hover { 1040 | background: rgba(255, 255, 255, 10); 1041 | } 1042 | 1043 | QDockWidget::close-button:pressed, QDockWidget::float-button:pressed { 1044 | padding: 1px -1px -1px 1px; 1045 | background: rgba(255, 255, 255, 10); 1046 | } 1047 | 1048 | QTreeView, QListView 1049 | { 1050 | border: 1px solid #76797C; 1051 | background-color: #232629; 1052 | } 1053 | 1054 | QTreeView:branch:selected, QTreeView:branch:hover 1055 | { 1056 | background: url(:/qss_icons/rc/transparent.png); 1057 | } 1058 | 1059 | QTreeView::branch:has-siblings:!adjoins-item { 1060 | border-image: url(:/qss_icons/rc/transparent.png); 1061 | } 1062 | 1063 | QTreeView::branch:has-siblings:adjoins-item { 1064 | border-image: url(:/qss_icons/rc/transparent.png); 1065 | } 1066 | 1067 | QTreeView::branch:!has-children:!has-siblings:adjoins-item { 1068 | border-image: url(:/qss_icons/rc/transparent.png); 1069 | } 1070 | 1071 | QTreeView::branch:has-children:!has-siblings:closed, 1072 | QTreeView::branch:closed:has-children:has-siblings { 1073 | image: url(:/qss_icons/rc/branch_closed.png); 1074 | } 1075 | 1076 | QTreeView::branch:open:has-children:!has-siblings, 1077 | QTreeView::branch:open:has-children:has-siblings { 1078 | image: url(:/qss_icons/rc/branch_open.png); 1079 | } 1080 | 1081 | QTreeView::branch:has-children:!has-siblings:closed:hover, 1082 | QTreeView::branch:closed:has-children:has-siblings:hover { 1083 | image: url(:/qss_icons/rc/branch_closed-on.png); 1084 | } 1085 | 1086 | QTreeView::branch:open:has-children:!has-siblings:hover, 1087 | QTreeView::branch:open:has-children:has-siblings:hover { 1088 | image: url(:/qss_icons/rc/branch_open-on.png); 1089 | } 1090 | 1091 | QListView::item:!selected:hover, QTreeView::item:!selected:hover { 1092 | background: rgba(167,218,245, 0.3); 1093 | outline: 0; 1094 | color: #eff0f1 1095 | } 1096 | 1097 | QListView::item:selected:hover, QTreeView::item:selected:hover { 1098 | background: #3daee9; 1099 | color: #eff0f1; 1100 | } 1101 | 1102 | QSlider::groove:horizontal { 1103 | border: 1px solid #565a5e; 1104 | height: 4px; 1105 | background: #565a5e; 1106 | margin: 0px; 1107 | border-radius: 2px; 1108 | } 1109 | 1110 | QSlider::handle:horizontal { 1111 | background: #232629; 1112 | border: 1px solid #565a5e; 1113 | width: 16px; 1114 | height: 16px; 1115 | margin: -8px 0; 1116 | border-radius: 9px; 1117 | } 1118 | 1119 | QSlider::groove:vertical { 1120 | border: 1px solid #565a5e; 1121 | width: 4px; 1122 | background: #565a5e; 1123 | margin: 0px; 1124 | border-radius: 3px; 1125 | } 1126 | 1127 | QSlider::handle:vertical { 1128 | background: #232629; 1129 | border: 1px solid #565a5e; 1130 | width: 16px; 1131 | height: 16px; 1132 | margin: 0 -8px; 1133 | border-radius: 9px; 1134 | } 1135 | 1136 | QToolButton { 1137 | background-color: transparent; 1138 | border: 1px transparent #76797C; 1139 | border-radius: 2px; 1140 | margin: 3px; 1141 | padding: 5px; 1142 | } 1143 | 1144 | QToolButton[popupMode="1"] { /* only for MenuButtonPopup */ 1145 | padding-right: 20px; /* make way for the popup button */ 1146 | border: 1px #76797C; 1147 | border-radius: 5px; 1148 | } 1149 | 1150 | QToolButton[popupMode="2"] { /* only for InstantPopup */ 1151 | padding-right: 10px; /* make way for the popup button */ 1152 | border: 1px #76797C; 1153 | } 1154 | 1155 | 1156 | QToolButton:hover, QToolButton::menu-button:hover { 1157 | background-color: transparent; 1158 | border: 1px solid #3daee9; 1159 | padding: 5px; 1160 | } 1161 | 1162 | QToolButton:checked, QToolButton:pressed, 1163 | QToolButton::menu-button:pressed { 1164 | background-color: #3daee9; 1165 | border: 1px solid #3daee9; 1166 | padding: 5px; 1167 | } 1168 | 1169 | /* the subcontrol below is used only in the InstantPopup or DelayedPopup mode */ 1170 | QToolButton::menu-indicator { 1171 | image: url(:/qss_icons/rc/down_arrow.png); 1172 | top: -7px; left: -2px; /* shift it a bit */ 1173 | } 1174 | 1175 | /* the subcontrols below are used only in the MenuButtonPopup mode */ 1176 | QToolButton::menu-button { 1177 | border: 1px transparent #76797C; 1178 | border-top-right-radius: 6px; 1179 | border-bottom-right-radius: 6px; 1180 | /* 16px width + 4px for border = 20px allocated above */ 1181 | width: 16px; 1182 | outline: none; 1183 | } 1184 | 1185 | QToolButton::menu-arrow { 1186 | image: url(:/qss_icons/rc/down_arrow.png); 1187 | } 1188 | 1189 | QToolButton::menu-arrow:open { 1190 | border: 1px solid #76797C; 1191 | } 1192 | 1193 | QPushButton::menu-indicator { 1194 | subcontrol-origin: padding; 1195 | subcontrol-position: bottom right; 1196 | left: 8px; 1197 | } 1198 | 1199 | QTableView 1200 | { 1201 | border: 1px solid #76797C; 1202 | gridline-color: #31363b; 1203 | background-color: #232629; 1204 | } 1205 | 1206 | 1207 | QTableView, QHeaderView 1208 | { 1209 | border-radius: 0px; 1210 | } 1211 | 1212 | QTableView::item:pressed, QListView::item:pressed, QTreeView::item:pressed { 1213 | background: #3daee9; 1214 | color: #eff0f1; 1215 | } 1216 | 1217 | QTableView::item:selected:active, QTreeView::item:selected:active, QListView::item:selected:active { 1218 | background: #3daee9; 1219 | color: #eff0f1; 1220 | } 1221 | 1222 | 1223 | QHeaderView 1224 | { 1225 | background-color: #31363b; 1226 | border: 1px transparent; 1227 | border-radius: 0px; 1228 | margin: 0px; 1229 | padding: 0px; 1230 | 1231 | } 1232 | 1233 | QHeaderView::section { 1234 | background-color: #31363b; 1235 | color: #eff0f1; 1236 | padding: 5px; 1237 | border: 1px solid #76797C; 1238 | border-radius: 0px; 1239 | text-align: center; 1240 | } 1241 | 1242 | QHeaderView::section::vertical::first, QHeaderView::section::vertical::only-one 1243 | { 1244 | border-top: 1px solid #76797C; 1245 | } 1246 | 1247 | QHeaderView::section::vertical 1248 | { 1249 | border-top: transparent; 1250 | } 1251 | 1252 | QHeaderView::section::horizontal::first, QHeaderView::section::horizontal::only-one 1253 | { 1254 | border-left: 1px solid #76797C; 1255 | } 1256 | 1257 | QHeaderView::section::horizontal 1258 | { 1259 | border-left: transparent; 1260 | } 1261 | 1262 | 1263 | QHeaderView::section:checked 1264 | { 1265 | color: white; 1266 | background-color: #334e5e; 1267 | } 1268 | 1269 | /* style the sort indicator */ 1270 | QHeaderView::down-arrow { 1271 | image: url(:/qss_icons/rc/down_arrow.png); 1272 | } 1273 | 1274 | QHeaderView::up-arrow { 1275 | image: url(:/qss_icons/rc/up_arrow.png); 1276 | } 1277 | 1278 | 1279 | QTableCornerButton::section { 1280 | background-color: #31363b; 1281 | border: 1px transparent #76797C; 1282 | border-radius: 0px; 1283 | } 1284 | 1285 | QToolBox { 1286 | padding: 5px; 1287 | border: 1px transparent black; 1288 | } 1289 | 1290 | QToolBox::tab { 1291 | color: #eff0f1; 1292 | background-color: #31363b; 1293 | border: 1px solid #76797C; 1294 | border-bottom: 1px transparent #31363b; 1295 | border-top-left-radius: 5px; 1296 | border-top-right-radius: 5px; 1297 | } 1298 | 1299 | QToolBox::tab:selected { /* italicize selected tabs */ 1300 | font: italic; 1301 | background-color: #31363b; 1302 | border-color: #3daee9; 1303 | } 1304 | 1305 | QStatusBar::item { 1306 | border: 0px transparent dark; 1307 | } 1308 | 1309 | 1310 | QFrame[height="3"], QFrame[width="3"] { 1311 | background-color: #76797C; 1312 | } 1313 | 1314 | 1315 | QSplitter::handle { 1316 | border: 1px dashed #76797C; 1317 | } 1318 | 1319 | QSplitter::handle:hover { 1320 | background-color: #787876; 1321 | border: 1px solid #76797C; 1322 | } 1323 | 1324 | QSplitter::handle:horizontal { 1325 | width: 1px; 1326 | } 1327 | 1328 | QSplitter::handle:vertical { 1329 | height: 1px; 1330 | } 1331 | 1332 | QProgressBar { 1333 | border: 1px solid #76797C; 1334 | border-radius: 5px; 1335 | text-align: center; 1336 | } 1337 | 1338 | QProgressBar::chunk { 1339 | background-color: #05B8CC; 1340 | } 1341 | 1342 | 1343 | -------------------------------------------------------------------------------- /ctaFunction/.visFunction.py.un~: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonnejs/uiKLine/08646956bd1d729c88d5d2617bf0599eb3efb3d1/ctaFunction/.visFunction.py.un~ -------------------------------------------------------------------------------- /ctaFunction/__init__.py: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | """ 3 | 包含一些CTA因子挖掘中常用的函数 4 | """ 5 | from __future__ import division 6 | 7 | from visFunction import * 8 | from calcFunction import * 9 | 10 | -------------------------------------------------------------------------------- /ctaFunction/calcFunction.py: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | import numba as nb 4 | import numpy as np 5 | 6 | @nb.autojit 7 | #------------------------------------------------ 8 | def get_capital_np(markets,signals,size,commiRate,climit = 4, wlimit = 2, op=True): 9 | """使用numpy回测,标签的盈亏, op 表示是否延迟一个tick以后撮合""" 10 | postions = np.zeros(len(signals)) 11 | actions = np.zeros(len(signals)) 12 | costs = np.zeros(len(signals)) 13 | pnls = np.zeros(len(signals)) 14 | lastsignal = 0 15 | lastpos = 0 16 | lastcost = 0 17 | num = 0 18 | for num in range(1,len(signals)): 19 | postions[num] = lastpos 20 | actions[num] = 0 21 | costs[num] = lastcost 22 | pnls[num] = 0 23 | # 止盈止损 24 | if lastpos > 0 and \ 25 | (markets[num,1]<=lastcost-climit or markets[num,1]>=lastcost+wlimit): 26 | postions[num] = 0 27 | actions[num] = -1 28 | costs[num] = 0 29 | fee = (markets[num,1]+lastcost)*size*commiRate 30 | pnls[num] = (markets[num,1]-lastcost)*size-fee 31 | elif lastpos < 0 and \ 32 | (markets[num,0]>=lastcost+climit or markets[num,0]<=lastcost-wlimit): 33 | postions[num] = 0 34 | actions[num] = 1 35 | costs[num] = 0 36 | fee = (markets[num,0]+lastcost)*size*commiRate 37 | pnls[num] = (lastcost-markets[num,0])*size-fee 38 | # 开仓 39 | if op: 40 | lastsignal = signals[num] 41 | if lastsignal > 0 and lastpos == 0: 42 | postions[num] = 1 43 | actions[num] = 1 44 | costs[num] = markets[num,0] 45 | elif lastsignal < 0 and lastpos == 0: 46 | postions[num] = -1 47 | actions[num] = -1 48 | costs[num] = markets[num,1] 49 | lastpos = postions[num] 50 | lastcost = costs[num] 51 | lastsignal = signals[num] 52 | return pnls,actions 53 | 54 | -------------------------------------------------------------------------------- /ctaFunction/visFunction.py: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | """ 3 | 包含一些CTA因子的可视化函数 4 | """ 5 | import matplotlib 6 | matplotlib.use('Qt4Agg') 7 | import numpy as np 8 | import pandas as pd 9 | import seaborn as sns 10 | #import matplotlib as mpl 11 | #mpl.rcParams["font.sans-serif"] = ["Microsoft YaHei"]# 12 | #mpl.rcParams['axes.unicode_minus'] = False 13 | import matplotlib.pyplot as plt 14 | from calcFunction import get_capital_np 15 | 16 | #---------------------------------------------------------------------- 17 | def plotSigCaps(signals,markets,climit=4,wlimit=2,size=1,rate=0.0001,op=True): 18 | """ 19 | 打印某一个信号的资金曲线 20 | """ 21 | plt.close() 22 | pnls,poss = get_capital_np(markets,signals,size,rate,\ 23 | climit=climit, wlimit=wlimit,op=op) 24 | caps = np.cumsum(pnls[pnls!=0]) 25 | return caps,poss 26 | 27 | #---------------------------------------------------------------------- 28 | def plotSigHeats(signals,markets,start=0,step=2,size=1,iters=6): 29 | """ 30 | 打印信号回测盈损热度图,寻找参数稳定岛 31 | """ 32 | sigMat = pd.DataFrame(index=range(iters),columns=range(iters)) 33 | for i in range(iters): 34 | for j in range(iters): 35 | climit = start + i*step 36 | wlimit = start + j*step 37 | caps,poss = plotSigCaps(signals,markets,climit=climit,wlimit=wlimit,size=size,op=False) 38 | sigMat[i][j] = caps[-1] 39 | sns.heatmap(sigMat.values.astype(np.float64),annot=True,fmt='.2f',annot_kws={"weight": "bold"}) 40 | xTicks = [i+0.5 for i in range(iters)] 41 | yTicks = [iters-i-0.5 for i in range(iters)] 42 | xyLabels = [str(start+i*step) for i in range(iters)] 43 | _, labels = plt.yticks(yTicks,xyLabels) 44 | plt.setp(labels, rotation=0) 45 | _, labels = plt.xticks(xTicks,xyLabels) 46 | plt.setp(labels, rotation=90) 47 | plt.xlabel('Loss Stop @') 48 | plt.ylabel('Profit Stop @') 49 | return sigMat 50 | -------------------------------------------------------------------------------- /ctaFunction/visFunction.py~: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | """ 3 | 包含一些CTA因子的可视化函数 4 | """ 5 | import numpy as np 6 | import pandas as pd 7 | import seaborn as sns 8 | #import matplotlib as mpl 9 | #mpl.rcParams["font.sans-serif"] = ["Microsoft YaHei"]# 10 | #mpl.rcParams['axes.unicode_minus'] = False 11 | import matplotlib.pyplot as plt 12 | from calcFunction import get_capital_np 13 | 14 | #---------------------------------------------------------------------- 15 | def plotSigCaps(signals,markets,climit=4,wlimit=2,size=1,rate=0.0001,op=True): 16 | """ 17 | 打印某一个信号的资金曲线 18 | """ 19 | plt.close() 20 | pnls,poss = get_capital_np(markets,signals,size,rate,\ 21 | climit=climit, wlimit=wlimit,op=op) 22 | caps = np.cumsum(pnls[pnls!=0]) 23 | return caps,poss 24 | 25 | #---------------------------------------------------------------------- 26 | def plotSigHeats(signals,markets,start=0,step=2,size=1,iters=6): 27 | """ 28 | 打印信号回测盈损热度图,寻找参数稳定岛 29 | """ 30 | sigMat = pd.DataFrame(index=range(iters),columns=range(iters)) 31 | for i in range(iters): 32 | for j in range(iters): 33 | climit = start + i*step 34 | wlimit = start + j*step 35 | caps,poss = plotSigCaps(signals,markets,climit=climit,wlimit=wlimit,size=size,op=False) 36 | sigMat[i][j] = caps[-1] 37 | sns.heatmap(sigMat.values.astype(np.float64),annot=True,fmt='.2f',annot_kws={"weight": "bold"}) 38 | xTicks = [i+0.5 for i in range(iters)] 39 | yTicks = [iters-i-0.5 for i in range(iters)] 40 | xyLabels = [str(start+i*step) for i in range(iters)] 41 | _, labels = plt.yticks(yTicks,xyLabels) 42 | plt.setp(labels, rotation=0) 43 | _, labels = plt.xticks(xTicks,xyLabels) 44 | plt.setp(labels, rotation=90) 45 | plt.xlabel('Loss Stop @') 46 | plt.ylabel('Profit Stop @') 47 | return sigMat 48 | -------------------------------------------------------------------------------- /func-button/klBacktest.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | 插入所有需要的库,和函数 4 | """ 5 | import numpy as np 6 | from ctaFunction.calcFunction import * 7 | from ctaFunction.visFunction import * 8 | 9 | #---------------------------------------------------------------------- 10 | def klBacktest(self): 11 | wLimit = self.getInputParamByName('wLimit') 12 | cLimit = self.getInputParamByName('cLimit') 13 | size = self.getInputParamByName('size') 14 | sLippage = self.getInputParamByName('sLippage') 15 | tickers = pd.DataFrame() 16 | tickers['bidPrice1'] = self.pdBars['open']-sLippage 17 | tickers['askPrice1'] = self.pdBars['open']+sLippage 18 | markets = tickers.values 19 | signals = np.array(self.signalsOpen) 20 | caps,poss = plotSigCaps(signals,markets,cLimit,wLimit,size=size) 21 | plt.plot(range(len(caps)),caps) 22 | plt.show() 23 | -------------------------------------------------------------------------------- /func-button/klClearSig.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | 插入所有需要的库,和函数 4 | """ 5 | #---------------------------------------------------------------------- 6 | def klClearSig(self): 7 | kTool = self.canvas 8 | for sig in kTool.sigPlots: 9 | kTool.pwKL.removeItem(kTool.sigPlots[sig]) 10 | kTool.sigData = {} 11 | kTool.sigPlots = {} 12 | for sig in kTool.subSigPlots: 13 | kTool.pwOI.removeItem(kTool.subSigPlots[sig]) 14 | kTool.subSigData = {} 15 | kTool.subSigPlots = {} 16 | -------------------------------------------------------------------------------- /func-button/klHeatmap.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | 插入所有需要的库,和函数 4 | """ 5 | import numpy as np 6 | from ctaFunction.calcFunction import * 7 | from ctaFunction.visFunction import * 8 | 9 | #---------------------------------------------------------------------- 10 | def klHeatmap(self): 11 | start = self.getInputParamByName('wLimit') 12 | step = self.getInputParamByName('cLimit') 13 | sLippage = self.getInputParamByName('sLippage') 14 | size = self.getInputParamByName('size') 15 | tickers = pd.DataFrame() 16 | tickers['bidPrice1'] = self.pdBars['open']-sLippage 17 | tickers['askPrice1'] = self.pdBars['open']+sLippage 18 | markets = tickers.values 19 | signals = np.array(self.signalsOpen) 20 | plotSigHeats(signals,markets,start=start,step=step,size=size,iters=6) 21 | plt.show() 22 | -------------------------------------------------------------------------------- /func-button/klLoad.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | 插入所有需要的库,和函数 4 | """ 5 | import pandas as pd 6 | 7 | #---------------------------------------------------------------------- 8 | def klLoad(self,bars=None): 9 | """载入合约数据""" 10 | bars = pd.DataFrame.from_csv('datasig.csv') 11 | kTool = self.canvas 12 | for sig in kTool.sigPlots: 13 | kTool.pwKL.removeItem(kTool.sigPlots[sig]) 14 | kTool.sigData = {} 15 | kTool.sigPlots = {} 16 | for sig in kTool.subSigPlots: 17 | kTool.pwOI.removeItem(kTool.subSigPlots[sig]) 18 | kTool.subSigData = {} 19 | kTool.subSigPlots = {} 20 | self.loadData(bars) 21 | 22 | -------------------------------------------------------------------------------- /func-button/klShowdown.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | 插入所有需要的库,和函数 4 | """ 5 | import pandas as pd 6 | import numpy as np 7 | 8 | #---------------------------------------------------------------------- 9 | def klShowdown(self): 10 | """信号曲线""" 11 | sigName = self.getInputParamByName('signalName') 12 | self.canvas.listOpenInterest = self.stateData[sigName] 13 | self.canvas.datas['openInterest'] = np.array(self.stateData[sigName]) 14 | self.canvas.plotOI(0,len(self.stateData[sigName])) 15 | self.canvas.showSig({sigName:self.stateData[sigName]},False) 16 | -------------------------------------------------------------------------------- /func-button/klShowmain.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | 插入所有需要的库,和函数 4 | """ 5 | import pandas as pd 6 | 7 | #---------------------------------------------------------------------- 8 | def klShowmain(self): 9 | """信号分析报告""" 10 | sigName = self.getInputParamByName('signalName') 11 | self.canvas.showSig({sigName:self.stateData[sigName]},True) 12 | 13 | -------------------------------------------------------------------------------- /func-button/klSigmode.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | 插入所有需要的库,和函数 4 | """ 5 | 6 | #---------------------------------------------------------------------- 7 | def klSigmode(self): 8 | """查找模式""" 9 | if self.mode == 'deal': 10 | self.canvas.updateSig(self.signalsOpen) 11 | self.mode = 'dealOpen' 12 | else: 13 | self.canvas.updateSig(self.signals) 14 | self.mode = 'deal' 15 | 16 | -------------------------------------------------------------------------------- /json/uiKLine_button.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "class": "输入控制", 4 | "label": "载入数据", 5 | "width": 2, 6 | "func": "klLoad", 7 | "style":"blueButton" 8 | }, 9 | { 10 | "class": "回测控制", 11 | "label": "信号回测", 12 | "width": 2, 13 | "func": "klBacktest", 14 | "style":"greenButton" 15 | }, 16 | { 17 | "class": "回测控制", 18 | "label": "盈损热力", 19 | "width": 2, 20 | "func": "klHeatmap", 21 | "style":"greenButton" 22 | }, 23 | { 24 | "class": "信号控制", 25 | "label": "只看开仓", 26 | "width": 2, 27 | "func": "klSigmode", 28 | "style":"redButton" 29 | }, 30 | { 31 | "class": "信号控制", 32 | "label": "主图显示", 33 | "width": 2, 34 | "func": "klShowmain", 35 | "style":"redButton" 36 | }, 37 | { 38 | "class": "信号控制", 39 | "label": "副图显示", 40 | "width": 2, 41 | "func": "klShowdown", 42 | "style":"redButton" 43 | }, 44 | { 45 | "class": "信号控制", 46 | "label": "清空信号", 47 | "width": 2, 48 | "func": "klClearSig", 49 | "style":"redButton" 50 | } 51 | ] 52 | -------------------------------------------------------------------------------- /json/uiKLine_input.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "signalName", 4 | "default": "", 5 | "class": "信号输入", 6 | "label": "信号选择", 7 | "width": 4, 8 | "type": "List", 9 | "eval": false, 10 | "ListVar" : "[]" 11 | }, 12 | { 13 | "name": "wLimit", 14 | "default": "10", 15 | "class": "回测参数", 16 | "label": "止盈/开始", 17 | "width": 2, 18 | "eval": true, 19 | "type": "Edit" 20 | }, 21 | { 22 | "name": "cLimit", 23 | "default": "4", 24 | "class": "回测参数", 25 | "label": "止损/间隔", 26 | "width": 2, 27 | "eval": true, 28 | "type": "Edit" 29 | }, 30 | { 31 | "name": "size", 32 | "default": "4", 33 | "class": "回测参数", 34 | "label": "下单手数", 35 | "width": 2, 36 | "eval": true, 37 | "type": "Edit" 38 | }, 39 | { 40 | "name": "sLippage", 41 | "default": "1", 42 | "class": "回测参数", 43 | "label": "合约滑点", 44 | "width": 2, 45 | "eval": true, 46 | "type": "Edit" 47 | } 48 | ] 49 | -------------------------------------------------------------------------------- /uiBasicIO.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # PyQt 3 | from qtpy.QtGui import * 4 | from qtpy.QtWidgets import * 5 | from qtpy.QtCore import * 6 | from qtpy import QtGui,QtCore 7 | # Others 8 | import os 9 | import imp 10 | import sys 11 | import json 12 | import glob 13 | from functools import partial 14 | from collections import OrderedDict 15 | 16 | # 导入按钮函数 17 | #--------------------------------------------------------------------------------------- 18 | ALL_FUNC_BUTTON = [] 19 | funcBtnPath = os.getcwd() + '/func-button/' 20 | allPath = glob.glob(funcBtnPath+r'*.py') 21 | for path in allPath: 22 | fileName = path.split("\\")[-1] 23 | modelName = fileName.split(".")[0] 24 | ALL_FUNC_BUTTON.append(modelName) 25 | imp.load_source('ctaFuncButttons',path) 26 | 27 | BUTTON_FUNC = {} 28 | from ctaFuncButttons import * 29 | for func_bt in ALL_FUNC_BUTTON: 30 | fn_obj = getattr(sys.modules['ctaFuncButttons'], func_bt) 31 | BUTTON_FUNC[func_bt] = fn_obj 32 | 33 | # 字符串转换 34 | #--------------------------------------------------------------------------------------- 35 | try: 36 | _fromUtf8 = QtCore.QString.fromUtf8 37 | except AttributeError: 38 | def _fromUtf8(s): 39 | return s 40 | 41 | ######################################################################## 42 | class uiBasicIO(QWidget): 43 | """通过json文件,自动生成输入框和按钮的元类""" 44 | 45 | #---------------------------------------------------------------------- 46 | def __init__(self,parent=None,inpFile='',btnFile=''): 47 | """初始化函数""" 48 | super(uiBasicIO,self).__init__(parent) 49 | 50 | # 输入框数据 51 | self.classDict = OrderedDict() 52 | self.labelDict = {} 53 | self.widthDict = {} 54 | self.typeDict = {} 55 | self.evalDict = {} 56 | self.editDict = {} 57 | # 按钮数据 58 | self.bClassDict = OrderedDict() 59 | self.bWidthDict = {} 60 | self.bFunDict = {} 61 | self.buttonDict = {} 62 | # 输入框和按钮 63 | self.groupInput = None 64 | self.groupProcess = None 65 | # 输入框和按钮的配置文件 66 | self.inpFile = inpFile 67 | self.btnFile = btnFile 68 | 69 | self.loadInputSetting() 70 | self.loadButtonSetting() 71 | self.initBasicUi() 72 | 73 | #---------------------------------------------------------------------- 74 | def getInputParamByName(self,name): 75 | """获得输入框参数值""" 76 | typeName = self.typeDict[name] 77 | editCell = self.editDict[name] 78 | val = str(editCell.currentText()) if typeName == 'List' else str(editCell.text()) 79 | try: 80 | return (eval(val) if self.evalDict[name] else val) 81 | except: 82 | return val 83 | 84 | #---------------------------------------------------------------------- 85 | def loadInputSetting(self): 86 | """载入输入框界面配置""" 87 | settingFile = self.inpFile 88 | with open(settingFile) as f: 89 | for setting in json.load(f): 90 | name = setting['name'] 91 | label = setting['label'] 92 | typeName = setting['type'] 93 | evalType = setting['eval'] 94 | width = setting['width'] 95 | className = setting['class'] 96 | default = setting['default'] 97 | # 标签 98 | self.labelDict[name] = QLabel(label) 99 | self.labelDict[name].setAlignment(QtCore.Qt.AlignCenter) 100 | # 宽度 101 | self.widthDict[name] = width 102 | # 输入框类型 103 | self.typeDict[name] = typeName 104 | self.evalDict[name] = evalType 105 | # 分类 106 | if className in self.classDict: 107 | self.classDict[className].append(name) 108 | else: 109 | self.classDict[className] = [name] 110 | # 输入框 111 | if typeName == 'Edit': 112 | self.editDict[name] = QLineEdit() 113 | self.editDict[name].setText(default) 114 | elif typeName == 'List': 115 | self.editDict[name] = QComboBox() 116 | self.editDict[name].addItems(eval(setting['ListVar'])) 117 | 118 | #---------------------------------------------------------------------- 119 | def loadButtonSetting(self): 120 | """载入按钮界面配置""" 121 | settingFile = self.btnFile 122 | with open(settingFile) as f: 123 | for setting in json.load(f): 124 | label = setting['label'] 125 | func = setting['func'] 126 | width = setting['width'] 127 | className = setting['class'] 128 | style = setting['style'] 129 | # 按钮 130 | self.buttonDict[func] = QPushButton(label) 131 | self.buttonDict[func].setObjectName(_fromUtf8(style)) 132 | self.buttonDict[func].clicked.connect(partial(BUTTON_FUNC[func],self)) 133 | # 宽度 134 | self.bWidthDict[func] = width 135 | # 分类 136 | if className in self.bClassDict: 137 | self.bClassDict[className].append(func) 138 | else: 139 | self.bClassDict[className] = [func] 140 | 141 | #---------------------------------------------------------------------- 142 | def initBasicUi(self): 143 | """初始化界面""" 144 | # 根据配置文件生成输入框界面 145 | self.groupInput = QGroupBox() 146 | self.groupInput.setTitle(u'') 147 | gridup = QGridLayout() 148 | i = 0 149 | for className in self.classDict: 150 | classIndex = i 151 | # 标题和输入框 152 | for name in self.classDict[className]: 153 | width = self.widthDict[name] 154 | qLabel = self.labelDict[name] 155 | qEdit = self.editDict[name] 156 | gridup.addWidget(qLabel, 1, i) 157 | gridup.addWidget(qEdit, 2, i) 158 | gridup.setColumnStretch(i, width) 159 | i+=1 160 | # 分类标题 161 | qcLabel = QLabel(className) 162 | qcLabel.setAlignment(QtCore.Qt.AlignCenter) 163 | qcLabel.setFont(QtGui.QFont("Roman times",10,QtGui.QFont.Bold)) 164 | gridup.addWidget(qcLabel, 0, classIndex,1,i-classIndex) 165 | # 分隔符 166 | for j in xrange(0,3): 167 | qcSplit = QLabel(u'|') 168 | qcSplit.setAlignment(QtCore.Qt.AlignCenter) 169 | gridup.addWidget(qcSplit, j, i) 170 | i+=1 171 | self.groupInput.setLayout(gridup) 172 | 173 | # 根据配置文件生成按钮界面 174 | self.groupProcess = QGroupBox() 175 | self.groupProcess.setTitle(u'') 176 | griddown = QGridLayout() 177 | i = 0 178 | for className in self.bClassDict: 179 | classIndex = i 180 | # 标题和输入框 181 | for name in self.bClassDict[className]: 182 | width = self.bWidthDict[name] 183 | qButton = self.buttonDict[name] 184 | griddown.addWidget(qButton, 1, i) 185 | griddown.setColumnStretch(i, width) 186 | i+=1 187 | # 分类标题 188 | qcLabel = QLabel(className) 189 | qcLabel.setAlignment(QtCore.Qt.AlignCenter) 190 | qcLabel.setFont(QFont("Roman times",10,QtGui.QFont.Bold)) 191 | griddown.addWidget(qcLabel, 0, classIndex,1,i-classIndex) 192 | # 分隔符 193 | for j in xrange(0,2): 194 | qcSplit = QLabel(u'|') 195 | qcSplit.setAlignment(QtCore.Qt.AlignCenter) 196 | griddown.addWidget(qcSplit, j, i) 197 | i+=1 198 | self.groupProcess.setLayout(griddown) 199 | 200 | -------------------------------------------------------------------------------- /uiCrosshair.py: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | import sys,os 3 | import qtpy 4 | import pyqtgraph as pg 5 | import datetime as dt 6 | import numpy as np 7 | import traceback 8 | 9 | #from pyqtgraph.Qt import QtGui, QtCore 10 | from qtpy import QtGui, QtCore 11 | from pyqtgraph.Point import Point 12 | 13 | ######################################################################## 14 | # 十字光标支持 15 | ######################################################################## 16 | class Crosshair(QtCore.QObject): 17 | """ 18 | 此类给pg.PlotWidget()添加crossHair功能,PlotWidget实例需要初始化时传入 19 | """ 20 | signal = QtCore.Signal(type(tuple([]))) 21 | signalInfo = QtCore.Signal(float,float) 22 | #---------------------------------------------------------------------- 23 | def __init__(self,parent,master): 24 | """Constructor""" 25 | self.__view = parent 26 | self.master = master 27 | super(Crosshair, self).__init__() 28 | 29 | self.xAxis = 0 30 | self.yAxis = 0 31 | 32 | self.datas = None 33 | 34 | self.yAxises = [0 for i in range(3)] 35 | self.leftX = [0 for i in range(3)] 36 | self.showHLine = [False for i in range(3)] 37 | self.textPrices = [pg.TextItem('',anchor=(1,1)) for i in range(3)] 38 | self.views = [parent.centralWidget.getItem(i+1,0) for i in range(3)] 39 | self.rects = [self.views[i].sceneBoundingRect() for i in range(3)] 40 | self.vLines = [pg.InfiniteLine(angle=90, movable=False) for i in range(3)] 41 | self.hLines = [pg.InfiniteLine(angle=0, movable=False) for i in range(3)] 42 | 43 | #mid 在y轴动态跟随最新价显示最新价和最新时间 44 | self.__textDate = pg.TextItem('date',anchor=(1,1)) 45 | self.__textInfo = pg.TextItem('lastBarInfo') 46 | self.__textSig = pg.TextItem('lastSigInfo',anchor=(1,0)) 47 | self.__textSubSig = pg.TextItem('lastSubSigInfo',anchor=(1,0)) 48 | self.__textVolume = pg.TextItem('lastBarVolume',anchor=(1,0)) 49 | 50 | self.__textDate.setZValue(2) 51 | self.__textInfo.setZValue(2) 52 | self.__textSig.setZValue(2) 53 | self.__textSubSig.setZValue(2) 54 | self.__textVolume.setZValue(2) 55 | self.__textInfo.border = pg.mkPen(color=(230, 255, 0, 255), width=1.2) 56 | 57 | for i in range(3): 58 | self.textPrices[i].setZValue(2) 59 | self.vLines[i].setPos(0) 60 | self.hLines[i].setPos(0) 61 | self.vLines[i].setZValue(0) 62 | self.hLines[i].setZValue(0) 63 | self.views[i].addItem(self.vLines[i]) 64 | self.views[i].addItem(self.hLines[i]) 65 | self.views[i].addItem(self.textPrices[i]) 66 | 67 | self.views[0].addItem(self.__textInfo, ignoreBounds=True) 68 | self.views[0].addItem(self.__textSig, ignoreBounds=True) 69 | self.views[1].addItem(self.__textVolume, ignoreBounds=True) 70 | self.views[2].addItem(self.__textDate, ignoreBounds=True) 71 | self.views[2].addItem(self.__textSubSig, ignoreBounds=True) 72 | self.proxy = pg.SignalProxy(self.__view.scene().sigMouseMoved, rateLimit=360, slot=self.__mouseMoved) 73 | # 跨线程刷新界面支持 74 | self.signal.connect(self.update) 75 | self.signalInfo.connect(self.plotInfo) 76 | 77 | #---------------------------------------------------------------------- 78 | def update(self,pos): 79 | """刷新界面显示""" 80 | xAxis,yAxis = pos 81 | xAxis,yAxis = (self.xAxis,self.yAxis) if xAxis is None else (xAxis,yAxis) 82 | self.moveTo(xAxis,yAxis) 83 | 84 | #---------------------------------------------------------------------- 85 | def __mouseMoved(self,evt): 86 | """鼠标移动回调""" 87 | pos = evt[0] 88 | self.rects = [self.views[i].sceneBoundingRect() for i in range(3)] 89 | for i in range(3): 90 | self.showHLine[i] = False 91 | if self.rects[i].contains(pos): 92 | mousePoint = self.views[i].vb.mapSceneToView(pos) 93 | xAxis = mousePoint.x() 94 | yAxis = mousePoint.y() 95 | self.yAxises[i] = yAxis 96 | self.showHLine[i] = True 97 | self.moveTo(xAxis,yAxis) 98 | 99 | #---------------------------------------------------------------------- 100 | def moveTo(self,xAxis,yAxis): 101 | xAxis,yAxis = (self.xAxis,self.yAxis) if xAxis is None else (int(xAxis),yAxis) 102 | self.rects = [self.views[i].sceneBoundingRect() for i in range(3)] 103 | if not xAxis or not yAxis: 104 | return 105 | self.xAxis = xAxis 106 | self.yAxis = yAxis 107 | self.vhLinesSetXY(xAxis,yAxis) 108 | self.plotInfo(xAxis,yAxis) 109 | self.master.volume.update() 110 | 111 | #---------------------------------------------------------------------- 112 | def vhLinesSetXY(self,xAxis,yAxis): 113 | """水平和竖线位置设置""" 114 | for i in range(3): 115 | self.vLines[i].setPos(xAxis) 116 | if self.showHLine[i]: 117 | self.hLines[i].setPos(yAxis if i==0 else self.yAxises[i]) 118 | self.hLines[i].show() 119 | else: 120 | self.hLines[i].hide() 121 | 122 | #---------------------------------------------------------------------- 123 | def plotInfo(self,xAxis,yAxis): 124 | """ 125 | 被嵌入的plotWidget在需要的时候通过调用此方法显示K线信息 126 | """ 127 | if self.datas is None: 128 | return 129 | try: 130 | # 获取K线数据 131 | data = self.datas[xAxis] 132 | lastdata = self.datas[xAxis-1] 133 | tickDatetime = data['datetime'] 134 | openPrice = data['open'] 135 | closePrice = data['close'] 136 | lowPrice = data['low'] 137 | highPrice = data['high'] 138 | volume = int(data['volume']) 139 | openInterest = int(data['openInterest']) 140 | preClosePrice = lastdata['close'] 141 | tradePrice = abs(self.master.listSig[xAxis]) 142 | except Exception as e: 143 | return 144 | 145 | if(isinstance(tickDatetime,dt.datetime)): 146 | datetimeText = dt.datetime.strftime(tickDatetime,'%Y-%m-%d %H:%M:%S') 147 | dateText = dt.datetime.strftime(tickDatetime,'%Y-%m-%d') 148 | timeText = dt.datetime.strftime(tickDatetime,'%H:%M:%S') 149 | else: 150 | datetimeText = "" 151 | dateText = "" 152 | timeText = "" 153 | 154 | # 显示所有的主图技术指标 155 | html = u'
' 156 | for sig in self.master.sigData: 157 | val = self.master.sigData[sig][xAxis] 158 | col = self.master.sigColor[sig] 159 | html+= u'  %s:%.2f' %(col,sig,val) 160 | html+=u'
' 161 | self.__textSig.setHtml(html) 162 | 163 | # 显示所有的主图技术指标 164 | html = u'
' 165 | for sig in self.master.subSigData: 166 | val = self.master.subSigData[sig][xAxis] 167 | col = self.master.subSigColor[sig] 168 | html+= u'  %s:%.2f' %(col,sig,val) 169 | html+=u'
' 170 | self.__textSubSig.setHtml(html) 171 | 172 | 173 | # 和上一个收盘价比较,决定K线信息的字符颜色 174 | cOpen = 'red' if openPrice > preClosePrice else 'green' 175 | cClose = 'red' if closePrice > preClosePrice else 'green' 176 | cHigh = 'red' if highPrice > preClosePrice else 'green' 177 | cLow = 'red' if lowPrice > preClosePrice else 'green' 178 | 179 | self.__textInfo.setHtml( 180 | u'
\ 181 | 日期
\ 182 | %s
\ 183 | 时间
\ 184 | %s
\ 185 | 价格
\ 186 | (开) %.3f
\ 187 | (高) %.3f
\ 188 | (低) %.3f
\ 189 | (收) %.3f
\ 190 | 成交量
\ 191 | (量) %d
\ 192 | 成交价
\ 193 | (价) %.3f
\ 194 |
'\ 195 | % (dateText,timeText,cOpen,openPrice,cHigh,highPrice,\ 196 | cLow,lowPrice,cClose,closePrice,volume,tradePrice)) 197 | self.__textDate.setHtml( 198 | '
\ 199 | %s\ 200 |
'\ 201 | % (datetimeText)) 202 | 203 | self.__textVolume.setHtml( 204 | '
\ 205 | VOL : %.3f\ 206 |
'\ 207 | % (volume)) 208 | # 坐标轴宽度 209 | rightAxisWidth = self.views[0].getAxis('right').width() 210 | bottomAxisHeight = self.views[2].getAxis('bottom').height() 211 | offset = QtCore.QPointF(rightAxisWidth,bottomAxisHeight) 212 | 213 | # 各个顶点 214 | tl = [self.views[i].vb.mapSceneToView(self.rects[i].topLeft()) for i in range(3)] 215 | br = [self.views[i].vb.mapSceneToView(self.rects[i].bottomRight()-offset) for i in range(3)] 216 | 217 | # 显示价格 218 | for i in range(3): 219 | if self.showHLine[i]: 220 | self.textPrices[i].setHtml( 221 | '
\ 222 | \ 223 | %0.3f\ 224 | \ 225 |
'\ 226 | % (yAxis if i==0 else self.yAxises[i])) 227 | self.textPrices[i].setPos(br[i].x(),yAxis if i==0 else self.yAxises[i]) 228 | self.textPrices[i].show() 229 | else: 230 | self.textPrices[i].hide() 231 | 232 | 233 | # 设置坐标 234 | self.__textInfo.setPos(tl[0]) 235 | self.__textSig.setPos(br[0].x(),tl[0].y()) 236 | self.__textSubSig.setPos(br[2].x(),tl[2].y()) 237 | self.__textVolume.setPos(br[1].x(),tl[1].y()) 238 | 239 | # 修改对称方式防止遮挡 240 | self.__textDate.anchor = Point((1,1)) if xAxis > self.master.index else Point((0,1)) 241 | self.__textDate.setPos(xAxis,br[2].y()) 242 | -------------------------------------------------------------------------------- /uiKLine.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Python K线模块,包含十字光标和鼠标键盘交互 4 | Support By 量投科技(http://www.quantdo.com.cn/) 5 | """ 6 | import traceback 7 | import numpy as np 8 | import pandas as pd 9 | from functools import partial 10 | from collections import deque 11 | 12 | from qtpy.QtGui import * 13 | from qtpy.QtCore import * 14 | from qtpy.QtWidgets import * 15 | from qtpy import QtGui,QtCore 16 | from uiCrosshair import Crosshair 17 | import pyqtgraph as pg 18 | 19 | 20 | 21 | # 字符串转换 22 | #--------------------------------------------------------------------------------------- 23 | try: 24 | _fromUtf8 = QtCore.QString.fromUtf8 25 | except AttributeError: 26 | def _fromUtf8(s): 27 | return s 28 | 29 | ######################################################################## 30 | # 键盘鼠标功能 31 | ######################################################################## 32 | class KeyWraper(QWidget): 33 | """键盘鼠标功能支持的元类""" 34 | #初始化 35 | #---------------------------------------------------------------------- 36 | def __init__(self, parent=None): 37 | QWidget.__init__(self, parent) 38 | self.setMouseTracking(True) 39 | 40 | #重载方法keyPressEvent(self,event),即按键按下事件方法 41 | #---------------------------------------------------------------------- 42 | def keyPressEvent(self, event): 43 | if event.key() == QtCore.Qt.Key_Up: 44 | self.onUp() 45 | elif event.key() == QtCore.Qt.Key_Down: 46 | self.onDown() 47 | elif event.key() == QtCore.Qt.Key_Left: 48 | self.onLeft() 49 | elif event.key() == QtCore.Qt.Key_Right: 50 | self.onRight() 51 | elif event.key() == QtCore.Qt.Key_PageUp: 52 | self.onPre() 53 | elif event.key() == QtCore.Qt.Key_PageDown: 54 | self.onNxt() 55 | 56 | #重载方法mousePressEvent(self,event),即鼠标点击事件方法 57 | #---------------------------------------------------------------------- 58 | def mousePressEvent(self, event): 59 | if event.button() == QtCore.Qt.RightButton: 60 | self.onRClick(event.pos()) 61 | elif event.button() == QtCore.Qt.LeftButton: 62 | self.onLClick(event.pos()) 63 | 64 | #重载方法mouseReleaseEvent(self,event),即鼠标点击事件方法 65 | #---------------------------------------------------------------------- 66 | def mouseRelease(self, event): 67 | if event.button() == QtCore.Qt.RightButton: 68 | self.onRRelease(event.pos()) 69 | elif event.button() == QtCore.Qt.LeftButton: 70 | self.onLRelease(event.pos()) 71 | self.releaseMouse() 72 | 73 | #重载方法wheelEvent(self,event),即滚轮事件方法 74 | #---------------------------------------------------------------------- 75 | def wheelEvent(self, event): 76 | return 77 | 78 | #重载方法paintEvent(self,event),即拖动事件方法 79 | #---------------------------------------------------------------------- 80 | def paintEvent(self, event): 81 | self.onPaint() 82 | 83 | # PgDown键 84 | #---------------------------------------------------------------------- 85 | def onNxt(self): 86 | pass 87 | 88 | # PgUp键 89 | #---------------------------------------------------------------------- 90 | def onPre(self): 91 | pass 92 | 93 | # 向上键和滚轮向上 94 | #---------------------------------------------------------------------- 95 | def onUp(self): 96 | pass 97 | 98 | # 向下键和滚轮向下 99 | #---------------------------------------------------------------------- 100 | def onDown(self): 101 | pass 102 | 103 | # 向左键 104 | #---------------------------------------------------------------------- 105 | def onLeft(self): 106 | pass 107 | 108 | # 向右键 109 | #---------------------------------------------------------------------- 110 | def onRight(self): 111 | pass 112 | 113 | # 鼠标左单击 114 | #---------------------------------------------------------------------- 115 | def onLClick(self,pos): 116 | pass 117 | 118 | # 鼠标右单击 119 | #---------------------------------------------------------------------- 120 | def onRClick(self,pos): 121 | pass 122 | 123 | # 鼠标左释放 124 | #---------------------------------------------------------------------- 125 | def onLRelease(self,pos): 126 | pass 127 | 128 | # 鼠标右释放 129 | #---------------------------------------------------------------------- 130 | def onRRelease(self,pos): 131 | pass 132 | 133 | # 画图 134 | #---------------------------------------------------------------------- 135 | def onPaint(self): 136 | pass 137 | 138 | 139 | ######################################################################## 140 | # 选择缩放功能支持 141 | ######################################################################## 142 | class CustomViewBox(pg.ViewBox): 143 | #---------------------------------------------------------------------- 144 | def __init__(self, *args, **kwds): 145 | pg.ViewBox.__init__(self, *args, **kwds) 146 | # 拖动放大模式 147 | #self.setMouseMode(self.RectMode) 148 | 149 | ## 右键自适应 150 | #---------------------------------------------------------------------- 151 | def mouseClickEvent(self, ev): 152 | if ev.button() == QtCore.Qt.RightButton: 153 | self.autoRange() 154 | 155 | 156 | ######################################################################## 157 | # 时间序列,横坐标支持 158 | ######################################################################## 159 | class MyStringAxis(pg.AxisItem): 160 | """时间序列横坐标支持""" 161 | 162 | # 初始化 163 | #---------------------------------------------------------------------- 164 | def __init__(self, xdict, *args, **kwargs): 165 | pg.AxisItem.__init__(self, *args, **kwargs) 166 | self.minVal = 0 167 | self.maxVal = 0 168 | self.xdict = xdict 169 | self.x_values = np.asarray(xdict.keys()) 170 | self.x_strings = xdict.values() 171 | self.setPen(color=(255, 255, 255, 255), width=0.8) 172 | self.setStyle(tickFont = QFont("Roman times",10,QFont.Bold),autoExpandTextSpace=True) 173 | 174 | # 更新坐标映射表 175 | #---------------------------------------------------------------------- 176 | def update_xdict(self, xdict): 177 | self.xdict.update(xdict) 178 | self.x_values = np.asarray(self.xdict.keys()) 179 | self.x_strings = self.xdict.values() 180 | 181 | # 将原始横坐标转换为时间字符串,第一个坐标包含日期 182 | #---------------------------------------------------------------------- 183 | def tickStrings(self, values, scale, spacing): 184 | strings = [] 185 | for v in values: 186 | vs = v * scale 187 | if vs in self.x_values: 188 | vstr = self.x_strings[np.abs(self.x_values-vs).argmin()] 189 | vstr = vstr.strftime('%Y-%m-%d %H:%M:%S') 190 | else: 191 | vstr = "" 192 | strings.append(vstr) 193 | return strings 194 | 195 | ######################################################################## 196 | # K线图形对象 197 | ######################################################################## 198 | class CandlestickItem(pg.GraphicsObject): 199 | """K线图形对象""" 200 | 201 | # 初始化 202 | #---------------------------------------------------------------------- 203 | def __init__(self, data): 204 | """初始化""" 205 | pg.GraphicsObject.__init__(self) 206 | # 数据格式: [ (time, open, close, low, high),...] 207 | self.data = data 208 | # 只重画部分图形,大大提高界面更新速度 209 | self.rect = None 210 | self.picture = None 211 | self.setFlag(self.ItemUsesExtendedStyleOption) 212 | # 画笔和画刷 213 | w = 0.4 214 | self.offset = 0 215 | self.low = 0 216 | self.high = 1 217 | self.picture = QtGui.QPicture() 218 | self.pictures = [] 219 | self.bPen = pg.mkPen(color=(0, 240, 240, 255), width=w*2) 220 | self.bBrush = pg.mkBrush((0, 240, 240, 255)) 221 | self.rPen = pg.mkPen(color=(255, 60, 60, 255), width=w*2) 222 | self.rBrush = pg.mkBrush((255, 60, 60, 255)) 223 | self.rBrush.setStyle(Qt.NoBrush) 224 | # 刷新K线 225 | self.generatePicture(self.data) 226 | 227 | 228 | # 画K线 229 | #---------------------------------------------------------------------- 230 | def generatePicture(self,data=None,redraw=False): 231 | """重新生成图形对象""" 232 | # 重画或者只更新最后一个K线 233 | if redraw: 234 | self.pictures = [] 235 | elif self.pictures: 236 | self.pictures.pop() 237 | w = 0.4 238 | bPen = self.bPen 239 | bBrush = self.bBrush 240 | rPen = self.rPen 241 | rBrush = self.rBrush 242 | self.low,self.high = (np.min(data['low']),np.max(data['high'])) if len(data)>0 else (0,1) 243 | npic = len(self.pictures) 244 | for (t, open0, close0, low0, high0) in data: 245 | if t >= npic: 246 | picture = QtGui.QPicture() 247 | p = QtGui.QPainter(picture) 248 | # 下跌蓝色(实心), 上涨红色(空心) 249 | pen,brush,pmin,pmax = (bPen,bBrush,close0,open0)\ 250 | if open0 > close0 else (rPen,rBrush,open0,close0) 251 | p.setPen(pen) 252 | p.setBrush(brush) 253 | # 画K线方块和上下影线 254 | if open0 == close0: 255 | p.drawLine(QtCore.QPointF(t-w,open0), QtCore.QPointF(t+w, close0)) 256 | else: 257 | p.drawRect(QtCore.QRectF(t-w, open0, w*2, close0-open0)) 258 | if pmin > low0: 259 | p.drawLine(QtCore.QPointF(t,low0), QtCore.QPointF(t, pmin)) 260 | if high0 > pmax: 261 | p.drawLine(QtCore.QPointF(t,pmax), QtCore.QPointF(t, high0)) 262 | p.end() 263 | self.pictures.append(picture) 264 | 265 | # 手动重画 266 | #---------------------------------------------------------------------- 267 | def update(self): 268 | if not self.scene() is None: 269 | self.scene().update() 270 | 271 | # 自动重画 272 | #---------------------------------------------------------------------- 273 | def paint(self, painter, opt, w): 274 | rect = opt.exposedRect 275 | xmin,xmax = (max(0,int(rect.left())),min(int(len(self.pictures)),int(rect.right()))) 276 | if not self.rect == (rect.left(),rect.right()) or self.picture is None: 277 | self.rect = (rect.left(),rect.right()) 278 | self.picture = self.createPic(xmin,xmax) 279 | self.picture.play(painter) 280 | elif not self.picture is None: 281 | self.picture.play(painter) 282 | 283 | # 缓存图片 284 | #---------------------------------------------------------------------- 285 | def createPic(self,xmin,xmax): 286 | picture = QPicture() 287 | p = QPainter(picture) 288 | [pic.play(p) for pic in self.pictures[xmin:xmax]] 289 | p.end() 290 | return picture 291 | 292 | # 定义边界 293 | #---------------------------------------------------------------------- 294 | def boundingRect(self): 295 | return QtCore.QRectF(0,self.low,len(self.pictures),(self.high-self.low)) 296 | 297 | 298 | ######################################################################## 299 | class KLineWidget(KeyWraper): 300 | """用于显示价格走势图""" 301 | 302 | # 窗口标识 303 | clsId = 0 304 | 305 | # 保存K线数据的列表和Numpy Array对象 306 | listBar = [] 307 | listVol = [] 308 | listHigh = [] 309 | listLow = [] 310 | listSig = [] 311 | listOpenInterest = [] 312 | arrows = [] 313 | 314 | # 是否完成了历史数据的读取 315 | initCompleted = False 316 | 317 | #---------------------------------------------------------------------- 318 | def __init__(self,parent=None): 319 | """Constructor""" 320 | self.parent = parent 321 | super(KLineWidget, self).__init__(parent) 322 | 323 | # 当前序号 324 | self.index = None # 下标 325 | self.countK = 60 # 显示的K线范围 326 | 327 | KLineWidget.clsId += 1 328 | self.windowId = str(KLineWidget.clsId) 329 | 330 | # 缓存数据 331 | self.datas = [] 332 | self.listBar = [] 333 | self.listVol = [] 334 | self.listHigh = [] 335 | self.listLow = [] 336 | self.listSig = [] 337 | self.listOpenInterest = [] 338 | self.arrows = [] 339 | 340 | # 所有K线上信号图 341 | self.allColor = deque(['blue','green','yellow','white']) 342 | self.sigData = {} 343 | self.sigColor = {} 344 | self.sigPlots = {} 345 | 346 | # 所副图上信号图 347 | self.allSubColor = deque(['blue','green','yellow','white']) 348 | self.subSigData = {} 349 | self.subSigColor = {} 350 | self.subSigPlots = {} 351 | 352 | # 初始化完成 353 | self.initCompleted = False 354 | 355 | # 调用函数 356 | self.initUi() 357 | 358 | #---------------------------------------------------------------------- 359 | # 初始化相关 360 | #---------------------------------------------------------------------- 361 | def initUi(self): 362 | """初始化界面""" 363 | self.setWindowTitle(u'K线工具') 364 | # 主图 365 | self.pw = pg.PlotWidget() 366 | # 界面布局 367 | self.lay_KL = pg.GraphicsLayout(border=(100,100,100)) 368 | self.lay_KL.setContentsMargins(10, 10, 10, 10) 369 | self.lay_KL.setSpacing(0) 370 | self.lay_KL.setBorder(color=(255, 255, 255, 255), width=0.8) 371 | self.lay_KL.setZValue(0) 372 | self.KLtitle = self.lay_KL.addLabel(u'') 373 | self.pw.setCentralItem(self.lay_KL) 374 | # 设置横坐标 375 | xdict = {} 376 | self.axisTime = MyStringAxis(xdict, orientation='bottom') 377 | # 初始化子图 378 | self.initplotKline() 379 | self.initplotVol() 380 | self.initplotOI() 381 | # 注册十字光标 382 | self.crosshair = Crosshair(self.pw,self) 383 | # 设置界面 384 | self.vb = QVBoxLayout() 385 | self.vb.addWidget(self.pw) 386 | self.setLayout(self.vb) 387 | # 初始化完成 388 | self.initCompleted = True 389 | 390 | #---------------------------------------------------------------------- 391 | def makePI(self,name): 392 | """生成PlotItem对象""" 393 | vb = CustomViewBox() 394 | plotItem = pg.PlotItem(viewBox = vb, name=name ,axisItems={'bottom': self.axisTime}) 395 | plotItem.setMenuEnabled(False) 396 | plotItem.setClipToView(True) 397 | plotItem.hideAxis('left') 398 | plotItem.showAxis('right') 399 | plotItem.setDownsampling(mode='peak') 400 | plotItem.setRange(xRange = (0,1),yRange = (0,1)) 401 | plotItem.getAxis('right').setWidth(60) 402 | plotItem.getAxis('right').setStyle(tickFont = QFont("Roman times",10,QFont.Bold)) 403 | plotItem.getAxis('right').setPen(color=(255, 255, 255, 255), width=0.8) 404 | plotItem.showGrid(True,True) 405 | plotItem.hideButtons() 406 | return plotItem 407 | 408 | #---------------------------------------------------------------------- 409 | def initplotVol(self): 410 | """初始化成交量子图""" 411 | self.pwVol = self.makePI('_'.join([self.windowId,'PlotVOL'])) 412 | self.volume = CandlestickItem(self.listVol) 413 | self.pwVol.addItem(self.volume) 414 | self.pwVol.setMaximumHeight(150) 415 | self.pwVol.setXLink('_'.join([self.windowId,'PlotOI'])) 416 | self.pwVol.hideAxis('bottom') 417 | 418 | self.lay_KL.nextRow() 419 | self.lay_KL.addItem(self.pwVol) 420 | 421 | #---------------------------------------------------------------------- 422 | def initplotKline(self): 423 | """初始化K线子图""" 424 | self.pwKL = self.makePI('_'.join([self.windowId,'PlotKL'])) 425 | self.candle = CandlestickItem(self.listBar) 426 | self.pwKL.addItem(self.candle) 427 | self.pwKL.setMinimumHeight(350) 428 | self.pwKL.setXLink('_'.join([self.windowId,'PlotOI'])) 429 | self.pwKL.hideAxis('bottom') 430 | 431 | self.lay_KL.nextRow() 432 | self.lay_KL.addItem(self.pwKL) 433 | 434 | #---------------------------------------------------------------------- 435 | def initplotOI(self): 436 | """初始化持仓量子图""" 437 | self.pwOI = self.makePI('_'.join([self.windowId,'PlotOI'])) 438 | self.curveOI = self.pwOI.plot() 439 | 440 | self.lay_KL.nextRow() 441 | self.lay_KL.addItem(self.pwOI) 442 | 443 | #---------------------------------------------------------------------- 444 | # 画图相关 445 | #---------------------------------------------------------------------- 446 | def plotVol(self,redraw=False,xmin=0,xmax=-1): 447 | """重画成交量子图""" 448 | if self.initCompleted: 449 | self.volume.generatePicture(self.listVol[xmin:xmax],redraw) # 画成交量子图 450 | 451 | #---------------------------------------------------------------------- 452 | def plotKline(self,redraw=False,xmin=0,xmax=-1): 453 | """重画K线子图""" 454 | if self.initCompleted: 455 | self.candle.generatePicture(self.listBar[xmin:xmax],redraw) # 画K线 456 | self.plotMark() # 显示开平仓信号位置 457 | 458 | #---------------------------------------------------------------------- 459 | def plotOI(self,xmin=0,xmax=-1): 460 | """重画持仓量子图""" 461 | if self.initCompleted: 462 | self.curveOI.setData(np.append(self.listOpenInterest[xmin:xmax],0), pen='w', name="OpenInterest") 463 | 464 | #---------------------------------------------------------------------- 465 | def addSig(self,sig,main=True): 466 | """新增信号图""" 467 | if main: 468 | if sig in self.sigPlots: 469 | self.pwKL.removeItem(self.sigPlots[sig]) 470 | self.sigPlots[sig] = self.pwKL.plot() 471 | self.sigColor[sig] = self.allColor[0] 472 | self.allColor.append(self.allColor.popleft()) 473 | else: 474 | if sig in self.subSigPlots: 475 | self.pwOI.removeItem(self.subSigPlots[sig]) 476 | self.subSigPlots[sig] = self.pwOI.plot() 477 | self.subSigColor[sig] = self.allSubColor[0] 478 | self.allSubColor.append(self.allSubColor.popleft()) 479 | 480 | #---------------------------------------------------------------------- 481 | def showSig(self,datas,main=True,clear=False): 482 | """刷新信号图""" 483 | if clear: 484 | self.clearSig(main) 485 | if datas and not main: 486 | sigDatas = np.array(datas.values()[0]) 487 | self.listOpenInterest = sigDatas 488 | self.datas['openInterest'] = sigDatas 489 | self.plotOI(0,len(sigDatas)) 490 | if main: 491 | for sig in datas: 492 | self.addSig(sig,main) 493 | self.sigData[sig] = datas[sig] 494 | self.sigPlots[sig].setData(np.append(datas[sig],0), pen=self.sigColor[sig][0], name=sig) 495 | else: 496 | for sig in datas: 497 | self.addSig(sig,main) 498 | self.subSigData[sig] = datas[sig] 499 | self.subSigPlots[sig].setData(np.append(datas[sig],0), pen=self.subSigColor[sig][0], name=sig) 500 | 501 | #---------------------------------------------------------------------- 502 | def plotMark(self): 503 | """显示开平仓信号""" 504 | # 检查是否有数据 505 | if len(self.datas)==0: 506 | return 507 | for arrow in self.arrows: 508 | self.pwKL.removeItem(arrow) 509 | # 画买卖信号 510 | for i in range(len(self.listSig)): 511 | # 无信号 512 | if self.listSig[i] == 0: 513 | continue 514 | # 买信号 515 | elif self.listSig[i] > 0: 516 | arrow = pg.ArrowItem(pos=(i, self.datas[i]['low']), angle=90, brush=(255, 0, 0)) 517 | # 卖信号 518 | elif self.listSig[i] < 0: 519 | arrow = pg.ArrowItem(pos=(i, self.datas[i]['high']), angle=-90, brush=(0, 255, 0)) 520 | self.pwKL.addItem(arrow) 521 | self.arrows.append(arrow) 522 | 523 | #---------------------------------------------------------------------- 524 | def updateAll(self): 525 | """ 526 | 手动更新所有K线图形,K线播放模式下需要 527 | """ 528 | datas = self.datas 529 | self.volume.pictrue = None 530 | self.candle.pictrue = None 531 | self.volume.update() 532 | self.candle.update() 533 | def update(view,low,high): 534 | vRange = view.viewRange() 535 | xmin = max(0,int(vRange[0][0])) 536 | xmax = max(0,int(vRange[0][1])) 537 | try: 538 | xmax = min(xmax,len(datas)-1) 539 | except: 540 | xmax = xmax 541 | if len(datas)>0 and xmax > xmin: 542 | ymin = min(datas[xmin:xmax][low]) 543 | ymax = max(datas[xmin:xmax][high]) 544 | view.setRange(yRange = (ymin,ymax)) 545 | else: 546 | view.setRange(yRange = (0,1)) 547 | update(self.pwKL.getViewBox(),'low','high') 548 | update(self.pwVol.getViewBox(),'volume','volume') 549 | 550 | #---------------------------------------------------------------------- 551 | def plotAll(self,redraw=True,xMin=0,xMax=-1): 552 | """ 553 | 重画所有界面 554 | redraw :False=重画最后一根K线; True=重画所有 555 | xMin,xMax : 数据范围 556 | """ 557 | xMax = len(self.datas)-1 if xMax < 0 else xMax 558 | #self.countK = xMax-xMin 559 | #self.index = int((xMax+xMin)/2) 560 | self.pwOI.setLimits(xMin=xMin,xMax=xMax) 561 | self.pwKL.setLimits(xMin=xMin,xMax=xMax) 562 | self.pwVol.setLimits(xMin=xMin,xMax=xMax) 563 | self.plotKline(redraw,xMin,xMax) # K线图 564 | self.plotVol(redraw,xMin,xMax) # K线副图,成交量 565 | self.plotOI(0,len(self.datas)) # K线副图,持仓量 566 | self.refresh() 567 | 568 | #---------------------------------------------------------------------- 569 | def refresh(self): 570 | """ 571 | 刷新三个子图的现实范围 572 | """ 573 | datas = self.datas 574 | minutes = int(self.countK/2) 575 | xmin = max(0,self.index-minutes) 576 | try: 577 | xmax = min(xmin+2*minutes,len(self.datas)-1) if self.datas else xmin+2*minutes 578 | except: 579 | xmax = xmin+2*minutes 580 | self.pwOI.setRange(xRange = (xmin,xmax)) 581 | self.pwKL.setRange(xRange = (xmin,xmax)) 582 | self.pwVol.setRange(xRange = (xmin,xmax)) 583 | 584 | #---------------------------------------------------------------------- 585 | # 快捷键相关 586 | #---------------------------------------------------------------------- 587 | def onNxt(self): 588 | """跳转到下一个开平仓点""" 589 | if len(self.listSig)>0 and not self.index is None: 590 | datalen = len(self.listSig) 591 | if self.index < datalen-2 : self.index+=1 592 | while self.index < datalen-2 and self.listSig[self.index] == 0: 593 | self.index+=1 594 | self.refresh() 595 | x = self.index 596 | y = self.datas[x]['close'] 597 | self.crosshair.signal.emit((x,y)) 598 | 599 | #---------------------------------------------------------------------- 600 | def onPre(self): 601 | """跳转到上一个开平仓点""" 602 | if len(self.listSig)>0 and not self.index is None: 603 | if self.index > 0: self.index-=1 604 | while self.index > 0 and self.listSig[self.index] == 0: 605 | self.index-=1 606 | self.refresh() 607 | x = self.index 608 | y = self.datas[x]['close'] 609 | self.crosshair.signal.emit((x,y)) 610 | 611 | #---------------------------------------------------------------------- 612 | def onDown(self): 613 | """放大显示区间""" 614 | self.countK = min(len(self.datas),int(self.countK*1.2)+1) 615 | self.refresh() 616 | if len(self.datas)>0: 617 | x = self.index-self.countK/2+2 if int(self.crosshair.xAxis)self.index+self.countK/2-2 else x 619 | x = len(self.datas)-1 if x > len(self.datas)-1 else int(x) 620 | y = self.datas[x][2] 621 | self.crosshair.signal.emit((x,y)) 622 | 623 | #---------------------------------------------------------------------- 624 | def onUp(self): 625 | """缩小显示区间""" 626 | self.countK = max(3,int(self.countK/1.2)-1) 627 | self.refresh() 628 | if len(self.datas)>0: 629 | x = self.index-self.countK/2+2 if int(self.crosshair.xAxis)self.index+self.countK/2-2 else x 631 | x = len(self.datas)-1 if x > len(self.datas)-1 else int(x) 632 | y = self.datas[x]['close'] 633 | self.crosshair.signal.emit((x,y)) 634 | 635 | #---------------------------------------------------------------------- 636 | def onLeft(self): 637 | """向左移动""" 638 | if len(self.datas)>0 and int(self.crosshair.xAxis)>2: 639 | x = int(self.crosshair.xAxis)-1 640 | x = len(self.datas)-1 if x > len(self.datas)-1 else int(x) 641 | y = self.datas[x]['close'] 642 | if x <= self.index-self.countK/2+2 and self.index>1: 643 | self.index -= 1 644 | self.refresh() 645 | self.crosshair.signal.emit((x,y)) 646 | 647 | #---------------------------------------------------------------------- 648 | def onRight(self): 649 | """向右移动""" 650 | if len(self.datas)>0 and int(self.crosshair.xAxis) len(self.datas)-1 else int(x) 653 | y = self.datas[x]['close'] 654 | if x >= self.index+int(self.countK/2)-2: 655 | self.index += 1 656 | self.refresh() 657 | self.crosshair.signal.emit((x,y)) 658 | 659 | #---------------------------------------------------------------------- 660 | # 界面回调相关 661 | #---------------------------------------------------------------------- 662 | def onPaint(self): 663 | """界面刷新回调""" 664 | view = self.pwKL.getViewBox() 665 | vRange = view.viewRange() 666 | xmin = max(0,int(vRange[0][0])) 667 | xmax = max(0,int(vRange[0][1])) 668 | self.index = int((xmin+xmax)/2)+1 669 | 670 | #---------------------------------------------------------------------- 671 | def resignData(self,datas): 672 | """更新数据,用于Y坐标自适应""" 673 | self.crosshair.datas = datas 674 | def viewXRangeChanged(low,high,self): 675 | vRange = self.viewRange() 676 | xmin = max(0,int(vRange[0][0])) 677 | xmax = max(0,int(vRange[0][1])) 678 | xmax = min(xmax,len(datas)) 679 | if len(datas)>0 and xmax > xmin: 680 | ymin = min(datas[xmin:xmax][low]) 681 | ymax = max(datas[xmin:xmax][high]) 682 | ymin,ymax = (-1,1) if ymin==ymax else (ymin,ymax) 683 | self.setRange(yRange = (ymin,ymax)) 684 | else: 685 | self.setRange(yRange = (0,1)) 686 | 687 | view = self.pwKL.getViewBox() 688 | view.sigXRangeChanged.connect(partial(viewXRangeChanged,'low','high')) 689 | 690 | view = self.pwVol.getViewBox() 691 | view.sigXRangeChanged.connect(partial(viewXRangeChanged,'volume','volume')) 692 | 693 | view = self.pwOI.getViewBox() 694 | view.sigXRangeChanged.connect(partial(viewXRangeChanged,'openInterest','openInterest')) 695 | 696 | #---------------------------------------------------------------------- 697 | # 数据相关 698 | #---------------------------------------------------------------------- 699 | def clearData(self): 700 | """清空数据""" 701 | # 清空数据,重新画图 702 | self.time_index = [] 703 | self.listBar = [] 704 | self.listVol = [] 705 | self.listLow = [] 706 | self.listHigh = [] 707 | self.listOpenInterest = [] 708 | self.listSig = [] 709 | self.sigData = {} 710 | self.datas = None 711 | 712 | #---------------------------------------------------------------------- 713 | def clearSig(self,main=True): 714 | """清空信号图形""" 715 | # 清空信号图 716 | if main: 717 | for sig in self.sigPlots: 718 | self.pwKL.removeItem(self.sigPlots[sig]) 719 | self.sigData = {} 720 | self.sigPlots = {} 721 | else: 722 | for sig in self.subSigPlots: 723 | self.pwOI.removeItem(self.subSigPlots[sig]) 724 | self.subSigData = {} 725 | self.subSigPlots = {} 726 | 727 | #---------------------------------------------------------------------- 728 | def updateSig(self,sig): 729 | """刷新买卖信号""" 730 | self.listSig = sig 731 | self.plotMark() 732 | 733 | #---------------------------------------------------------------------- 734 | def onBar(self, bar): 735 | """ 736 | 新增K线数据,K线播放模式 737 | """ 738 | # 是否需要更新K线 739 | newBar = False if len(self.datas)>0 and bar.datetime==self.datas[-1].datetime else True 740 | nrecords = len(self.datas) if newBar else len(self.datas)-1 741 | bar.openInterest = np.random.randint(0,3) if bar.openInterest==np.inf or bar.openInterest==-np.inf else bar.openInterest 742 | recordVol = (nrecords,abs(bar.volume),0,0,abs(bar.volume)) if bar.close < bar.open else (nrecords,0,abs(bar.volume),0,abs(bar.volume)) 743 | 744 | if newBar and any(self.datas): 745 | self.datas.resize(nrecords+1,refcheck=0) 746 | self.listBar.resize(nrecords+1,refcheck=0) 747 | self.listVol.resize(nrecords+1,refcheck=0) 748 | elif any(self.datas): 749 | self.listLow.pop() 750 | self.listHigh.pop() 751 | self.listOpenInterest.pop() 752 | if any(self.datas): 753 | self.datas[-1] = (bar.datetime, bar.open, bar.close, bar.low, bar.high, bar.volume, bar.openInterest) 754 | self.listBar[-1] = (nrecords, bar.open, bar.close, bar.low, bar.high) 755 | self.listVol[-1] = recordVol 756 | else: 757 | self.datas = np.rec.array([(bar.datetime, bar.open, bar.close, bar.low, bar.high, bar.volume, bar.openInterest)],\ 758 | names=('datetime','open','close','low','high','volume','openInterest')) 759 | self.listBar = np.rec.array([(nrecords, bar.open, bar.close, bar.low, bar.high)],\ 760 | names=('time_int','open','close','low','high')) 761 | self.listVol = np.rec.array([recordVol],names=('time_int','open','close','low','high')) 762 | self.resignData(self.datas) 763 | 764 | self.axisTime.update_xdict({nrecords:bar.datetime}) 765 | self.listLow.append(bar.low) 766 | self.listHigh.append(bar.high) 767 | self.listOpenInterest.append(bar.openInterest) 768 | self.resignData(self.datas) 769 | return newBar 770 | 771 | #---------------------------------------------------------------------- 772 | def loadData(self, datas, sigs = None): 773 | """ 774 | 载入pandas.DataFrame数据 775 | datas : 数据格式,cols : datetime, open, close, low, high 776 | """ 777 | # 设置中心点时间 778 | # 绑定数据,更新横坐标映射,更新Y轴自适应函数,更新十字光标映射 779 | datas['time_int'] = np.array(range(len(datas.index))) 780 | self.datas = datas[['open','close','low','high','volume','openInterest']].to_records() 781 | self.axisTime.xdict={} 782 | xdict = dict(enumerate(datas.index.tolist())) 783 | self.axisTime.update_xdict(xdict) 784 | self.resignData(self.datas) 785 | # 更新画图用到的数据 786 | self.listBar = datas[['time_int','open','close','low','high']].to_records(False) 787 | self.listHigh = list(datas['high']) 788 | self.listLow = list(datas['low']) 789 | self.listOpenInterest = list(datas['openInterest']) 790 | self.listSig = [0]*(len(self.datas)-1) if sigs is None else sigs 791 | # 成交量颜色和涨跌同步,K线方向由涨跌决定 792 | datas0 = pd.DataFrame() 793 | datas0['open'] = datas.apply(lambda x:0 if x['close'] >= x['open'] else x['volume'],axis=1) 794 | datas0['close'] = datas.apply(lambda x:0 if x['close'] < x['open'] else x['volume'],axis=1) 795 | datas0['low'] = 0 796 | datas0['high'] = datas['volume'] 797 | datas0['time_int'] = np.array(range(len(datas.index))) 798 | self.listVol = datas0[['time_int','open','close','low','high']].to_records(False) 799 | 800 | #---------------------------------------------------------------------- 801 | def refreshAll(self, redraw=True, update=False): 802 | """ 803 | 更新所有界面 804 | """ 805 | # 调用画图函数 806 | self.index = len(self.datas) 807 | self.plotAll(redraw,0,len(self.datas)) 808 | if not update: 809 | self.updateAll() 810 | self.crosshair.signal.emit((None,None)) 811 | 812 | ######################################################################## 813 | # 功能测试 814 | ######################################################################## 815 | import sys 816 | if __name__ == '__main__': 817 | app = QApplication(sys.argv) 818 | # 界面设置 819 | cfgfile = QtCore.QFile('css.qss') 820 | cfgfile.open(QtCore.QFile.ReadOnly) 821 | styleSheet = cfgfile.readAll() 822 | styleSheet = unicode(styleSheet, encoding='utf8') 823 | app.setStyleSheet(styleSheet) 824 | # K线界面 825 | ui = KLineWidget() 826 | ui.show() 827 | ui.KLtitle.setText('rb1701',size='20pt') 828 | ui.loadData(pd.DataFrame.from_csv('data.csv')) 829 | ui.refreshAll() 830 | app.exec_() 831 | -------------------------------------------------------------------------------- /uiKLineTool.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import sip 3 | sip.setapi("QString", 2) 4 | sip.setapi("QVariant", 2) 5 | import sys 6 | reload(sys) 7 | sys.setdefaultencoding('utf8') 8 | import os 9 | os.environ['QT_API'] = 'pyqt' 10 | from uiBasicIO import uiBasicIO 11 | from uiKLine import KLineWidget 12 | # PyQt 13 | from qtpy.QtGui import * 14 | from qtpy.QtWidgets import * 15 | from qtpy.QtCore import * 16 | from qtpy import QtGui,QtCore 17 | 18 | 19 | import pandas as pd 20 | 21 | 22 | 23 | ######################################################################## 24 | class uiKLineTool(uiBasicIO): 25 | """K线回测分析工具""" 26 | 27 | dbClient = None 28 | signal = QtCore.Signal(type({})) 29 | 30 | #---------------------------------------------------------------------- 31 | def __init__(self,parent=None): 32 | """初始化函数""" 33 | super(uiKLineTool,self).__init__(parent,\ 34 | 'json\\uiKLine_input.json',\ 35 | 'json\\uiKLine_button.json') # 输入配置文件,按钮配置文件 36 | 37 | # 用于计算因子的数据 38 | self.bars = [] 39 | self.pdBars = pd.DataFrame() 40 | self.signals = [] 41 | self.signalsOpen = [] 42 | self.states = [] 43 | self.stateData = {} 44 | 45 | self.datas = None 46 | self.vtSymbol = "" 47 | self.vtSymbol1 = "" 48 | self.mode = 'deal' 49 | 50 | self.canvas = KLineWidget() 51 | self.signal.connect(self.loadData) 52 | 53 | self.initUi() 54 | 55 | 56 | #----------------------------------------------- 57 | def loadData(self,data): 58 | """加载数据""" 59 | self.signals = data['deal'] 60 | self.signalsOpen = data['dealOpen'] 61 | kTool = self.canvas 62 | for sig in kTool.sigPlots: 63 | kTool.pwKL.removeItem(kTool.sigPlots[sig]) 64 | kTool.sigData = {} 65 | kTool.sigPlots = {} 66 | for sig in kTool.subSigPlots: 67 | kTool.pwOI.removeItem(kTool.subSigPlots[sig]) 68 | kTool.subSigData = {} 69 | kTool.subSigPlots = {} 70 | print u'正在准备回测结果数据....' 71 | self.canvas.clearData() 72 | self.pdBars = data[['open','close','low','high','volume','openInterest']] 73 | self.canvas.loadData(self.pdBars) 74 | self.canvas.updateSig(self.signals) 75 | barinfo = ['datetime','open','close','low','high','volume','openInterest','deal','dealOpen'] 76 | allState = [k for k in data if not k in barinfo] 77 | self.stateData = data[allState].to_records() 78 | self.editDict['signalName'].clear() 79 | self.editDict['signalName'].addItems(allState) 80 | print u'数据准备完成!' 81 | 82 | #---------------------------------------------------------------------- 83 | def initUi(self): 84 | """初始化界面""" 85 | hbox = QHBoxLayout() 86 | hbox.addWidget(self.groupInput) 87 | hbox.addWidget(self.groupProcess) 88 | vbox = QVBoxLayout() 89 | vbox.addLayout(hbox) 90 | vbox.addWidget(self.canvas) 91 | self.setLayout(vbox) 92 | 93 | #---------------------------------------------------------------------- 94 | def setSymbol(self, symbol): 95 | """设置合约信息""" 96 | self.vtSymbol = symbol 97 | 98 | ######################################################################## 99 | import sys 100 | if __name__ == '__main__': 101 | app = QApplication(sys.argv) 102 | # 界面设置 103 | cfgfile = QtCore.QFile('css.qss') 104 | cfgfile.open(QtCore.QFile.ReadOnly) 105 | styleSheet = cfgfile.readAll() 106 | styleSheet = unicode(styleSheet, encoding='utf8') 107 | app.setStyleSheet(styleSheet) 108 | # K线界面 109 | ui = uiKLineTool() 110 | ui.show() 111 | app.exec_() 112 | -------------------------------------------------------------------------------- /启动界面.cmd: -------------------------------------------------------------------------------- 1 | @python -u uiKLineTool.py 2 | pause 3 | --------------------------------------------------------------------------------