├── .gitignore ├── DevilYuan.jpg ├── DevilYuan.pyproj ├── DevilYuan.sln ├── DyCommon ├── DyCommon.py ├── DyScheduler.py ├── DyTalib.py ├── Ui │ ├── DyBasicMainWindow.py │ ├── DyCodeDateDlg.py │ ├── DyDataFrameTableWidget.py │ ├── DyDataFrameWindow.py │ ├── DyDateDlg.py │ ├── DyInfoDlg.py │ ├── DyLogWidget.py │ ├── DyProcessNbrDlg.py │ ├── DyProgressWidget.py │ ├── DySingleEditDlg.py │ ├── DyStatsDataFrameTableWidget.py │ ├── DyStatsTableWidget.py │ ├── DySubInfoWidget.py │ ├── DyTableWidget.py │ ├── DyTreeWidget.py │ └── __init__.py └── __init__.py ├── DyMainWindow.py ├── EventEngine ├── DyEvent.py ├── DyEventEngine.py └── __init__.py ├── LICENSE ├── README.md ├── Stock ├── BackTesting │ ├── DyStockBackTestingCommon.py │ ├── Engine │ │ ├── DyStockBackTestingAccountManager.py │ │ ├── DyStockBackTestingMainEngine.py │ │ ├── Strategy │ │ │ ├── DyStockBackTestingCtaEngine.py │ │ │ ├── DyStockBackTestingStrategyEngine.py │ │ │ ├── DyStockBackTestingStrategyEngineProcess.py │ │ │ ├── DyStockBackTestingStrategyEngineProxy.py │ │ │ └── __init__.py │ │ └── __init__.py │ ├── Ui │ │ ├── Basic │ │ │ ├── DyStockBackTestingResultWidget.py │ │ │ ├── DyStockBackTestingStrategyWidget.py │ │ │ ├── Strategy │ │ │ │ ├── DyStockBackTestingStrategyParamGroupResultWidget.py │ │ │ │ ├── DyStockBackTestingStrategyPeriodResultWidget.py │ │ │ │ ├── DyStockBackTestingStrategyPeriodsResultWidget.py │ │ │ │ ├── DyStockBackTestingStrategyResultDealsWidget.py │ │ │ │ ├── DyStockBackTestingStrategyResultPositionWidget.py │ │ │ │ ├── DyStockBackTestingStrategyResultStatsWidget.py │ │ │ │ ├── DyStockBackTestingStrategyResultWidget.py │ │ │ │ ├── Other │ │ │ │ │ ├── DyStockBackTestingStrategyResultOverviewWindow.py │ │ │ │ │ └── __init__.py │ │ │ │ └── __init__.py │ │ │ └── __init__.py │ │ ├── DyStockBackTestingMainWindow.py │ │ ├── Other │ │ │ ├── DyStockBackTestingSettingDlg.py │ │ │ └── __init__.py │ │ └── __init__.py │ └── __init__.py ├── Common │ ├── DyStockCommon.py │ ├── Ui │ │ ├── Basic │ │ │ ├── Dlg │ │ │ │ ├── DyStockIndustryCompareDlg.py │ │ │ │ ├── DyStockInfoDlg.py │ │ │ │ ├── DyStockTableAddColumnsDlg.py │ │ │ │ ├── DyStockTableColumnOperateDlg.py │ │ │ │ ├── DyStockTableFilterDlg.py │ │ │ │ ├── DyStockTableSelectDlg.py │ │ │ │ ├── DyStockVolatilityDistDlg.py │ │ │ │ └── __init__.py │ │ │ ├── DyStockTableWidget.py │ │ │ ├── Other │ │ │ │ ├── DyStockIndustryCompareWindow.py │ │ │ │ └── __init__.py │ │ │ └── __init__.py │ │ ├── Deal │ │ │ ├── Basic │ │ │ │ ├── DyStockDealDetailsInfoWidget.py │ │ │ │ ├── DyStockDealDetailsWidget.py │ │ │ │ └── __init__.py │ │ │ ├── DyStockDealDetailsMainWindow.py │ │ │ └── __init__.py │ │ ├── DyStockMaViewerIndicatorMenu.py │ │ └── __init__.py │ └── __init__.py ├── Config │ ├── DyStockConfig.py │ ├── DyStockHistDaysDataSourceConfigDlg.py │ ├── DyStockMongoDbConfigDlg.py │ ├── Trade │ │ ├── DyStockAccountConfigDlg.py │ │ ├── DyStockWxScKeyConfigDlg.py │ │ └── __init__.py │ └── __init__.py ├── Data │ ├── DyStockDataCommon.py │ ├── Engine │ │ ├── Common │ │ │ ├── DyStockDataCodeTable.py │ │ │ ├── DyStockDataCommonEngine.py │ │ │ ├── DyStockDataSectorCodeTable.py │ │ │ ├── DyStockDataTradeDayTable.py │ │ │ └── __init__.py │ │ ├── DyStockDataDaysEngine.py │ │ ├── DyStockDataEngine.py │ │ ├── DyStockDataMainEngine.py │ │ ├── DyStockDataStrategyDataPrepareEngine.py │ │ ├── DyStockDataTicksEngine.py │ │ ├── DyStockDbCache.py │ │ ├── DyStockMongoDbEngine.py │ │ └── __init__.py │ ├── Gateway │ │ ├── DyStockDataGateway.py │ │ ├── DyStockDataTdx.py │ │ ├── DyStockDataTicksGateway.py │ │ ├── DyStockDataWind.py │ │ └── __init__.py │ ├── Ui │ │ ├── DyStockDataMainWindow.py │ │ ├── Other │ │ │ ├── DyStockDataHistDaysManualUpdateDlg.py │ │ │ ├── DyStockDataHistTicksVerifyDlg.py │ │ │ ├── DyStockDataStrategyDataPrepareDlg.py │ │ │ └── __init__.py │ │ └── __init__.py │ ├── Utility │ │ ├── DyStockDataAssembler.py │ │ ├── DyStockDataML.py │ │ ├── DyStockDataSpider.py │ │ ├── DyStockDataUtility.py │ │ ├── Other │ │ │ ├── DyStockDataFocusAnalysisUtility.py │ │ │ └── __init__.py │ │ └── __init__.py │ ├── Viewer │ │ ├── DyStockDataViewer.py │ │ ├── DyStockDataWindow.py │ │ ├── FocusAnalysis │ │ │ ├── DyStockDataFocusAnalysisMainWindow.py │ │ │ ├── DyStockDataFocusAnalysisWidget.py │ │ │ ├── DyStockDataFocusInfoPoolWidget.py │ │ │ └── __init__.py │ │ ├── IndexConsecutiveDayLineStats │ │ │ ├── DyStockDataIndexConsecutiveDayLineStatsTabWidget.py │ │ │ ├── DyStockDataIndexConsecutiveDayLineStatsWidget.py │ │ │ └── __init__.py │ │ ├── JaccardIndex │ │ │ ├── DyStockDataJaccardIndexCodeSetDetailsWidget.py │ │ │ ├── DyStockDataJaccardIndexCodeSetWidget.py │ │ │ ├── DyStockDataJaccardIndexCodeSetWidgets.py │ │ │ ├── DyStockDataJaccardIndexMainWindow.py │ │ │ ├── DyStockDataJaccardIndexPlotDlg.py │ │ │ ├── DyStockDataJaccardIndexWidget.py │ │ │ ├── DyStockDataJaccardIndexWidgets.py │ │ │ └── __init__.py │ │ ├── LimitUpStats │ │ │ ├── DyStockDataLimitUpStatsMainWindow.py │ │ │ └── __init__.py │ │ └── __init__.py │ └── __init__.py ├── Select │ ├── DyStockSelectCommon.py │ ├── Engine │ │ ├── DyStockSelectMainEngine.py │ │ ├── DyStockSelectSelectEngine.py │ │ ├── DyStockSelectViewerEngine.py │ │ ├── Regression │ │ │ ├── DyStockSelectRegressionEngine.py │ │ │ ├── DyStockSelectRegressionEngineProcess.py │ │ │ ├── DyStockSelectRegressionEngineProxy.py │ │ │ └── __init__.py │ │ └── __init__.py │ ├── Strategy │ │ ├── Cta │ │ │ ├── DySS_BBands.py │ │ │ ├── DySS_HighTurn.py │ │ │ ├── DySS_InsideRedLine.py │ │ │ ├── DySS_LimitUpRise.py │ │ │ ├── DySS_MaWalk.py │ │ │ ├── DySS_MacdBottomDeviation.py │ │ │ ├── DySS_MasAttack.py │ │ │ ├── DySS_MasLong.py │ │ │ ├── DySS_NeedleBottom.py │ │ │ ├── DySS_OverSold.py │ │ │ ├── DySS_PlatformUpBreakThrough.py │ │ │ ├── DySS_Rebound.py │ │ │ ├── DySS_RisingEve.py │ │ │ ├── DySS_Stagflation.py │ │ │ ├── DySS_StrongBack.py │ │ │ ├── DySS_SwingTrough.py │ │ │ ├── DySS_Th666.py │ │ │ └── __init__.py │ │ ├── DyStockSelectStrategyTemplate.py │ │ ├── Fundamental │ │ │ ├── DySS_GrowingStocks.py │ │ │ ├── DySS_High2SendStocks.py │ │ │ └── __init__.py │ │ ├── Other │ │ │ ├── DySS_ETF.py │ │ │ └── __init__.py │ │ ├── Stats │ │ │ ├── DySS_AbnormalVolatility.py │ │ │ ├── DySS_ChipDist.py │ │ │ ├── DySS_Correlation.py │ │ │ ├── DySS_LimitUp.py │ │ │ ├── DySS_LimitUpAnalysis.py │ │ │ ├── DySS_LimitUpPredict.py │ │ │ ├── DySS_Pairs.py │ │ │ ├── DySS_UpDownList.py │ │ │ └── __init__.py │ │ └── __init__.py │ ├── Ui │ │ ├── Basic │ │ │ ├── Dlg │ │ │ │ ├── DyStockSelectAddColumnsDlg.py │ │ │ │ ├── DyStockSelectFilterDlg.py │ │ │ │ ├── DyStockSelectIndustryCompareDlg.py │ │ │ │ ├── DyStockSelectRefactoryParamsDlg.py │ │ │ │ ├── DyStockSelectSaveAsDlg.py │ │ │ │ ├── DyStockSelectStockInfoDlg.py │ │ │ │ ├── DyStockSelectVolatilityDistDlg.py │ │ │ │ └── __init__.py │ │ │ ├── DyStockSelectStrategyWidget.py │ │ │ ├── Other │ │ │ │ ├── DyStockSelectItemMenu.py │ │ │ │ └── __init__.py │ │ │ ├── Param │ │ │ │ ├── DyStockSelectParamWidget.py │ │ │ │ ├── DyStockSelectStrategyParamWidget.py │ │ │ │ └── __init__.py │ │ │ ├── Regression │ │ │ │ ├── DyStockSelectRegressionResultWidget.py │ │ │ │ ├── DyStockSelectStrategyRegressionPeriodResultWidget.py │ │ │ │ ├── DyStockSelectStrategyRegressionResultWidget.py │ │ │ │ └── __init__.py │ │ │ ├── Select │ │ │ │ ├── DyStockSelectSelectResultWidget.py │ │ │ │ ├── DyStockSelectStrategySelectResultWidget.py │ │ │ │ ├── DyStockSelectStrategySelectResultWidget1.py │ │ │ │ └── __init__.py │ │ │ └── __init__.py │ │ ├── DyStockSelectMainWindow.py │ │ ├── Other │ │ │ ├── DyStockSelectBBandsStatsDlg.py │ │ │ ├── DyStockSelectDayKChartPeriodDlg.py │ │ │ ├── DyStockSelectIndexMaKChartStatsDlg.py │ │ │ ├── DyStockSelectIndexMaStatsDlg.py │ │ │ ├── DyStockSelectJaccardIndexDlg.py │ │ │ ├── DyStockSelectTestedStocksDlg.py │ │ │ └── __init__.py │ │ └── __init__.py │ └── __init__.py ├── Trade │ ├── AccountManager │ │ ├── Broker │ │ │ ├── DyStockGtjaAccountManager.py │ │ │ ├── DyStockSimuAccountManager.py │ │ │ ├── DyStockThsAccountManager.py │ │ │ ├── DyStockYhAccountManager.py │ │ │ └── __init__.py │ │ ├── DyStockAccountManager.py │ │ ├── DyStockPos.py │ │ ├── StopMode │ │ │ ├── DyStockStopLossMaMode.py │ │ │ ├── DyStockStopLossPnlRatioMode.py │ │ │ ├── DyStockStopLossStepMode.py │ │ │ ├── DyStockStopMode.py │ │ │ ├── DyStockStopProfitMaMode.py │ │ │ ├── DyStockStopProfitPnlRatioMode.py │ │ │ ├── DyStockStopTimeMode.py │ │ │ └── __init__.py │ │ └── __init__.py │ ├── Broker │ │ ├── DyStockTradeBrokerEngine.py │ │ ├── DyTrader.py │ │ ├── Simu │ │ │ ├── SimuTrader.py │ │ │ └── __init__.py │ │ ├── ThirdLibrary │ │ │ ├── __init__.py │ │ │ ├── getcode_jdk1.5.jar │ │ │ └── yjb_verify_code.jar │ │ ├── Ths │ │ │ ├── ThsTrader.py │ │ │ ├── ThsUiTrader.py │ │ │ ├── __init__.py │ │ │ └── pop_dialog_handler.py │ │ ├── UiTrader.py │ │ ├── WebTrader.py │ │ ├── YhNew │ │ │ ├── YhTrader.py │ │ │ ├── YhUiTrader.py │ │ │ ├── __init__.py │ │ │ ├── clienttrader.py │ │ │ ├── config │ │ │ │ ├── __init__.py │ │ │ │ └── client.py │ │ │ ├── exceptions.py │ │ │ ├── helpers.py │ │ │ └── log.py │ │ └── __init__.py │ ├── DyStockStrategyBase.py │ ├── DyStockTradeCommon.py │ ├── Engine │ │ ├── DyStockTradeMainEngine.py │ │ └── __init__.py │ ├── Market │ │ ├── DyStockMarketEngine.py │ │ ├── DyStockMarketFilter.py │ │ ├── DyStockSinaQuotation.py │ │ └── __init__.py │ ├── Strategy │ │ ├── Cta │ │ │ ├── DyST_AbnormalVolatility.py │ │ │ ├── DyST_BankIntraDaySpread.py │ │ │ ├── DyST_IntraDayT.py │ │ │ ├── DyST_LimitUpDownMonitor.py │ │ │ ├── DyST_MaWalk.py │ │ │ ├── DyST_TraceFocus.py │ │ │ └── __init__.py │ │ ├── DyStockCtaBase.py │ │ ├── DyStockCtaEngine.py │ │ ├── DyStockCtaEngineExtra.py │ │ ├── DyStockCtaTemplate.py │ │ └── __init__.py │ ├── Ui │ │ ├── Basic │ │ │ ├── Account │ │ │ │ ├── DyStockTradeAccountWidget.py │ │ │ │ ├── DyStockTradeBrokerAccountWidget.py │ │ │ │ ├── DyStockTradeCapitalWidget.py │ │ │ │ ├── DyStockTradeCurDealsWidget.py │ │ │ │ ├── DyStockTradeCurEntrustsWidget.py │ │ │ │ ├── DyStockTradePositionWidget.py │ │ │ │ └── __init__.py │ │ │ ├── DyStockMarketIndexMonitorWidget.py │ │ │ ├── DyStockMarketStrengthWidget.py │ │ │ ├── DyStockTradeStrategyWidget.py │ │ │ ├── StrategyMarket │ │ │ │ ├── Data │ │ │ │ │ ├── DyStockTradeStrategyMarketMonitorDataWidget.py │ │ │ │ │ └── __init__.py │ │ │ │ ├── DyStockTradeStrategiesMarketMonitorWidget.py │ │ │ │ ├── DyStockTradeStrategyMarketMonitorWidget.py │ │ │ │ ├── Ind │ │ │ │ │ ├── Account │ │ │ │ │ │ ├── DyStockTradeStrategyAccountWidget.py │ │ │ │ │ │ ├── DyStockTradeStrategyDealsWidget.py │ │ │ │ │ │ ├── DyStockTradeStrategyEntrustsWidget.py │ │ │ │ │ │ ├── DyStockTradeStrategyPosWidget.py │ │ │ │ │ │ └── __init__.py │ │ │ │ │ ├── DyStockTradeStrategyMarketMonitorIndWidget.py │ │ │ │ │ ├── DyStockTradeStrategyMarketMonitorOpWidget.py │ │ │ │ │ ├── DyStockTradeStrategyMarketMonitorSignalDetailsWidget.py │ │ │ │ │ └── __init__.py │ │ │ │ ├── Other │ │ │ │ │ ├── DyStockTradeStrategyBuyDlg.py │ │ │ │ │ ├── DyStockTradeStrategySellDlg.py │ │ │ │ │ └── __init__.py │ │ │ │ └── __init__.py │ │ │ └── __init__.py │ │ ├── DyStockTradeMainWindow.py │ │ ├── DyStockTradeOneKeyHangUp.py │ │ └── __init__.py │ ├── WeChat │ │ ├── DyStockTradeWxEngine.py │ │ ├── __init__.py │ │ └── wxbot.py │ └── __init__.py └── __init__.py └── docs ├── backtesting ├── config.png ├── result.png └── resultDetails.png ├── brief_introduction.pdf ├── config ├── Config.md └── Config_TuSharePro.png ├── data ├── DownloadHistoryData.md ├── mannualDaysConfig.png ├── mannualTicksConfig.png └── result.png ├── main.png ├── select └── result.png └── trade ├── WriteATradeStrategy.md ├── strategyPath.png ├── strategyUi.png ├── trade.png ├── trade_xmind.png └── yhLoginUI.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | .vs/ 3 | .vscode/ 4 | *.csv -------------------------------------------------------------------------------- /DevilYuan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/DevilYuan.jpg -------------------------------------------------------------------------------- /DevilYuan.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27004.2010 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "DevilYuan", "DevilYuan.pyproj", "{1FD641C0-E2DF-4A50-B5B2-91880B64BA01}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {1FD641C0-E2DF-4A50-B5B2-91880B64BA01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {1FD641C0-E2DF-4A50-B5B2-91880B64BA01}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | GlobalSection(ExtensibilityGlobals) = postSolution 21 | SolutionGuid = {86A1D936-F0E4-4C38-932B-D5C90E93CF8A} 22 | EndGlobalSection 23 | EndGlobal 24 | -------------------------------------------------------------------------------- /DyCommon/DyScheduler.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import time 3 | from datetime import datetime 4 | from collections import namedtuple 5 | 6 | 7 | class DyScheduler: 8 | """ 9 | 任务调度器 10 | 由于APScheduler可能会发生Job missed的情况,所以自己写一个。 11 | !!!不支持一天开始和结束的几分钟。 12 | """ 13 | Job = namedtuple('Job', 'job dayOfWeek timeOfDay') 14 | precision = 60*2 # 2 minutes 15 | 16 | 17 | def __init__(self): 18 | self._jobs = [] 19 | 20 | self._active = False 21 | self._hand = None 22 | self._preTime = None 23 | 24 | def addJob(self, job, dayOfWeek, timeOfDay): 25 | """ 26 | @job: job的处理函数 27 | @dayOfWeek: set, like {1, 2, 3, 4, 5, 6, 7} 28 | @timeOfDay: string, like '18:31:00' 29 | """ 30 | self._jobs.append(self.Job(job, dayOfWeek, timeOfDay)) 31 | 32 | def _run(self): 33 | while self._active: 34 | now = datetime.now() 35 | dayOfWeek = now.weekday() + 1 36 | curTime = now.strftime('%H:%M:%S') 37 | 38 | if self._preTime is not None: 39 | for job in self._jobs: 40 | if dayOfWeek in job.dayOfWeek and self._preTime <= job.timeOfDay < curTime: 41 | job.job() 42 | 43 | self._preTime = curTime 44 | 45 | time.sleep(self.precision) 46 | 47 | def start(self): 48 | self._active = True 49 | 50 | self._hand = threading.Thread(target=self._run) 51 | self._hand.start() 52 | 53 | def shutdown(self): 54 | self._active = False 55 | 56 | self._hand.join() 57 | 58 | self._hand = None 59 | self._preTime = None -------------------------------------------------------------------------------- /DyCommon/Ui/DyCodeDateDlg.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from PyQt5.QtWidgets import QDialog, QGridLayout, QLabel, QLineEdit, QPushButton 4 | 5 | 6 | class DyCodeDateDlg(QDialog): 7 | 8 | def __init__(self, codeLabelText, data, parent=None): 9 | super().__init__(parent) 10 | 11 | self._data = data 12 | 13 | self._initUi(codeLabelText) 14 | 15 | def _initUi(self, codeLabelText): 16 | self.setWindowTitle('代码日期') 17 | 18 | # 控件 19 | codeLable = QLabel(codeLabelText) 20 | self._codeLineEdit = QLineEdit() 21 | codes = self._data.get('codes') 22 | if codes: 23 | self._codeLineEdit.setText(','.join(codes)) 24 | 25 | startDateLable = QLabel('开始日期') 26 | self._startDateLineEdit = QLineEdit(datetime.now().strftime("%Y-%m-%d")) 27 | 28 | endDateLable = QLabel('结束日期') 29 | self._endDateLineEdit = QLineEdit(datetime.now().strftime("%Y-%m-%d")) 30 | 31 | cancelPushButton = QPushButton('Cancel') 32 | okPushButton = QPushButton('OK') 33 | cancelPushButton.clicked.connect(self._cancel) 34 | okPushButton.clicked.connect(self._ok) 35 | 36 | # 布局 37 | grid = QGridLayout() 38 | grid.setSpacing(10) 39 | 40 | grid.addWidget(startDateLable, 0, 0) 41 | grid.addWidget(self._startDateLineEdit, 1, 0) 42 | 43 | grid.addWidget(endDateLable, 0, 1) 44 | grid.addWidget(self._endDateLineEdit, 1, 1) 45 | 46 | grid.addWidget(codeLable, 2, 0, 1, 2) 47 | grid.addWidget(self._codeLineEdit, 3, 0, 1, 2) 48 | 49 | grid.addWidget(okPushButton, 4, 1) 50 | grid.addWidget(cancelPushButton, 4, 0) 51 | 52 | self.setLayout(grid) 53 | 54 | def _ok(self): 55 | self._data['startDate'] = self._startDateLineEdit.text() 56 | self._data['endDate'] = self._endDateLineEdit.text() 57 | 58 | codes = self._codeLineEdit.text() 59 | self._data['codes'] = codes.split(',') if codes else None 60 | 61 | self.accept() 62 | 63 | def _cancel(self): 64 | self.reject() 65 | -------------------------------------------------------------------------------- /DyCommon/Ui/DyDataFrameTableWidget.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtCore 2 | 3 | import pandas as pd 4 | 5 | from DyCommon.Ui.DyTableWidget import * 6 | 7 | 8 | class DyDataFrameTableWidget(DyTableWidget): 9 | 10 | def __init__(self, df, parent=None): 11 | super().__init__(parent=parent, readOnly=True, index=False, floatCut=True, autoScroll=False) 12 | 13 | self.verticalHeader().setVisible(True) 14 | 15 | self._initDf(df) 16 | 17 | def _setRowNames(self, names): 18 | self.setRowCount(len(names)) 19 | 20 | self.setVerticalHeaderLabels(names) 21 | 22 | def _initDf(self, df): 23 | self.setColNames(list(df.columns)) 24 | self.fastAppendRows(df.values.tolist()) 25 | 26 | self._setRowNames(list(df.index)) 27 | 28 | self.setSortingEnabled(False) -------------------------------------------------------------------------------- /DyCommon/Ui/DyDataFrameWindow.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QApplication 2 | 3 | from .DyDataFrameTableWidget import * 4 | 5 | 6 | class DyDataFrameWindow(DyDataFrameTableWidget): 7 | """ 显示DataFrame """ 8 | 9 | def __init__(self, title, df, parent=None): 10 | super().__init__(df, parent) 11 | 12 | self._initUi(title) 13 | 14 | def _initUi(self, title): 15 | self.setWindowTitle(title) 16 | self.setWindowFlags(Qt.Window) 17 | self.resize(QApplication.desktop().size().width()//2, QApplication.desktop().size().height()//2) 18 | 19 | self.show() 20 | 21 | self.move((QApplication.desktop().size().width() - self.width())//2, (QApplication.desktop().size().height() - self.height())//2) 22 | -------------------------------------------------------------------------------- /DyCommon/Ui/DyDateDlg.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from PyQt5.QtWidgets import QDialog, QGridLayout, QLabel, QLineEdit, QPushButton 4 | 5 | 6 | class DyDateDlg(QDialog): 7 | 8 | def __init__(self, data, parent=None): 9 | super().__init__(parent) 10 | 11 | self._data = data 12 | 13 | self._initUi() 14 | 15 | def _initUi(self): 16 | self.setWindowTitle('日期') 17 | 18 | # 控件 19 | startDateLable = QLabel('开始日期') 20 | self._startDateLineEdit = QLineEdit(datetime.now().strftime("%Y-%m-%d")) 21 | 22 | endDateLable = QLabel('结束日期') 23 | self._endDateLineEdit = QLineEdit(datetime.now().strftime("%Y-%m-%d")) 24 | 25 | cancelPushButton = QPushButton('Cancel') 26 | okPushButton = QPushButton('OK') 27 | cancelPushButton.clicked.connect(self._cancel) 28 | okPushButton.clicked.connect(self._ok) 29 | 30 | # 布局 31 | grid = QGridLayout() 32 | grid.setSpacing(10) 33 | 34 | grid.addWidget(startDateLable, 0, 0) 35 | grid.addWidget(self._startDateLineEdit, 1, 0) 36 | 37 | grid.addWidget(endDateLable, 0, 1) 38 | grid.addWidget(self._endDateLineEdit, 1, 1) 39 | 40 | grid.addWidget(okPushButton, 2, 1) 41 | grid.addWidget(cancelPushButton, 2, 0) 42 | 43 | 44 | self.setLayout(grid) 45 | 46 | def _ok(self): 47 | self._data['startDate'] = self._startDateLineEdit.text() 48 | self._data['endDate'] = self._endDateLineEdit.text() 49 | 50 | self.accept() 51 | 52 | def _cancel(self): 53 | self.reject() 54 | -------------------------------------------------------------------------------- /DyCommon/Ui/DyInfoDlg.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QDialog, QLabel, QVBoxLayout 2 | from PyQt5.QtGui import QFont 3 | 4 | 5 | class DyInfoDlg(QDialog): 6 | """显示信息""" 7 | 8 | def __init__(self, title, info, parent=None): 9 | super().__init__(parent) 10 | 11 | self.setWindowTitle(title) 12 | 13 | self._initUi(info) 14 | 15 | def _initUi(self, info): 16 | text = info 17 | 18 | label = QLabel() 19 | label.setText(text) 20 | label.setMinimumWidth(500) 21 | 22 | label.setFont(QFont('Courier New', 12)) 23 | 24 | vbox = QVBoxLayout() 25 | vbox.addWidget(label) 26 | 27 | self.setLayout(vbox) -------------------------------------------------------------------------------- /DyCommon/Ui/DyLogWidget.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtCore 2 | from PyQt5.QtGui import QColor 3 | 4 | from .DyTableWidget import * 5 | from EventEngine.DyEvent import * 6 | 7 | 8 | class DyLogWidget(DyTableWidget): 9 | 10 | signal = QtCore.pyqtSignal(type(DyEvent())) 11 | 12 | header = ['时间','类型(错误:0,警告:0)','描述'] 13 | 14 | def __init__(self, eventEngine=None): 15 | super().__init__(None, True, True) 16 | 17 | self._eventEngine = eventEngine 18 | 19 | self._errorCount = 0 20 | self._warningCount = 0 21 | 22 | self.setColNames(self.header) 23 | 24 | self._registerEvent() 25 | 26 | def _setRowForeground(self, row, data): 27 | if data.type == data.ind: 28 | self.setRowForeground(row, Qt.darkGreen) 29 | 30 | elif data.type == data.ind1: 31 | self.setRowForeground(row, QColor("#4169E1")) 32 | 33 | elif data.type == data.ind2: 34 | self.setRowForeground(row, QColor("#C71585")) 35 | 36 | elif data.type == data.error: 37 | self._errorCount += 1 38 | self.setRowForeground(row, Qt.red) 39 | 40 | elif data.type == data.warning: 41 | self._warningCount += 1 42 | self.setRowForeground(row, QColor("#FF6100")) 43 | 44 | def _logHandler(self, event): 45 | data = event.data 46 | 47 | savedErrorCount = self._errorCount 48 | savedWarningCount = self._warningCount 49 | 50 | self.setSortingEnabled(False) 51 | 52 | row = self.appendRow([data.time, data.type, data.description], disableSorting=False) 53 | 54 | self._setRowForeground(row, data) 55 | 56 | self.setSortingEnabled(True) 57 | 58 | # check if need to change 类型 header name 59 | if self._errorCount != savedErrorCount or self._warningCount != savedWarningCount: 60 | self.setColName(1, '类型(错误:{0},警告:{1})'.format(self._errorCount, self._warningCount)) 61 | 62 | def _registerEvent(self): 63 | """ 注册GUI更新相关的事件监听 """ 64 | 65 | if self._eventEngine is None: return 66 | 67 | self.signal.connect(self._logHandler) 68 | self._eventEngine.register(DyEventType.log, self.signal.emit) 69 | 70 | def append(self, logData): 71 | rows = [[data.time, data.type, data.description] for data in logData] 72 | self.fastAppendRows(rows) 73 | 74 | for row, data in enumerate(logData): 75 | self._setRowForeground(row, data) 76 | 77 | self.setColName(1, '类型(错误:{0},警告:{1})'.format(self._errorCount, self._warningCount)) 78 | -------------------------------------------------------------------------------- /DyCommon/Ui/DyProcessNbrDlg.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QDialog, QGridLayout, QLabel, QLineEdit, QPushButton 2 | 3 | 4 | class DyProcessNbrDlg(QDialog): 5 | 6 | def __init__(self, data, parent=None): 7 | super().__init__(parent) 8 | 9 | self._data = data 10 | 11 | self._initUi() 12 | 13 | def _initUi(self): 14 | self.setWindowTitle('进程数') 15 | 16 | # 控件 17 | processNbrLable = QLabel('进程数') 18 | self._processNbrLineEdit = QLineEdit(str(self._data['nbr']) if self._data else '0' ) 19 | 20 | cancelPushButton = QPushButton('Cancel') 21 | okPushButton = QPushButton('OK') 22 | cancelPushButton.clicked.connect(self._cancel) 23 | okPushButton.clicked.connect(self._ok) 24 | 25 | # 布局 26 | grid = QGridLayout() 27 | grid.setSpacing(10) 28 | 29 | grid.addWidget(processNbrLable, 0, 0) 30 | grid.addWidget(self._processNbrLineEdit, 1, 0, 1, 2) 31 | 32 | grid.addWidget(okPushButton, 2, 1) 33 | grid.addWidget(cancelPushButton, 2, 0) 34 | 35 | 36 | self.setLayout(grid) 37 | 38 | def _ok(self): 39 | self._data['nbr'] = int(self._processNbrLineEdit.text()) 40 | 41 | self.accept() 42 | 43 | def _cancel(self): 44 | self.reject() 45 | -------------------------------------------------------------------------------- /DyCommon/Ui/DyProgressWidget.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtCore 2 | from PyQt5.QtWidgets import QWidget, QLabel, QProgressBar 3 | from PyQt5.Qt import QVBoxLayout 4 | 5 | 6 | from EventEngine.DyEvent import * 7 | 8 | 9 | class DyProgressWidget(QWidget): 10 | signalSingle = QtCore.pyqtSignal(type(DyEvent())) 11 | signalTotal = QtCore.pyqtSignal(type(DyEvent())) 12 | 13 | def __init__(self, eventEngine, parent=None): 14 | super().__init__(parent) 15 | self._eventEngine = eventEngine 16 | 17 | self._initUi() 18 | self._registerEvent() 19 | 20 | def _initUi(self): 21 | """初始化界面""" 22 | self.setWindowTitle('进度') 23 | 24 | labelTotal = QLabel('总体进度') 25 | labelSingle = QLabel('个体进度') 26 | 27 | self._progressSingle = QProgressBar(self) 28 | self._progressTotal = QProgressBar(self) 29 | 30 | vbox = QVBoxLayout() 31 | vbox.addWidget(labelTotal) 32 | vbox.addWidget(self._progressTotal) 33 | vbox.addWidget(labelSingle) 34 | vbox.addWidget(self._progressSingle) 35 | vbox.addStretch() 36 | 37 | self.setLayout(vbox) 38 | 39 | def _updateProgressSingle(self, event): 40 | self._progressSingle.setValue(event.data) 41 | 42 | def _updateProgressTotal(self, event): 43 | self._progressTotal.setValue(event.data) 44 | 45 | def _registerEvent(self): 46 | """连接Signal""" 47 | self.signalSingle.connect(self._updateProgressSingle) 48 | self.signalTotal.connect(self._updateProgressTotal) 49 | 50 | self._eventEngine.register(DyEventType.progressSingle, self.signalSingle.emit) 51 | self._eventEngine.register(DyEventType.progressTotal, self.signalTotal.emit) 52 | 53 | -------------------------------------------------------------------------------- /DyCommon/Ui/DySingleEditDlg.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QDialog, QGridLayout, QLabel, QLineEdit, QPushButton 2 | 3 | 4 | class DySingleEditDlg(QDialog): 5 | 6 | def __init__(self, data, title, label, default='', parent=None): 7 | """ 8 | @default: LineEdit initial value 9 | """ 10 | super().__init__(parent) 11 | 12 | self._data = data 13 | 14 | self._initUi(title, label, default) 15 | 16 | def _initUi(self, title, label, default): 17 | self.setWindowTitle(title) 18 | 19 | # 控件 20 | label_ = QLabel(label) 21 | self._lineEdit = QLineEdit(str(self._data['data']) if self._data else str(default) ) 22 | 23 | cancelPushButton = QPushButton('Cancel') 24 | okPushButton = QPushButton('OK') 25 | cancelPushButton.clicked.connect(self._cancel) 26 | okPushButton.clicked.connect(self._ok) 27 | 28 | # 布局 29 | grid = QGridLayout() 30 | grid.setSpacing(10) 31 | 32 | grid.addWidget(label_, 0, 0, 1, 2) 33 | grid.addWidget(self._lineEdit, 0, 2, 1, 2) 34 | 35 | grid.addWidget(okPushButton, 1, 2, 1, 2) 36 | grid.addWidget(cancelPushButton, 1, 0, 1, 2) 37 | 38 | 39 | self.setLayout(grid) 40 | 41 | def _ok(self): 42 | text = self._lineEdit.text() 43 | 44 | try: 45 | self._data['data'] = int(text) 46 | except Exception as ex: 47 | self._data['data'] = text 48 | 49 | self.accept() 50 | 51 | def _cancel(self): 52 | self.reject() -------------------------------------------------------------------------------- /DyCommon/Ui/DyStatsDataFrameTableWidget.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtCore 2 | 3 | import pandas as pd 4 | 5 | from DyCommon.Ui.DyStatsTableWidget import * 6 | 7 | 8 | class DyStatsDataFrameTableWidget(DyStatsTableWidget): 9 | """ 10 | 只显示DF的列,index需要用户自己转换成列 11 | """ 12 | def __init__(self, df, parent=None): 13 | super().__init__(parent=parent, readOnly=True, index=False, floatCut=True, autoScroll=False) 14 | 15 | self._initDf(df) 16 | 17 | def _initDf(self, df): 18 | self.setColNames(list(df.columns)) 19 | self.fastAppendRows(df.values.tolist()) 20 | -------------------------------------------------------------------------------- /DyCommon/Ui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/DyCommon/Ui/__init__.py -------------------------------------------------------------------------------- /DyCommon/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/DyCommon/__init__.py -------------------------------------------------------------------------------- /EventEngine/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/EventEngine/__init__.py -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 moyuanz 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. -------------------------------------------------------------------------------- /Stock/BackTesting/DyStockBackTestingCommon.py: -------------------------------------------------------------------------------- 1 | 2 | class DyStockBackTestingEventHandType: 3 | engine = 0 4 | other = 1 5 | 6 | nbr = 2 7 | 8 | 9 | class DyStockBackTestingStrategyReqData: 10 | def __init__(self, strategyCls, tDays, settings, param, codes=None, paramGroupNo=None): 11 | self.strategyCls = strategyCls 12 | self.tDays = tDays # 来自UI的req,@tDays是[start date, end date]。分发给子进程的req,@tDays是[tDay] 13 | self.settings = settings # {}, 回测参数设置, 不包含日期(也就是说忽略日期相关参数) 14 | self.codes = codes # 测试用的股票代码集 15 | self.param = param # 策略参数 16 | self.paramGroupNo = paramGroupNo # 策略参数组合号 17 | 18 | 19 | class DyStockBackTestingStrategyAckData: 20 | def __init__(self, datetime, strategyCls, paramGroupNo, period, isClose=False): 21 | self.datetime = datetime 22 | self.strategyCls = strategyCls 23 | self.paramGroupNo = paramGroupNo # 策略参数组合编号 24 | self.period = period # 策略运行周期, [start tDay, end tDay] 25 | 26 | self.day = None # 策略现在运行日期 27 | 28 | self.deals = [] # 交易细节的增量 29 | self.curPos = [] # 持仓 30 | 31 | self.curCash = None 32 | self.initCash = None 33 | 34 | self.isClose = isClose # 是不是收盘后的Ack 35 | 36 | 37 | class DyStockBackTestingCommon: 38 | maViewerIndicator = 'close' 39 | -------------------------------------------------------------------------------- /Stock/BackTesting/Engine/DyStockBackTestingMainEngine.py: -------------------------------------------------------------------------------- 1 | from DyCommon.DyCommon import * 2 | from ..DyStockBackTestingCommon import * 3 | from EventEngine.DyEventEngine import * 4 | 5 | from .Strategy.DyStockBackTestingStrategyEngine import * 6 | 7 | 8 | class DyStockBackTestingMainEngine(object): 9 | """ 股票回测主引擎 """ 10 | 11 | def __init__(self): 12 | self._eventEngine = DyEventEngine(DyStockBackTestingEventHandType.nbr, False) 13 | self._info = DyInfo(self._eventEngine) 14 | 15 | self._strategyEngine = DyStockBackTestingStrategyEngine(self._eventEngine, self._info) 16 | 17 | self._eventEngine.start() 18 | 19 | @property 20 | def eventEngine(self): 21 | return self._eventEngine 22 | 23 | @property 24 | def info(self): 25 | return self._info 26 | 27 | def exit(self): 28 | pass 29 | 30 | def setThreadMode(self): 31 | self._strategyEngine.setThreadMode() 32 | 33 | def setProcessMode(self, mode): 34 | self._strategyEngine.setProcessMode(mode) -------------------------------------------------------------------------------- /Stock/BackTesting/Engine/Strategy/DyStockBackTestingStrategyEngineProcess.py: -------------------------------------------------------------------------------- 1 | import queue 2 | 3 | from DyCommon.DyCommon import * 4 | from EventEngine.DyEvent import * 5 | from EventEngine.DyEventEngine import * 6 | from ...DyStockBackTestingCommon import * 7 | from .DyStockBackTestingCtaEngine import * 8 | from ....Data.Engine.DyStockDataEngine import * 9 | from Stock.Config.DyStockConfig import DyStockConfig 10 | from Stock.Data.Engine.DyStockDbCache import DyStockDbCache 11 | 12 | 13 | def __setDbCache(reqData): 14 | dbCachePreLoadDaysSize = reqData.settings.get('dbCachePreLoadDaysSize') 15 | 16 | useDbCache = False 17 | if dbCachePreLoadDaysSize is not None: 18 | useDbCache = True 19 | 20 | # change to the length of period 21 | if dbCachePreLoadDaysSize == 0: 22 | dbCachePreLoadDaysSize = len(reqData.tDays) 23 | 24 | DyStockDbCache.preLoadDaysSize = dbCachePreLoadDaysSize 25 | 26 | return useDbCache 27 | 28 | def dyStockBackTestingStrategyEngineProcess(outQueue, inQueue, reqData, config=None): 29 | """ 30 | 股票回测处理实体。每个回测处理实体由一个参数组合和一个回测周期组成。 31 | 每个交易日回测结束后向UI推送持仓和成交信息 32 | """ 33 | paramGroupNo = reqData.paramGroupNo 34 | period = [reqData.tDays[0], reqData.tDays[-1]] 35 | 36 | if config is not None: 37 | DyStockConfig.setConfigForBackTesting(config) 38 | 39 | # set DB Cache 40 | useDbCache = __setDbCache(reqData) 41 | 42 | # Engines 43 | eventEngine = DyDummyEventEngine() 44 | info = DySubInfo(paramGroupNo, period, outQueue) 45 | dataEngine = DyStockDataEngine(eventEngine, info, False, dbCache=useDbCache) 46 | 47 | # create stock back testing CTA engine 48 | ctaEngine = DyStockBackTestingCtaEngine(eventEngine, info, dataEngine, reqData, dbCache=useDbCache) 49 | 50 | for tDay in reqData.tDays: 51 | try: 52 | event = inQueue.get_nowait() 53 | except queue.Empty: 54 | pass 55 | 56 | # 回测当日数据 57 | if not ctaEngine.run(tDay): 58 | break 59 | 60 | # 发送当日回测结果数据事件 61 | event = DyEvent(DyEventType.stockStrategyBackTestingAck) 62 | event.data = ctaEngine.getCurAckData() 63 | 64 | outQueue.put(event) 65 | 66 | # 发送'股票回测策略引擎处理结束'事件 67 | event = DyEvent(DyEventType.stockBackTestingStrategyEngineProcessEnd) 68 | event.data[paramGroupNo] = period 69 | 70 | outQueue.put(event) 71 | -------------------------------------------------------------------------------- /Stock/BackTesting/Engine/Strategy/DyStockBackTestingStrategyEngineProxy.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | import threading 3 | import queue 4 | 5 | from .DyStockBackTestingStrategyEngineProcess import * 6 | from Stock.Config.DyStockConfig import DyStockConfig 7 | 8 | 9 | class DyStockBackTestingStrategyEngineProxy(threading.Thread): 10 | """ 以进程方式启动一个周期的策略回测 """ 11 | 12 | def __init__(self, eventEngine): 13 | super().__init__() 14 | 15 | self._eventEngine = eventEngine 16 | 17 | self._ctx = multiprocessing.get_context('spawn') 18 | self._queue = self._ctx.Queue() # queue to receive event from child processes 19 | 20 | self._processes = [] 21 | self._childQueues = [] 22 | 23 | self.start() 24 | 25 | def run(self): 26 | while True: 27 | event = self._queue.get() 28 | 29 | self._eventEngine.put(event) 30 | 31 | def startBackTesting(self, reqData): 32 | childQueue = self._ctx.Queue() 33 | self._childQueues.append(childQueue) 34 | 35 | config = DyStockConfig.getConfigForBackTesting() 36 | p = self._ctx.Process(target=dyStockBackTestingStrategyEngineProcess, args=(self._queue, childQueue, reqData, config)) 37 | p.start() 38 | 39 | self._processes.append(p) 40 | 41 | 42 | class DyStockBackTestingStrategyEngineProxyThread(threading.Thread): 43 | """ 以线程方式启动一个周期的策略回测, 主要做调试用 """ 44 | 45 | def __init__(self, eventEngine): 46 | super().__init__() 47 | 48 | self._eventEngine = eventEngine 49 | 50 | self._queue = queue.Queue() # queue to receive event from child threads 51 | 52 | self._threads = [] 53 | self._childQueues = [] 54 | 55 | self.start() 56 | 57 | def run(self): 58 | while True: 59 | event = self._queue.get() 60 | 61 | self._eventEngine.put(event) 62 | 63 | def startBackTesting(self, reqData): 64 | childQueue = queue.Queue() 65 | self._childQueues.append(childQueue) 66 | 67 | t = threading.Thread(target=dyStockBackTestingStrategyEngineProcess, args=(self._queue, childQueue, reqData)) 68 | t.start() 69 | 70 | self._threads.append(t) 71 | -------------------------------------------------------------------------------- /Stock/BackTesting/Engine/Strategy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/BackTesting/Engine/Strategy/__init__.py -------------------------------------------------------------------------------- /Stock/BackTesting/Engine/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/BackTesting/Engine/__init__.py -------------------------------------------------------------------------------- /Stock/BackTesting/Ui/Basic/DyStockBackTestingStrategyWidget.py: -------------------------------------------------------------------------------- 1 | from DyCommon.Ui.DyTreeWidget import * 2 | 3 | from EventEngine.DyEvent import * 4 | from ....Trade.Ui.Basic.DyStockTradeStrategyWidget import * 5 | from ....Select.Ui.Basic.DyStockSelectStrategyWidget import * 6 | 7 | 8 | class DyStockBackTestingStrategyWidget(DyStockSelectStrategyWidget): 9 | """ 只能选中一个策略回测 """ 10 | 11 | def __init__(self, paramWidget=None): 12 | self.__class__.strategyFields = DyStockTradeStrategyWidget.strategyFields 13 | super().__init__(paramWidget) 14 | -------------------------------------------------------------------------------- /Stock/BackTesting/Ui/Basic/Strategy/DyStockBackTestingStrategyParamGroupResultWidget.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QWidget 2 | from PyQt5.Qt import QGridLayout 3 | 4 | from .....Select.Ui.Basic.Param.DyStockSelectStrategyParamWidget import * 5 | from .DyStockBackTestingStrategyPeriodsResultWidget import * 6 | 7 | 8 | class DyStockBackTestingStrategyParamGroupResultWidget(QWidget): 9 | """ 策略一个参数组合的回测结果窗口 10 | 组成部分: 11 | 参数组合窗口 12 | 多个周期结果窗口 13 | """ 14 | 15 | def __init__(self, strategyCls, paramGroupNo, param, eventEngine, dataEngine, dataViewer): 16 | super().__init__() 17 | 18 | self._strategyCls = strategyCls 19 | self._paramGroupNo = paramGroupNo 20 | self._param = param 21 | self._eventEngine = eventEngine 22 | self._dataEngine = dataEngine 23 | self._dataViewer = dataViewer 24 | self._windows = [] 25 | 26 | self._initUi() 27 | 28 | def _initUi(self): 29 | self._paramWidget = DyStockSelectStrategyParamWidget() 30 | self._paramWidget.set(self._param) 31 | 32 | self._periodsResultWidget = DyStockBackTestingStrategyPeriodsResultWidget(self._strategyCls, self._paramGroupNo, self._param, self._eventEngine, self._dataEngine, self._dataViewer) 33 | 34 | grid = QGridLayout() 35 | grid.setSpacing(0) 36 | 37 | grid.addWidget(self._paramWidget, 0, 0) 38 | grid.addWidget(self._periodsResultWidget, 1, 0) 39 | 40 | grid.setRowStretch(0, 1) 41 | grid.setRowStretch(1, 20) 42 | 43 | self.setLayout(grid) 44 | 45 | def newPeriod(self, event): 46 | self._periodsResultWidget.newPeriod(event) 47 | 48 | def update(self, ackData): 49 | """ 更新策略参数组合一个回测周期的回测结果 """ 50 | self._periodsResultWidget.update(ackData) 51 | 52 | def removeAll(self): 53 | self._periodsResultWidget.removeAll() 54 | 55 | def getCurPnlRatio(self): 56 | return self._periodsResultWidget.getCurPnlRatio() 57 | 58 | def overview(self): 59 | groupParams = self._paramWidget.get() 60 | groupNames, groupData = list(groupParams.keys()), list(groupParams.values()) 61 | 62 | statsOverviewNames, statsOverviewData = self._periodsResultWidget.overview() 63 | 64 | # add seperator '' 65 | return groupNames + [''] + statsOverviewNames, groupData + [''] + statsOverviewData 66 | 67 | def mergePeriod(self): 68 | # new window 69 | window = self.__class__(self._strategyCls, self._paramGroupNo, self._param, self._eventEngine, self._dataEngine, self._dataViewer) 70 | 71 | window._periodsResultWidget.mergePeriod(self._periodsResultWidget) 72 | 73 | window.setWindowTitle('{}: 参数{}'.format(self._strategyCls.chName, self._paramGroupNo)) 74 | window.showMaximized() 75 | 76 | self._windows.append(window) 77 | -------------------------------------------------------------------------------- /Stock/BackTesting/Ui/Basic/Strategy/DyStockBackTestingStrategyResultPositionWidget.py: -------------------------------------------------------------------------------- 1 | from DyCommon.Ui.DyTableWidget import * 2 | 3 | from EventEngine.DyEvent import * 4 | 5 | 6 | class DyStockBackTestingStrategyResultPositionWidget(DyTableWidget): 7 | 8 | header = ['代码','名称','可用数量/总数量','成本价/现价','盈亏(%)','最大盈/亏(%)','持有期','除权除息'] 9 | 10 | def __init__(self, dataViewer): 11 | super().__init__(None, True, False) 12 | 13 | self._dataViewer = dataViewer 14 | 15 | self.setColNames(self.header) 16 | self.setAutoForegroundCol('盈亏(%)') 17 | 18 | def update(self, pos): 19 | # remove non-existing codes 20 | rows = self.getAll() 21 | 22 | for row in rows: 23 | if row[0] not in pos: 24 | self.removeRow(row[0]) 25 | 26 | # update new positions 27 | for code, pos_ in pos.items(): 28 | self[code] = [pos_.code, pos_.name, 29 | '%.2f/%.2f'%(pos_.availVolume, pos_.totalVolume), 30 | '%.3f/%.3f'%(pos_.cost, pos_.price), 31 | pos_.pnlRatio, 32 | '%.2f/%.2f'%(pos_.maxPnlRatio, pos_.minPnlRatio), 33 | pos_.holdingPeriod, 34 | '是' if pos_.xrd else '否' 35 | ] 36 | 37 | def combineInit(self, selves): 38 | """ 39 | use self widgets to initialize itself 40 | @selves: [self object] 41 | """ 42 | # get column names and rows 43 | colNames = None 44 | rows = [] 45 | for self_ in selves: 46 | colNames = self_.getColNames() 47 | rows.extend(self_.getAll()) 48 | 49 | if colNames is None: 50 | return 51 | 52 | # show on widget 53 | self.setColNames(colNames) 54 | self.fastAppendRows(rows, self.getAutoForegroundColName()) 55 | 56 | -------------------------------------------------------------------------------- /Stock/BackTesting/Ui/Basic/Strategy/Other/DyStockBackTestingStrategyResultOverviewWindow.py: -------------------------------------------------------------------------------- 1 | from DyCommon.Ui.DyStatsTableWidget import * 2 | 3 | 4 | class DyStockBackTestingStrategyResultOverviewWindow(DyStatsTableWidget): 5 | 6 | def __init__(self, strategyResultWidget, strategyParamGroupWidgets, header, rows, autoForegroundColName): 7 | super().__init__(None, True, True) 8 | 9 | self._strategyResultWidget = strategyResultWidget 10 | self._strategyParamGroupWidgets = strategyParamGroupWidgets 11 | 12 | self.setColNames(header) 13 | self.fastAppendRows(rows, autoForegroundColName) 14 | 15 | self.itemDoubleClicked.connect(self._itemDoubleClicked) 16 | 17 | def _itemDoubleClicked(self, item): 18 | self._strategyResultWidget.setCurrentWidget(self._strategyParamGroupWidgets[self.org(item.row())-1]) -------------------------------------------------------------------------------- /Stock/BackTesting/Ui/Basic/Strategy/Other/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/BackTesting/Ui/Basic/Strategy/Other/__init__.py -------------------------------------------------------------------------------- /Stock/BackTesting/Ui/Basic/Strategy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/BackTesting/Ui/Basic/Strategy/__init__.py -------------------------------------------------------------------------------- /Stock/BackTesting/Ui/Basic/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/BackTesting/Ui/Basic/__init__.py -------------------------------------------------------------------------------- /Stock/BackTesting/Ui/Other/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/BackTesting/Ui/Other/__init__.py -------------------------------------------------------------------------------- /Stock/BackTesting/Ui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/BackTesting/Ui/__init__.py -------------------------------------------------------------------------------- /Stock/BackTesting/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/BackTesting/__init__.py -------------------------------------------------------------------------------- /Stock/Common/Ui/Basic/Dlg/DyStockIndustryCompareDlg.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QApplication, QDialog, QGridLayout, QLabel, QLineEdit, QPushButton, QCheckBox 2 | 3 | 4 | class DyStockIndustryCompareDlg(QDialog): 5 | 6 | def __init__(self, name, baseDate, data, parent=None): 7 | super().__init__(parent) 8 | 9 | self._data = data 10 | 11 | self._initUi(name, baseDate) 12 | 13 | def _initUi(self, name, baseDate): 14 | self.setWindowTitle('行业对比[{0}]-基准日期[{1}]'.format(name, baseDate)) 15 | 16 | # 控件 17 | forwardNTDaysLabel = QLabel('向前N日涨幅(%)') 18 | self._forwardNTDaysLineEdit = QLineEdit('30') 19 | 20 | self._industry2CheckBox = QCheckBox('行业二级分级') 21 | #self._industry2CheckBox.setChecked(True) 22 | 23 | self._industry3CheckBox = QCheckBox('行业三级分级') 24 | self._industry3CheckBox.setChecked(True) 25 | 26 | cancelPushButton = QPushButton('Cancel') 27 | okPushButton = QPushButton('OK') 28 | cancelPushButton.clicked.connect(self._cancel) 29 | okPushButton.clicked.connect(self._ok) 30 | 31 | # 布局 32 | grid = QGridLayout() 33 | grid.setSpacing(10) 34 | 35 | grid.addWidget(forwardNTDaysLabel, 0, 0) 36 | grid.addWidget(self._forwardNTDaysLineEdit, 0, 1) 37 | 38 | grid.addWidget(self._industry2CheckBox, 1, 0) 39 | grid.addWidget(self._industry3CheckBox, 1, 1) 40 | 41 | grid.addWidget(okPushButton, 2, 1) 42 | grid.addWidget(cancelPushButton, 2, 0) 43 | 44 | self.setLayout(grid) 45 | 46 | self.setMinimumWidth(QApplication.desktop().size().width()//5) 47 | 48 | def _ok(self): 49 | self._data['forwardNTDays'] = int(self._forwardNTDaysLineEdit.text()) 50 | 51 | self._data['industry2'] = self._industry2CheckBox.isChecked() 52 | self._data['industry3'] = self._industry3CheckBox.isChecked() 53 | 54 | self.accept() 55 | 56 | def _cancel(self): 57 | self.reject() 58 | -------------------------------------------------------------------------------- /Stock/Common/Ui/Basic/Dlg/DyStockInfoDlg.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QDialog, QGridLayout, QPushButton, QApplication, QMessageBox 2 | 3 | from DyCommon.Ui.DyTreeWidget import * 4 | 5 | 6 | class DyStockInfoDlg(QDialog): 7 | """ 个股资料选择对话框 8 | """ 9 | fields = \ 10 | [ 11 | ['公司资料', 12 | ['所属行业'], 13 | ['主营业务'], 14 | ['涉及概念'] 15 | ], 16 | ['股本', 17 | ['实际流通股(亿)'], 18 | ['实际流通市值(亿元)'], 19 | ['机构占比流通(%)'], 20 | ['流通市值(亿元)'], 21 | ] 22 | ] 23 | 24 | def __init__(self, data, parent=None): 25 | super().__init__(parent) 26 | 27 | self._data = data 28 | 29 | self._initUi() 30 | 31 | def _initUi(self): 32 | self.setWindowTitle('个股资料(F10)') 33 | 34 | # 控件 35 | cancelPushButton = QPushButton('Cancel') 36 | okPushButton = QPushButton('OK') 37 | cancelPushButton.clicked.connect(self._cancel) 38 | okPushButton.clicked.connect(self._ok) 39 | 40 | self._stockInfoWidget = DyTreeWidget(self.fields) 41 | 42 | # 布局 43 | grid = QGridLayout() 44 | grid.setSpacing(10) 45 | 46 | grid.addWidget(self._stockInfoWidget, 0, 0, 20, 10) 47 | 48 | grid.addWidget(okPushButton, 0, 10) 49 | grid.addWidget(cancelPushButton, 1, 10) 50 | 51 | self.setLayout(grid) 52 | self.resize(QApplication.desktop().size().width()//3, QApplication.desktop().size().height()//2) 53 | 54 | def _ok(self): 55 | indicators = self._stockInfoWidget.getCheckedTexts() 56 | 57 | if not indicators: 58 | QMessageBox.warning(self, '错误', '没有选择指标!') 59 | return 60 | 61 | self._data['indicators'] = indicators 62 | 63 | self.accept() 64 | 65 | def _cancel(self): 66 | self.reject() 67 | -------------------------------------------------------------------------------- /Stock/Common/Ui/Basic/Dlg/DyStockTableAddColumnsDlg.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QDialog, QGridLayout, QLabel, QLineEdit, QPushButton, QApplication, QRadioButton, QButtonGroup 2 | 3 | 4 | class DyStockTableAddColumnsDlg(QDialog): 5 | 6 | def __init__(self, data, title, parent=None, backward=True): 7 | super().__init__(parent) 8 | 9 | self._data = data 10 | 11 | self._initUi(title, backward) 12 | 13 | def _initUi(self, title, backward): 14 | self.setWindowTitle('添加{0}列'.format(title)) 15 | 16 | # 控件 17 | increaseColumnsLable = QLabel('基准日期几日{0}'.format(title)) 18 | self._increaseColumnsLineEdit = QLineEdit(','.join([str(x) for x in self._data['days']]) if self._data else '1,2,3,4,5,10') 19 | 20 | # 前 & 后 21 | forwardRadioButton = QRadioButton('向前') 22 | backwardRadioButton = QRadioButton('向后') 23 | if backward: 24 | backwardRadioButton.setChecked(True) 25 | else: 26 | forwardRadioButton.setChecked(True) 27 | 28 | # 添加到QButtonGroup 29 | self._wardButtonGroup = QButtonGroup() 30 | self._wardButtonGroup.addButton(forwardRadioButton, 1) 31 | self._wardButtonGroup.addButton(backwardRadioButton, 2) 32 | 33 | cancelPushButton = QPushButton('Cancel') 34 | okPushButton = QPushButton('OK') 35 | cancelPushButton.clicked.connect(self._cancel) 36 | okPushButton.clicked.connect(self._ok) 37 | 38 | # 布局 39 | grid = QGridLayout() 40 | grid.setSpacing(10) 41 | 42 | grid.addWidget(increaseColumnsLable, 0, 0, 1, 2) 43 | grid.addWidget(self._increaseColumnsLineEdit, 1, 0, 1, 2) 44 | 45 | grid.addWidget(forwardRadioButton, 2, 0) 46 | grid.addWidget(backwardRadioButton, 2, 1) 47 | 48 | grid.addWidget(okPushButton, 3, 1) 49 | grid.addWidget(cancelPushButton, 3, 0) 50 | 51 | 52 | self.setLayout(grid) 53 | self.setMinimumWidth(QApplication.desktop().size().width()//5) 54 | 55 | def _ok(self): 56 | checkedButton = self._wardButtonGroup.checkedButton() 57 | text = checkedButton.text() 58 | self._data['backward'] = True if text == '向后' else False 59 | 60 | self._data['days'] = [int(x) for x in self._increaseColumnsLineEdit.text().split(',')] 61 | 62 | self.accept() 63 | 64 | def _cancel(self): 65 | self.reject() 66 | -------------------------------------------------------------------------------- /Stock/Common/Ui/Basic/Dlg/DyStockTableColumnOperateDlg.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QDialog, QGridLayout, QLabel, QTextEdit, QPushButton, QApplication, QCheckBox 2 | 3 | from DyCommon.Ui.DyTableWidget import * 4 | 5 | 6 | class DyStockTableColumnOperateDlg(QDialog): 7 | """ 8 | 列运算对话框 9 | """ 10 | 11 | def __init__(self, data, colNames, parent=None): 12 | super().__init__(parent) 13 | 14 | self._data = data 15 | 16 | self._initUi(colNames) 17 | 18 | def _initUi(self, colNames): 19 | self.setWindowTitle('列运算') 20 | 21 | # 控件 22 | table = DyTableWidget(parent=None, readOnly=True, index=False, floatCut=True, autoScroll=False) 23 | table.setColNames(['列名', '表达式']) 24 | rows = [[name, 'x[{0}]'.format(i)] for i, name in enumerate(colNames)] 25 | table.fastAppendRows(rows) 26 | 27 | descriptionLabel = QLabel('列运算表达式(Pandas语法)') 28 | self._expressionTextEdit = QTextEdit() 29 | 30 | cancelPushButton = QPushButton('Cancel') 31 | okPushButton = QPushButton('OK') 32 | cancelPushButton.clicked.connect(self._cancel) 33 | okPushButton.clicked.connect(self._ok) 34 | 35 | # 布局 36 | grid = QGridLayout() 37 | grid.setSpacing(10) 38 | 39 | grid.addWidget(table, 0, 0, 22, 1) 40 | 41 | grid.addWidget(descriptionLabel, 0, 1) 42 | 43 | grid.addWidget(self._expressionTextEdit, 1, 1, 20, 20) 44 | 45 | grid.addWidget(okPushButton, 0, 21) 46 | grid.addWidget(cancelPushButton, 1, 21) 47 | 48 | 49 | self.setLayout(grid) 50 | self.resize(QApplication.desktop().size().width()//2, QApplication.desktop().size().height()//4*3) 51 | 52 | def _ok(self): 53 | expression = self._expressionTextEdit.toPlainText().replace('\n', ' ') 54 | 55 | self._data['exp'] = expression 56 | 57 | self.accept() 58 | 59 | def _cancel(self): 60 | self.reject() 61 | -------------------------------------------------------------------------------- /Stock/Common/Ui/Basic/Dlg/DyStockTableFilterDlg.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QDialog, QGridLayout, QLabel, QTextEdit, QPushButton, QApplication, QCheckBox 2 | 3 | from DyCommon.Ui.DyTableWidget import * 4 | 5 | 6 | class DyStockTableFilterDlg(QDialog): 7 | 8 | def __init__(self, data, colNames, parent=None): 9 | super().__init__(parent) 10 | 11 | self._data = data 12 | 13 | self._initUi(colNames) 14 | 15 | def _initUi(self, colNames): 16 | self.setWindowTitle('过滤') 17 | 18 | # 控件 19 | table = DyTableWidget(parent=None, readOnly=True, index=False, floatCut=True, autoScroll=False) 20 | table.setColNames(['列名', '表达式']) 21 | rows = [[name, 'x[{0}]'.format(i)] for i, name in enumerate(colNames)] 22 | table.fastAppendRows(rows) 23 | 24 | descriptionLabel = QLabel('行过滤表达式(Python语法)') 25 | self._filterTextEdit = QTextEdit() 26 | self._newWindowCheckBox = QCheckBox('新窗口') 27 | self._newWindowCheckBox.setChecked(True) 28 | self._highlightCheckBox = QCheckBox('原窗口高亮') 29 | self._highlightCheckBox.setChecked(False) 30 | 31 | cancelPushButton = QPushButton('Cancel') 32 | okPushButton = QPushButton('OK') 33 | cancelPushButton.clicked.connect(self._cancel) 34 | okPushButton.clicked.connect(self._ok) 35 | 36 | # 布局 37 | grid = QGridLayout() 38 | grid.setSpacing(10) 39 | 40 | grid.addWidget(table, 0, 0, 22, 1) 41 | grid.addWidget(self._newWindowCheckBox, 0, 1) 42 | grid.addWidget(self._highlightCheckBox, 0, 2) 43 | 44 | grid.addWidget(descriptionLabel, 1, 1) 45 | 46 | grid.addWidget(self._filterTextEdit, 2, 1, 20, 20) 47 | 48 | grid.addWidget(okPushButton, 0, 21) 49 | grid.addWidget(cancelPushButton, 1, 21) 50 | 51 | 52 | self.setLayout(grid) 53 | self.resize(QApplication.desktop().size().width()//2, QApplication.desktop().size().height()//4*3) 54 | 55 | def _ok(self): 56 | filter = self._filterTextEdit.toPlainText().replace('\n', ' ') 57 | 58 | self._data['filter'] = filter 59 | self._data['newWindow'] = self._newWindowCheckBox.isChecked() 60 | self._data['highlight'] = self._highlightCheckBox.isChecked() 61 | 62 | self.accept() 63 | 64 | def _cancel(self): 65 | self.reject() 66 | -------------------------------------------------------------------------------- /Stock/Common/Ui/Basic/Dlg/DyStockTableSelectDlg.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QDialog, QGridLayout, QPushButton, QRadioButton, QButtonGroup, QApplication 2 | 3 | 4 | class DyStockTableSelectDlg(QDialog): 5 | 6 | def __init__(self, data, dlgName): 7 | super().__init__() 8 | 9 | self._data = data 10 | 11 | self._initUi(dlgName) 12 | 13 | def _initUi(self, dlgName): 14 | self.setWindowTitle(dlgName) 15 | 16 | allRadioButton = QRadioButton('所有'); allRadioButton.setChecked(True) 17 | highlightRadioButton = QRadioButton('高亮') 18 | 19 | # 添加到QButtonGroup 20 | self._buttonGroup = QButtonGroup() 21 | self._buttonGroup.addButton(allRadioButton, 1); 22 | self._buttonGroup.addButton(highlightRadioButton, 2) 23 | 24 | cancelPushButton = QPushButton('Cancel') 25 | okPushButton = QPushButton('OK') 26 | cancelPushButton.clicked.connect(self._cancel) 27 | okPushButton.clicked.connect(self._ok) 28 | 29 | # 布局 30 | grid = QGridLayout() 31 | grid.setSpacing(10) 32 | 33 | grid.addWidget(allRadioButton, 1, 0) 34 | grid.addWidget(highlightRadioButton, 1, 1) 35 | 36 | grid.addWidget(okPushButton, 2, 1) 37 | grid.addWidget(cancelPushButton, 2, 0) 38 | 39 | self.setLayout(grid) 40 | self.setMinimumWidth(QApplication.desktop().size().width()//5) 41 | 42 | def _ok(self): 43 | checkedButton = self._buttonGroup.checkedButton() 44 | text = checkedButton.text() 45 | self._data['all'] = True if text == '所有' else False 46 | 47 | self.accept() 48 | 49 | def _cancel(self): 50 | self.reject() 51 | -------------------------------------------------------------------------------- /Stock/Common/Ui/Basic/Dlg/DyStockVolatilityDistDlg.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QApplication, QDialog, QGridLayout, QLabel, QLineEdit, QPushButton, QRadioButton, QButtonGroup 2 | 3 | 4 | class DyStockVolatilityDistDlg(QDialog): 5 | """ 6 | !!!暂时这个类没有用 7 | """ 8 | 9 | def __init__(self, name, baseDate, data, parent=None): 10 | """ 11 | @name: 股票名称 12 | """ 13 | super().__init__(parent) 14 | 15 | self._data = data 16 | 17 | self._initUi(name, baseDate) 18 | 19 | def _initUi(self, name, baseDate): 20 | self.setWindowTitle('波动分布[{0}]'.format(name)) 21 | 22 | # 控件 23 | forwardNTDaysLabel = QLabel('基准日期[{0}]向前N日(不包含基准日期)'.format(baseDate)) 24 | self._forwardNTDaysLineEdit = QLineEdit('30') 25 | 26 | # 自身波动和绝对波动 27 | # 个股绝对波动 = 个股自身波动 + 大盘波动 28 | selfVolatilityRadioButton = QRadioButton('自身波动'); selfVolatilityRadioButton.setChecked(True) 29 | selfVolatilityRadioButton.setToolTip('个股绝对波动 = 个股自身波动 + 大盘波动') 30 | 31 | absoluteVolatilityRadioButton = QRadioButton('绝对波动') 32 | absoluteVolatilityRadioButton.setToolTip('个股绝对波动 = 个股自身波动 + 大盘波动') 33 | 34 | # 添加到QButtonGroup 35 | self._volatilityButtonGroup = QButtonGroup() 36 | self._volatilityButtonGroup.addButton(selfVolatilityRadioButton, 1); 37 | self._volatilityButtonGroup.addButton(absoluteVolatilityRadioButton, 2) 38 | 39 | cancelPushButton = QPushButton('Cancel') 40 | okPushButton = QPushButton('OK') 41 | cancelPushButton.clicked.connect(self._cancel) 42 | okPushButton.clicked.connect(self._ok) 43 | 44 | # 布局 45 | grid = QGridLayout() 46 | grid.setSpacing(10) 47 | 48 | grid.addWidget(forwardNTDaysLabel, 0, 0) 49 | grid.addWidget(self._forwardNTDaysLineEdit, 0, 1) 50 | 51 | grid.addWidget(selfVolatilityRadioButton, 1, 0) 52 | grid.addWidget(absoluteVolatilityRadioButton, 1, 1) 53 | 54 | grid.addWidget(okPushButton, 2, 1) 55 | grid.addWidget(cancelPushButton, 2, 0) 56 | 57 | self.setLayout(grid) 58 | 59 | self.setMinimumWidth(QApplication.desktop().size().width()//5) 60 | 61 | def _getVolatility(self): 62 | checkedButton = self._volatilityButtonGroup.checkedButton() 63 | text = checkedButton.text() 64 | 65 | return True if text == '自身波动' else False 66 | 67 | def _ok(self): 68 | self._data['forwardNTDays'] = int(self._forwardNTDaysLineEdit.text()) 69 | self._data['selfVolatility'] = self._getVolatility() 70 | 71 | self.accept() 72 | 73 | def _cancel(self): 74 | self.reject() 75 | -------------------------------------------------------------------------------- /Stock/Common/Ui/Basic/Dlg/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Common/Ui/Basic/Dlg/__init__.py -------------------------------------------------------------------------------- /Stock/Common/Ui/Basic/Other/DyStockIndustryCompareWindow.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QTabWidget 2 | 3 | 4 | class DyStockIndustryCompareWindow(QTabWidget): 5 | """ 股票行业比较窗口 """ 6 | 7 | def __init__(self, eventEngine, tableCls, targetCode, targetName, baseDate): 8 | """ 9 | @tableCls: DyStockTableWidget class, 这么做是防止import递归 10 | @targetCode, @targetName: 跟哪个股票进行行业比较 11 | """ 12 | super().__init__() 13 | 14 | self._eventEngine = eventEngine 15 | self._tableCls = tableCls 16 | self._targetCode = targetCode 17 | self._targetName = targetName 18 | self._baseDate = baseDate 19 | 20 | def addCategorys(self, dfs): 21 | for category, df in dfs.items(): 22 | header = list(df.columns) 23 | data = df.values.tolist() 24 | 25 | widget = self._tableCls(self._eventEngine, name=category, baseDate=self._baseDate) 26 | widget.appendStocks(data, header, autoForegroundColName=header[-1]) 27 | 28 | widget.markByData('名称', self._targetName) 29 | 30 | self.addTab(widget, category) 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Stock/Common/Ui/Basic/Other/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Common/Ui/Basic/Other/__init__.py -------------------------------------------------------------------------------- /Stock/Common/Ui/Basic/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Common/Ui/Basic/__init__.py -------------------------------------------------------------------------------- /Stock/Common/Ui/Deal/Basic/DyStockDealDetailsWidget.py: -------------------------------------------------------------------------------- 1 | 2 | from DyCommon.Ui.DyStatsTableWidget import * 3 | 4 | 5 | class DyStockDealDetailsWidget(DyStatsTableWidget): 6 | 7 | colNames = ['时间', '价格', '成交量(手)', '类型'] 8 | 9 | def __init__(self, dataEngine): 10 | super().__init__(None, True, False, autoScroll=False) 11 | 12 | self._ticksEngine = dataEngine.ticksEngine 13 | self._daysEngine = dataEngine.daysEngine 14 | 15 | self.setColNames(self.colNames + ['换手(万分之)']) 16 | 17 | def setInfoWidget(self, widget): 18 | self._infoWidget = widget 19 | 20 | def set(self, code, date, n = 0): 21 | date = self._daysEngine.codeTDayOffset(code, date, n) 22 | if date is None: return 23 | 24 | self._code = code 25 | self._day = date 26 | 27 | if not self._ticksEngine.loadCode(code, date): 28 | return 29 | 30 | df = self._ticksEngine.getDataFrame(code) 31 | 32 | self._set(df) 33 | 34 | def getForegroundOverride(self, value): 35 | 36 | if value == '买盘': 37 | color = Qt.red 38 | elif value == '卖盘': 39 | color = Qt.darkGreen 40 | else: 41 | color = None 42 | 43 | return color 44 | 45 | def _set(self, df): 46 | 47 | df.drop('amount', axis=1, inplace=True) 48 | 49 | df.reset_index(inplace=True) # 把时间索引转成列 50 | df[['datetime']] = df['datetime'].map(lambda x: x.strftime('%H:%M:%S')) 51 | 52 | df.rename(columns={'datetime':'时间', 'price':'价格', 'volume':'成交量(手)', 'type':'类型'}, inplace=True) 53 | df.reindex(columns=self.colNames, copy=False) 54 | 55 | # 计算每笔的换手率 56 | volumeSeries = df['成交量(手)'] 57 | volumeSum = volumeSeries.sum() 58 | df['换手(万分之)'] = volumeSeries * ((self._infoWidget.turn*100)/volumeSum) 59 | 60 | rows = df.values.tolist() 61 | 62 | self.fastAppendRows(rows, '类型', True) 63 | 64 | def forward(self): 65 | self.set(self._code, self._day, -1) 66 | 67 | def backward(self): 68 | self.set(self._code, self._day, 1) 69 | -------------------------------------------------------------------------------- /Stock/Common/Ui/Deal/Basic/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Common/Ui/Deal/Basic/__init__.py -------------------------------------------------------------------------------- /Stock/Common/Ui/Deal/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Common/Ui/Deal/__init__.py -------------------------------------------------------------------------------- /Stock/Common/Ui/DyStockMaViewerIndicatorMenu.py: -------------------------------------------------------------------------------- 1 | from PyQt5.Qt import QAction 2 | 3 | 4 | class DyStockMaViewerIndicatorMenu(object): 5 | """ 设置股票视图显示均线采用的指标 """ 6 | 7 | def __init__(self, interface): 8 | self._interface = interface 9 | 10 | self._init() 11 | 12 | def _init(self): 13 | menu = self._interface.getMaViewerIndicatorParentMenu() 14 | 15 | maIndicatorMenu = menu.addMenu('均线视图指标') 16 | 17 | # OHLC 18 | openAction = QAction('开盘价', maIndicatorMenu) 19 | openAction.triggered.connect(self._act) 20 | openAction.setCheckable(True) 21 | maIndicatorMenu.addAction(openAction) 22 | 23 | highAction = QAction('最高价', maIndicatorMenu) 24 | highAction.triggered.connect(self._act) 25 | highAction.setCheckable(True) 26 | maIndicatorMenu.addAction(highAction) 27 | 28 | lowAction = QAction('最低价', maIndicatorMenu) 29 | lowAction.triggered.connect(self._act) 30 | lowAction.setCheckable(True) 31 | maIndicatorMenu.addAction(lowAction) 32 | 33 | closeAction = QAction('收盘价', maIndicatorMenu) 34 | closeAction.triggered.connect(self._act) 35 | closeAction.setCheckable(True) 36 | maIndicatorMenu.addAction(closeAction) 37 | 38 | self._actions = [(openAction, 'open'), (highAction, 'high'), (lowAction, 'low'), (closeAction, 'close')] 39 | 40 | closeAction.setChecked(True) 41 | self._checkedAction = closeAction 42 | self._checkedIndicator = 'close' 43 | 44 | def _act(self): 45 | for action, indicator in self._actions: 46 | # new indicator 47 | if action.isChecked() and self._checkedAction != action: 48 | self._checkedAction = action 49 | self._checkedIndicator = indicator 50 | break 51 | 52 | self._checkedAction.setChecked(True) 53 | 54 | for action, indicator in self._actions: 55 | if self._checkedAction != action: 56 | action.setChecked(False) 57 | 58 | self._interface.setMaViewerIndicator(self._checkedIndicator) 59 | 60 | -------------------------------------------------------------------------------- /Stock/Common/Ui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Common/Ui/__init__.py -------------------------------------------------------------------------------- /Stock/Common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Common/__init__.py -------------------------------------------------------------------------------- /Stock/Config/Trade/DyStockWxScKeyConfigDlg.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from PyQt5.QtWidgets import QDialog, QLabel, QLineEdit, QPushButton, QVBoxLayout 4 | 5 | from DyCommon.DyCommon import DyCommon 6 | from Stock.Common.DyStockCommon import DyStockCommon 7 | from ..DyStockConfig import DyStockConfig 8 | 9 | 10 | class DyStockWxScKeyConfigDlg(QDialog): 11 | 12 | def __init__(self, parent=None): 13 | super().__init__(parent) 14 | 15 | self._read() 16 | self._initUi() 17 | 18 | def _initUi(self): 19 | self.setWindowTitle('配置-微信(实盘交易)') 20 | 21 | # 控件 22 | label = QLabel('Sever酱(方糖)的SCKEY ') 23 | 24 | self._lineEditScKey = QLineEdit(self._data["WxScKey"]) 25 | 26 | cancelPushButton = QPushButton('Cancel') 27 | okPushButton = QPushButton('OK') 28 | cancelPushButton.clicked.connect(self._cancel) 29 | okPushButton.clicked.connect(self._ok) 30 | 31 | # 布局 32 | vbox = QVBoxLayout() 33 | 34 | vbox.addWidget(label) 35 | vbox.addWidget(self._lineEditScKey) 36 | 37 | vbox.addWidget(QLabel(" ")) 38 | 39 | vbox.addWidget(okPushButton) 40 | vbox.addWidget(cancelPushButton) 41 | 42 | self.setLayout(vbox) 43 | 44 | def _read(self): 45 | file = DyStockConfig.getStockWxScKeyFileName() 46 | 47 | # open 48 | try: 49 | with open(file) as f: 50 | self._data = json.load(f) 51 | except: 52 | self._data = DyStockConfig.defaultWxScKey 53 | 54 | def _ok(self): 55 | # get data from UI 56 | data = {"WxScKey": self._lineEditScKey.text()} 57 | 58 | # config to variables 59 | DyStockConfig.configStockWxScKey(data) 60 | 61 | # save config 62 | file = DyStockConfig.getStockWxScKeyFileName() 63 | with open(file, 'w') as f: 64 | f.write(json.dumps(data, indent=4)) 65 | 66 | self.accept() 67 | 68 | def _cancel(self): 69 | self.reject() -------------------------------------------------------------------------------- /Stock/Config/Trade/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Config/Trade/__init__.py -------------------------------------------------------------------------------- /Stock/Config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Config/__init__.py -------------------------------------------------------------------------------- /Stock/Data/DyStockDataCommon.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class DyStockDataEventHandType: 4 | DY_STOCK_DATA_HIST_TICKS_HAND_NBR = 5 # TDX support max 5 live connections 5 | 6 | stockHistTicksHandNbr = DY_STOCK_DATA_HIST_TICKS_HAND_NBR 7 | ticksEngine = DY_STOCK_DATA_HIST_TICKS_HAND_NBR 8 | daysEngine = DY_STOCK_DATA_HIST_TICKS_HAND_NBR + 1 9 | strategyDataPrepare = DY_STOCK_DATA_HIST_TICKS_HAND_NBR + 2 10 | other = DY_STOCK_DATA_HIST_TICKS_HAND_NBR + 3 11 | 12 | nbr = DY_STOCK_DATA_HIST_TICKS_HAND_NBR + 4 13 | 14 | 15 | class DyStockHistTicksReqData: 16 | def __init__(self, code, date): 17 | self.code = code 18 | self.date = date 19 | 20 | class DyStockHistTicksAckData: 21 | noData = 'noData' 22 | 23 | def __init__(self, code, date, data): 24 | self.code = code 25 | self.date = date 26 | self.data = data 27 | 28 | """ 29 | ["股本指标", 30 | ["流通A股", 'float_a_shares'], 31 | ["A股合计", 'share_totala'] 32 | ], 33 | 34 | ["行情指标", 35 | ["开盘价", 'open'], 36 | ["收盘价", 'close'], 37 | ["最高价", 'high'], 38 | ["最低价", 'low'], 39 | ["成交量", 'volume'], 40 | ["成交额", 'amt'], 41 | ["换手率", 'turn'], 42 | ["净流入资金", 'mf_amt'], 43 | ["净流入量", 'mf_vol'] 44 | ], 45 | """ 46 | class DyStockDataCommon: 47 | # Wind的volume是成交量,单位是股数。数据库里的成交量也是股数。 48 | dayIndicators = ['open', 'high', 'low', 'close', 'volume', 'amt', 'turn', 'adjfactor'] 49 | adjFactor = 'adjfactor' 50 | 51 | logDetailsEnabled = False 52 | 53 | defaultHistTicksDataSource = '智能' # '新浪', '腾讯' , '网易', '智能' 54 | -------------------------------------------------------------------------------- /Stock/Data/Engine/Common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Data/Engine/Common/__init__.py -------------------------------------------------------------------------------- /Stock/Data/Engine/DyStockDataMainEngine.py: -------------------------------------------------------------------------------- 1 | from EventEngine.DyEventEngine import * 2 | from .DyStockDataEngine import * 3 | from ..DyStockDataCommon import * 4 | from DyCommon.DyCommon import * 5 | 6 | 7 | class DyStockDataMainEngine(object): 8 | """description of class""" 9 | 10 | def __init__(self): 11 | self._eventEngine = DyEventEngine(DyStockDataEventHandType.nbr, False) 12 | self._info = DyInfo(self._eventEngine) 13 | 14 | self._dataEngine = DyStockDataEngine(self._eventEngine, self._info) 15 | 16 | self._eventEngine.start() 17 | 18 | @property 19 | def eventEngine(self): 20 | return self._eventEngine 21 | 22 | @property 23 | def info(self): 24 | return self._info 25 | 26 | @property 27 | def dataEngine(self): 28 | return self._dataEngine 29 | 30 | def exit(self): 31 | pass -------------------------------------------------------------------------------- /Stock/Data/Engine/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Data/Engine/__init__.py -------------------------------------------------------------------------------- /Stock/Data/Gateway/__init__.py: -------------------------------------------------------------------------------- 1 | class __init__(object): 2 | """description of class""" 3 | 4 | 5 | -------------------------------------------------------------------------------- /Stock/Data/Ui/Other/DyStockDataHistTicksVerifyDlg.py: -------------------------------------------------------------------------------- 1 | from datetime import * 2 | 3 | from PyQt5.QtWidgets import QDialog, QGridLayout, QLabel, QLineEdit, QPushButton, QCheckBox 4 | 5 | 6 | class DyStockDataHistTicksVerifyDlg(QDialog): 7 | 8 | def __init__(self, data, parent=None): 9 | super().__init__(parent) 10 | 11 | self._data = data 12 | 13 | self._initUi() 14 | 15 | def _initUi(self): 16 | self.setWindowTitle('历史分笔数据校验') 17 | 18 | # 控件 19 | startDateLable = QLabel('开始日期') 20 | self._startDateLineEdit = QLineEdit(datetime.now().strftime("%Y-%m-%d")) 21 | 22 | endDateLable = QLabel('结束日期') 23 | self._endDateLineEdit = QLineEdit(datetime.now().strftime("%Y-%m-%d")) 24 | 25 | self._addCheckBox = QCheckBox('校验缺失历史分笔数据') 26 | self._addCheckBox.setChecked(True) 27 | self._deleteCheckBox = QCheckBox('校验无效历史分笔数据') 28 | #self._deleteCheckBox.setChecked(True) 29 | 30 | cancelPushButton = QPushButton('Cancel') 31 | okPushButton = QPushButton('OK') 32 | cancelPushButton.clicked.connect(self._cancel) 33 | okPushButton.clicked.connect(self._ok) 34 | 35 | # 布局 36 | grid = QGridLayout() 37 | grid.setSpacing(10) 38 | 39 | grid.addWidget(startDateLable, 0, 0) 40 | grid.addWidget(self._startDateLineEdit, 1, 0) 41 | 42 | grid.addWidget(endDateLable, 0, 1) 43 | grid.addWidget(self._endDateLineEdit, 1, 1) 44 | 45 | grid.addWidget(self._addCheckBox, 2, 0) 46 | grid.addWidget(self._deleteCheckBox, 2, 1) 47 | 48 | grid.addWidget(okPushButton, 3, 1) 49 | grid.addWidget(cancelPushButton, 3, 0) 50 | 51 | self.setLayout(grid) 52 | 53 | def _ok(self): 54 | self._data['startDate'] = self._startDateLineEdit.text() 55 | self._data['endDate'] = self._endDateLineEdit.text() 56 | 57 | self._data['verifyMissing'] = self._addCheckBox.isChecked() 58 | self._data['verifyInvalid'] = self._deleteCheckBox.isChecked() 59 | 60 | self.accept() 61 | 62 | def _cancel(self): 63 | self.reject() 64 | -------------------------------------------------------------------------------- /Stock/Data/Ui/Other/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Data/Ui/Other/__init__.py -------------------------------------------------------------------------------- /Stock/Data/Ui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Data/Ui/__init__.py -------------------------------------------------------------------------------- /Stock/Data/Utility/Other/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Data/Utility/Other/__init__.py -------------------------------------------------------------------------------- /Stock/Data/Utility/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Data/Utility/__init__.py -------------------------------------------------------------------------------- /Stock/Data/Viewer/FocusAnalysis/DyStockDataFocusAnalysisMainWindow.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QDockWidget 2 | 3 | from .DyStockDataFocusAnalysisWidget import * 4 | from DyCommon.Ui.DyBasicMainWindow import * 5 | from ....Common.DyStockCommon import * 6 | 7 | 8 | class DyStockDataFocusAnalysisMainWindow(DyBasicMainWindow): 9 | """ 热点分析主窗口 """ 10 | 11 | name = 'DyStockDataFocusAnalysisMainWindow' 12 | 13 | def __init__(self, dataWindow, focusStrengthDf, focusInfoPoolDict): 14 | super().__init__(None, None) 15 | 16 | self._dataWindow = dataWindow 17 | self._focusStrengthDf = focusStrengthDf 18 | self._focusInfoPoolDict = focusInfoPoolDict 19 | 20 | self._initUi() 21 | 22 | def _initUi(self): 23 | """ 初始化界面 """ 24 | self.setWindowTitle('热点分析') 25 | 26 | self._initCentral() 27 | self._initToolBar() 28 | 29 | self._loadWindowSettings() 30 | 31 | def _initCentral(self): 32 | """ 初始化中心区域 """ 33 | widgetStats, dockStats = self._createDock(DyStockDataFocusAnalysisWidget, '热点强度', Qt.BottomDockWidgetArea, self._dataWindow, self._focusStrengthDf.copy(), self._focusInfoPoolDict) 34 | 35 | def _initToolBar(self): 36 | """ 初始化工具栏 """ 37 | toolBar = self.addToolBar('工具栏') 38 | toolBar.setObjectName('工具栏') 39 | 40 | # 创建工具栏的操作 41 | action = QAction('画图', self) 42 | action.triggered.connect(self._plotAct) 43 | toolBar.addAction(action) 44 | 45 | def closeEvent(self, event): 46 | """ 关闭事件 """ 47 | return super().closeEvent(event) 48 | 49 | def _plotAct(self): 50 | self._dataWindow.plotFocusStrength(DyStockCommon.shIndex, self._focusStrengthDf) -------------------------------------------------------------------------------- /Stock/Data/Viewer/FocusAnalysis/DyStockDataFocusAnalysisWidget.py: -------------------------------------------------------------------------------- 1 | from DyCommon.Ui.DyStatsDataFrameTableWidget import * 2 | from .DyStockDataFocusInfoPoolWidget import * 3 | 4 | 5 | class DyStockDataFocusAnalysisWidget(DyStatsDataFrameTableWidget): 6 | """ 热点分析窗口 """ 7 | 8 | def __init__(self, dataWindow, focusStrengthDf, focusInfoPoolDict): 9 | # change index to string 10 | focusStrengthDf.index = focusStrengthDf.index.map(lambda x: x.strftime('%Y-%m-%d')) 11 | 12 | # change index to column 13 | focusStrengthDf.reset_index(inplace=True) 14 | focusStrengthDf.rename(columns={'index':'日期'}, inplace=True) 15 | 16 | super().__init__(focusStrengthDf) 17 | 18 | self._dataWindow = dataWindow 19 | self._focusInfoPoolDict = focusInfoPoolDict 20 | 21 | self._windows = [] 22 | 23 | self.itemDoubleClicked.connect(self._itemDoubleClicked) 24 | 25 | def _itemDoubleClicked(self, item): 26 | row = self.row(item) 27 | date = self[row, 0] 28 | 29 | window = DyStockDataFocusInfoPoolWidget(self._dataWindow.dataViewer, date, self._focusInfoPoolDict[date]) 30 | window.showMaximized() 31 | 32 | self._windows.append(window) 33 | 34 | -------------------------------------------------------------------------------- /Stock/Data/Viewer/FocusAnalysis/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Data/Viewer/FocusAnalysis/__init__.py -------------------------------------------------------------------------------- /Stock/Data/Viewer/IndexConsecutiveDayLineStats/DyStockDataIndexConsecutiveDayLineStatsTabWidget.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QTabWidget 2 | 3 | from ....Common.DyStockCommon import * 4 | from .DyStockDataIndexConsecutiveDayLineStatsWidget import * 5 | 6 | 7 | class DyStockDataIndexConsecutiveDayLineStatsTabWidget(QTabWidget): 8 | 9 | def __init__(self, dataWindow, startDate, endDate, indexCountedDfs, greenLine=True): 10 | super().__init__() 11 | 12 | for index, df in indexCountedDfs.items(): 13 | self.addTab(DyStockDataIndexConsecutiveDayLineStatsWidget(dataWindow, index, df), 14 | DyStockCommon.indexes[index] 15 | ) 16 | 17 | self.setWindowTitle('指数连续日{0}线统计[{1},{2}]'.format('阴' if greenLine else '阳', startDate, endDate)) -------------------------------------------------------------------------------- /Stock/Data/Viewer/IndexConsecutiveDayLineStats/DyStockDataIndexConsecutiveDayLineStatsWidget.py: -------------------------------------------------------------------------------- 1 | from ....Common.DyStockCommon import * 2 | from DyCommon.Ui.DyStatsDataFrameTableWidget import * 3 | 4 | 5 | class DyStockDataIndexConsecutiveDayLineStatsWidget(DyStatsDataFrameTableWidget): 6 | 7 | def __init__(self, dataWindow, indexCode, df, parent=None): 8 | super().__init__(df, parent) 9 | 10 | self._indexCode = indexCode 11 | self._dataWindow = dataWindow 12 | 13 | self.itemDoubleClicked.connect(self._itemDoubleClicked) 14 | 15 | # set foreground of item 16 | cols = [] 17 | for i, name in enumerate(self.getColNames()): 18 | if '(%)' in name: 19 | cols.append(i) 20 | 21 | for row in range(self.rowCount()): 22 | for col in cols: 23 | value = self[row, col] 24 | if value is None: continue 25 | 26 | if value > 0: 27 | self.setItemForeground(row, col, Qt.red) 28 | elif value < 0: 29 | self.setItemForeground(row, col, Qt.darkGreen) 30 | 31 | def _itemDoubleClicked(self, item): 32 | # get start date 33 | row = self.row(item) 34 | startDate = self[row, '开始时间'] 35 | 36 | # plot candle stick 37 | self._dataWindow.dataViewer.plotCandleStick(self._indexCode, [-DyStockCommon.dayKChartPeriodNbr, startDate, DyStockCommon.dayKChartPeriodNbr]) 38 | -------------------------------------------------------------------------------- /Stock/Data/Viewer/IndexConsecutiveDayLineStats/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Data/Viewer/IndexConsecutiveDayLineStats/__init__.py -------------------------------------------------------------------------------- /Stock/Data/Viewer/JaccardIndex/DyStockDataJaccardIndexCodeSetDetailsWidget.py: -------------------------------------------------------------------------------- 1 | import operator 2 | 3 | from PyQt5.QtGui import QColor 4 | from PyQt5.QtWidgets import QTabWidget 5 | 6 | from ....Common.Ui.Basic.DyStockTableWidget import * 7 | 8 | 9 | class DyStockDataJaccardIndexCodeSetDetailsWidget(QTabWidget): 10 | 11 | def __init__(self, dataViewer, baseDate, orgDf, codeSetDf, codeIncreaseDfDict, codeTable): 12 | super().__init__() 13 | 14 | self._dataViewer = dataViewer 15 | self._baseDate = baseDate 16 | 17 | self._orgDf = orgDf 18 | self._codeSetDf = codeSetDf 19 | self._codeIncreaseDfDict = codeIncreaseDfDict 20 | self._codeTable = codeTable 21 | 22 | self._initUi() 23 | 24 | self._setColor() 25 | 26 | def _initUi(self): 27 | self.setWindowTitle('代码集明细[{0}]'.format(self._baseDate)) 28 | 29 | for column in self._codeSetDf.columns: 30 | codeSet = self._codeSetDf.ix[self._baseDate, column] 31 | 32 | newColumn = column[2:-1] 33 | 34 | # get days of A set and B set 35 | a, b = newColumn.split(';') 36 | aDays, _ = a.split(',') 37 | bDays, _ = b.split(',') 38 | aDays, bDays = int(aDays), int(bDays) 39 | 40 | # get corresponding increase for each code 41 | header = ['代码', '名称', '最前{0}涨幅(%)'.format(aDays), '前{0}涨幅(%)'.format(bDays)] 42 | rows = [] 43 | for code in codeSet: 44 | # 最前{0}涨幅(%) position 45 | baseDatePos = self._orgDf.index.get_loc(self._baseDate) 46 | pos = baseDatePos - (bDays - aDays) 47 | posDate = self._orgDf.index[pos] 48 | 49 | row = [code, self._codeTable[code], 50 | self._codeIncreaseDfDict[code].ix[posDate, a], 51 | self._codeIncreaseDfDict[code].ix[self._baseDate, b] 52 | ] 53 | 54 | rows.append(row) 55 | 56 | rows.sort(key=operator.itemgetter(3), reverse=True) 57 | 58 | widget = DyStockSelectStrategySelectResultWidget(self._dataViewer, self._baseDate) 59 | widget.append(rows, header) 60 | 61 | self.addTab(widget, column) 62 | 63 | def _setColor(self): 64 | for i in range(self.count()): 65 | widget = self.widget(i) 66 | 67 | for row in range(widget.rowCount()): 68 | if widget[row, 2] < widget[row, 3]: 69 | widget.setRowForeground(row, QColor('#4169E1')) -------------------------------------------------------------------------------- /Stock/Data/Viewer/JaccardIndex/DyStockDataJaccardIndexCodeSetWidget.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtGui import QColor 2 | 3 | from DyCommon.Ui.DyStatsDataFrameTableWidget import * 4 | from .DyStockDataJaccardIndexCodeSetDetailsWidget import * 5 | 6 | 7 | class DyStockDataJaccardIndexCodeSetWidget(DyStatsDataFrameTableWidget): 8 | """ 杰卡德指数代码交集窗口 """ 9 | 10 | def __init__(self, dataViewer, orgDf, codeSetDf, codeIncreaseDfDict, codeTable, parent=None): 11 | self._dataViewer = dataViewer 12 | self._orgDf = orgDf 13 | self._codeSetDf = codeSetDf 14 | self._codeIncreaseDfDict = codeIncreaseDfDict 15 | self._codeTable = codeTable 16 | 17 | self._windows = [] 18 | 19 | df = codeSetDf 20 | if not df.empty: 21 | df = df.applymap(lambda x: ','.join(x) if len(x) <= 5 else ','.join(list(x)[:5]) + ',...总共{0}'.format(len(x))) 22 | 23 | # change index to column 24 | df.reset_index(inplace=True) 25 | df.rename(columns={'index':'日期'}, inplace=True) 26 | 27 | super().__init__(df, parent) 28 | 29 | self.itemDoubleClicked.connect(self._itemDoubleClicked) 30 | 31 | def _itemDoubleClicked(self, item): 32 | row = item.row() 33 | date = self[row, '日期'] 34 | 35 | window = DyStockDataJaccardIndexCodeSetDetailsWidget(self._dataViewer, date, self._orgDf, self._codeSetDf, self._codeIncreaseDfDict, self._codeTable) 36 | 37 | rect = QApplication.desktop().availableGeometry() 38 | taskBarHeight = QApplication.desktop().height() - rect.height() 39 | 40 | window.resize(rect.width()//3 * 2, rect.height() - taskBarHeight) 41 | window.move((rect.width() - window.width())//2, 0) 42 | 43 | window.show() 44 | 45 | self._windows.append(window) 46 | -------------------------------------------------------------------------------- /Stock/Data/Viewer/JaccardIndex/DyStockDataJaccardIndexCodeSetWidgets.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QTabWidget 2 | 3 | from ....Common.DyStockCommon import * 4 | from .DyStockDataJaccardIndexCodeSetWidget import * 5 | 6 | 7 | class DyStockDataJaccardIndexCodeSetWidgets(QTabWidget): 8 | 9 | def __init__(self, dataViewer, orgDfs, codeSetDfs, codeIncreaseDfDicts, codeTable): 10 | super().__init__() 11 | 12 | self._dataViewer = dataViewer 13 | self._orgDfs = orgDfs 14 | self._codeSetDfs = codeSetDfs 15 | self._codeIncreaseDfDicts = codeIncreaseDfDicts 16 | self._codeTable = codeTable 17 | 18 | self._initUi() 19 | 20 | self.currentChanged.connect(self._onChange) 21 | 22 | def _initUi(self): 23 | for index in sorted(self._orgDfs): 24 | widget = DyStockDataJaccardIndexCodeSetWidget(self._dataViewer, self._orgDfs[index], self._codeSetDfs[index], self._codeIncreaseDfDicts[index], self._codeTable) 25 | 26 | self.addTab(widget, DyStockCommon.indexes[index]) 27 | 28 | def setJaccardIndexWidgets(self, jaccardIndexWidgets): 29 | self._jaccardIndexWidgets = jaccardIndexWidgets 30 | 31 | def _onChange(self): 32 | self._jaccardIndexWidgets.blockSignals(True) 33 | self._jaccardIndexWidgets.setCurrentIndex(self.currentIndex()) 34 | self._jaccardIndexWidgets.blockSignals(False) 35 | -------------------------------------------------------------------------------- /Stock/Data/Viewer/JaccardIndex/DyStockDataJaccardIndexMainWindow.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QDockWidget 2 | 3 | from DyCommon.Ui.DyBasicMainWindow import * 4 | from .DyStockDataJaccardIndexCodeSetWidgets import * 5 | from .DyStockDataJaccardIndexWidgets import * 6 | from .DyStockDataJaccardIndexPlotDlg import * 7 | 8 | 9 | class DyStockDataJaccardIndexMainWindow(DyBasicMainWindow): 10 | name = 'DyStockDataJaccardIndexMainWindow' 11 | 12 | def __init__(self, orgDfs, jaccardDfs, codeSetDfs, codeIncreaseDfDicts, codeTable, dataViewer, parent=None): 13 | super().__init__(None, None, parent) 14 | 15 | self._orgDfs = orgDfs 16 | self._jaccardDfs = jaccardDfs 17 | self._codeSetDfs = codeSetDfs 18 | self._codeIncreaseDfDicts = codeIncreaseDfDicts 19 | self._codeTable = codeTable 20 | self._dataViewer = dataViewer 21 | 22 | self._initUi() 23 | 24 | def _initUi(self): 25 | """初始化界面""" 26 | self.setWindowTitle('杰卡德指数') 27 | 28 | self._initCentral() 29 | self._initToolBar() 30 | 31 | self._loadWindowSettings() 32 | 33 | def _initCentral(self): 34 | """初始化中心区域""" 35 | widgetJaccardIndexCodeSet, dockJaccardIndexCodeSet = self._createDock(DyStockDataJaccardIndexCodeSetWidgets, '代码交集', Qt.TopDockWidgetArea, self._dataViewer, self._orgDfs, self._codeSetDfs, self._codeIncreaseDfDicts, self._codeTable) 36 | self._widgetJaccardIndex, dockJaccardIndex = self._createDock(DyStockDataJaccardIndexWidgets, '杰卡德指数', Qt.BottomDockWidgetArea, self._jaccardDfs) 37 | 38 | widgetJaccardIndexCodeSet.setJaccardIndexWidgets(self._widgetJaccardIndex) 39 | self._widgetJaccardIndex.setCodeSetWidgets(widgetJaccardIndexCodeSet) 40 | 41 | def _initToolBar(self): 42 | """ 初始化工具栏 """ 43 | # 添加工具栏 44 | toolBar = self.addToolBar('工具栏') 45 | toolBar.setObjectName('工具栏') 46 | 47 | # 创建操作 48 | action = QAction('画图', self) 49 | action.triggered.connect(self._plotAct) 50 | toolBar.addAction(action) 51 | 52 | def closeEvent(self, event): 53 | """关闭事件""" 54 | return super().closeEvent(event) 55 | 56 | def _plotAct(self): 57 | index, jaccardDf = self._widgetJaccardIndex.getActiveIndexJaccardDf() 58 | if jaccardDf.empty: return 59 | 60 | data = {} 61 | if DyStockDataJaccardIndexPlotDlg(data, list(jaccardDf.columns), self).exec_(): 62 | self._dataViewer.plotJaccardIndex(index, jaccardDf, data['data']) 63 | -------------------------------------------------------------------------------- /Stock/Data/Viewer/JaccardIndex/DyStockDataJaccardIndexPlotDlg.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QDialog, QGridLayout, QPushButton, QApplication, QMessageBox 2 | 3 | from DyCommon.Ui.DyTreeWidget import * 4 | 5 | 6 | class DyStockDataJaccardIndexPlotDlg(QDialog): 7 | """ 选择哪些杰卡德指数可视化 8 | """ 9 | def __init__(self, data, columns, parent=None): 10 | super().__init__(parent) 11 | 12 | self._data = data 13 | self._columns = columns 14 | 15 | self._initUi() 16 | 17 | def _initUi(self): 18 | self.setWindowTitle('选择哪些杰卡德指数可视化') 19 | 20 | # 控件 21 | cancelPushButton = QPushButton('Cancel') 22 | okPushButton = QPushButton('OK') 23 | cancelPushButton.clicked.connect(self._cancel) 24 | okPushButton.clicked.connect(self._ok) 25 | 26 | self._jaccardIndexWidget = DyTreeWidget([[x] for x in self._columns]) 27 | 28 | # 布局 29 | grid = QGridLayout() 30 | grid.setSpacing(10) 31 | 32 | grid.addWidget(self._jaccardIndexWidget, 0, 0, 20, 2) 33 | 34 | grid.addWidget(okPushButton, 20, 1) 35 | grid.addWidget(cancelPushButton, 20, 0) 36 | 37 | self.setLayout(grid) 38 | self.resize(QApplication.desktop().size().width()//6, QApplication.desktop().size().height()//2) 39 | 40 | def _ok(self): 41 | names = self._jaccardIndexWidget.getCheckedTexts() 42 | 43 | if not names: 44 | QMessageBox.warning(self, '错误', '没有选择杰卡德指数!') 45 | return 46 | 47 | self._data['data'] = names 48 | 49 | self.accept() 50 | 51 | def _cancel(self): 52 | self.reject() 53 | -------------------------------------------------------------------------------- /Stock/Data/Viewer/JaccardIndex/DyStockDataJaccardIndexWidget.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtGui import QColor 2 | 3 | from DyCommon.Ui.DyStatsDataFrameTableWidget import * 4 | 5 | 6 | class DyStockDataJaccardIndexWidget(DyStatsDataFrameTableWidget): 7 | """ 杰卡德指数窗口 """ 8 | 9 | def __init__(self, stockIndex, jaccardDf, parent=None): 10 | 11 | self._jaccardDf = jaccardDf 12 | self._stockIndex = stockIndex 13 | 14 | df = jaccardDf 15 | if not df.empty: 16 | # change index to column 17 | df = jaccardDf.reset_index() 18 | df.rename(columns={'index':'日期'}, inplace=True) 19 | 20 | super().__init__(df, parent) 21 | 22 | # set color 23 | self._setColor() 24 | 25 | def _setColor(self): 26 | pass -------------------------------------------------------------------------------- /Stock/Data/Viewer/JaccardIndex/DyStockDataJaccardIndexWidgets.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QTabWidget 2 | 3 | from ....Common.DyStockCommon import * 4 | from .DyStockDataJaccardIndexWidget import * 5 | 6 | 7 | class DyStockDataJaccardIndexWidgets(QTabWidget): 8 | 9 | def __init__(self, jaccardDfs): 10 | super().__init__() 11 | 12 | self._jaccardDfs = jaccardDfs 13 | 14 | self._initUi() 15 | 16 | self.currentChanged.connect(self._onChange) 17 | 18 | def _initUi(self): 19 | for index in sorted(self._jaccardDfs): 20 | widget = DyStockDataJaccardIndexWidget(index, self._jaccardDfs[index]) 21 | 22 | self.addTab(widget, DyStockCommon.indexes[index]) 23 | 24 | def getActiveIndexJaccardDf(self): 25 | indexName = self.tabText(self.currentIndex()) 26 | index = DyStockCommon.getIndexByName(indexName) 27 | 28 | return index, self._jaccardDfs[index] 29 | 30 | def setCodeSetWidgets(self, codeSetWidgets): 31 | self._codeSetWidgets = codeSetWidgets 32 | 33 | def _onChange(self): 34 | self._codeSetWidgets.blockSignals(True) 35 | self._codeSetWidgets.setCurrentIndex(self.currentIndex()) 36 | self._codeSetWidgets.blockSignals(False) 37 | -------------------------------------------------------------------------------- /Stock/Data/Viewer/JaccardIndex/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Data/Viewer/JaccardIndex/__init__.py -------------------------------------------------------------------------------- /Stock/Data/Viewer/LimitUpStats/DyStockDataLimitUpStatsMainWindow.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QDockWidget 2 | 3 | from DyCommon.Ui.DyBasicMainWindow import * 4 | from DyCommon.Ui.DyDataFrameTableWidget import * 5 | from ....Common.DyStockCommon import * 6 | 7 | 8 | class DyStockDataLimitUpStatsMainWindow(DyBasicMainWindow): 9 | name = 'DyStockDataLimitUpStatsMainWindow' 10 | 11 | def __init__(self, dataWindow, df): 12 | super().__init__(None, None) 13 | 14 | self._dataWindow = dataWindow 15 | self._df = df 16 | 17 | self._initUi() 18 | 19 | def _initUi(self): 20 | """ 初始化界面 """ 21 | self.setWindowTitle('封板率统计[{0}~{1}]'.format(self._df.index[0].strftime("%Y-%m-%d"), self._df.index[-1].strftime("%Y-%m-%d"))) 22 | 23 | self._initCentral() 24 | self._initToolBar() 25 | 26 | self._loadWindowSettings() 27 | 28 | def _initCentral(self): 29 | """ 初始化中心区域 """ 30 | # change index to string 31 | df = self._df.copy() 32 | df.index = df.index.map(lambda x: x.strftime('%Y-%m-%d')) 33 | 34 | widgetStats, dockStats = self._createDock(DyDataFrameTableWidget, '封板率统计', Qt.BottomDockWidgetArea, df) 35 | 36 | def _initToolBar(self): 37 | """ 初始化工具栏 """ 38 | toolBar = self.addToolBar('工具栏') 39 | toolBar.setObjectName('工具栏') 40 | 41 | # 创建工具栏的操作 42 | action = QAction('画图', self) 43 | action.triggered.connect(self._plotAct) 44 | toolBar.addAction(action) 45 | 46 | def closeEvent(self, event): 47 | """ 关闭事件 """ 48 | return super().closeEvent(event) 49 | 50 | def _plotAct(self): 51 | self._dataWindow.plotLimitUpStats(DyStockCommon.shIndex, self._df) -------------------------------------------------------------------------------- /Stock/Data/Viewer/LimitUpStats/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Data/Viewer/LimitUpStats/__init__.py -------------------------------------------------------------------------------- /Stock/Data/Viewer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Data/Viewer/__init__.py -------------------------------------------------------------------------------- /Stock/Data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Data/__init__.py -------------------------------------------------------------------------------- /Stock/Select/Engine/DyStockSelectMainEngine.py: -------------------------------------------------------------------------------- 1 | from EventEngine.DyEventEngine import * 2 | from ..DyStockSelectCommon import * 3 | from DyCommon.DyCommon import * 4 | from .DyStockSelectSelectEngine import * 5 | from .Regression.DyStockSelectRegressionEngine import * 6 | from ...Data.Viewer.DyStockDataViewer import * 7 | from .DyStockSelectViewerEngine import * 8 | 9 | 10 | class DyStockSelectMainEngine(object): 11 | 12 | def __init__(self): 13 | self._eventEngine = DyEventEngine(DyStockSelectEventHandType.nbr, False) 14 | self._info = DyInfo(self._eventEngine) 15 | 16 | self._selectEngine = DyStockSelectSelectEngine(self._eventEngine, self._info) 17 | self._regressionEngine = DyStockSelectRegressionEngine(self._eventEngine, self._info) 18 | self._viewerEngine = DyStockSelectViewerEngine(self._eventEngine, self._info) 19 | 20 | self._initDataViewer() 21 | 22 | self._eventEngine.start() 23 | 24 | @property 25 | def eventEngine(self): 26 | return self._eventEngine 27 | 28 | @property 29 | def info(self): 30 | return self._info 31 | 32 | def exit(self): 33 | pass 34 | 35 | def _initDataViewer(self): 36 | errorInfo = DyErrorInfo(self._eventEngine) 37 | dataEngine = DyStockDataEngine(self._eventEngine, errorInfo, False) 38 | self._dataViewer = DyStockDataViewer(dataEngine, errorInfo) 39 | 40 | @property 41 | def dataViewer(self): 42 | return self._dataViewer 43 | -------------------------------------------------------------------------------- /Stock/Select/Engine/Regression/DyStockSelectRegressionEngineProcess.py: -------------------------------------------------------------------------------- 1 | import queue 2 | 3 | from DyCommon.DyCommon import * 4 | from EventEngine.DyEvent import * 5 | from EventEngine.DyEventEngine import * 6 | from ..DyStockSelectSelectEngine import * 7 | from ....Common.DyStockCommon import DyStockCommon 8 | 9 | 10 | def dyStockSelectRegressionEngineProcess(outQueue, inQueue, tradeDays, strategy, codes, histDaysDataSource): 11 | strategyCls = strategy['class'] 12 | parameters = strategy['param'] 13 | 14 | DyStockCommon.defaultHistDaysDataSource = histDaysDataSource 15 | 16 | dummyEventEngine = DyDummyEventEngine() 17 | queueInfo = DyQueueInfo(outQueue) 18 | 19 | selectEngine = DyStockSelectSelectEngine(dummyEventEngine, queueInfo, False) 20 | selectEngine.setTestedStocks(codes) 21 | 22 | for day in tradeDays: 23 | try: 24 | event = inQueue.get_nowait() 25 | except queue.Empty: 26 | pass 27 | 28 | parameters['基准日期'] = day 29 | 30 | if selectEngine.runStrategy(strategyCls, parameters): 31 | event = DyEvent(DyEventType.stockSelectStrategyRegressionAck) 32 | event.data['class'] = strategyCls 33 | event.data['period'] = [tradeDays[0], tradeDays[-1]] 34 | event.data['day'] = day 35 | event.data['result'] = selectEngine.result 36 | 37 | outQueue.put(event) 38 | else: 39 | queueInfo.print('回归选股策略失败:{0}, 周期[{1}, {2}], 基准日期{3}'.format(strategyCls.chName, tradeDays[0], tradeDays[-1], day), DyLogData.error) 40 | 41 | -------------------------------------------------------------------------------- /Stock/Select/Engine/Regression/DyStockSelectRegressionEngineProxy.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | import threading 3 | 4 | from .DyStockSelectRegressionEngineProcess import * 5 | from ....Common.DyStockCommon import DyStockCommon 6 | 7 | 8 | class DyStockSelectRegressionEngineProxy(threading.Thread): 9 | threadMode = False # only for debug without care about errors 10 | 11 | 12 | def __init__(self, eventEngine): 13 | super().__init__() 14 | 15 | self._eventEngine = eventEngine 16 | 17 | self._ctx = multiprocessing.get_context('spawn') 18 | self._queue = self._ctx.Queue() # queue to receive event from child processes 19 | 20 | self._processes = [] 21 | self._childQueues = [] 22 | 23 | self.start() 24 | 25 | def run(self): 26 | while True: 27 | event = self._queue.get() 28 | 29 | self._eventEngine.put(event) 30 | 31 | def startRegression(self, tradeDays, strategy, codes = None): 32 | """ 33 | @strategy: {'class':strategyCls, 'param': strategy paramters} 34 | """ 35 | _childQueue = self._ctx.Queue() 36 | self._childQueues.append(_childQueue) 37 | 38 | if self.threadMode: 39 | p = threading.Thread(target=dyStockSelectRegressionEngineProcess, args=(self._queue, _childQueue, tradeDays, strategy, codes, DyStockCommon.defaultHistDaysDataSource)) 40 | else: 41 | p = self._ctx.Process(target=dyStockSelectRegressionEngineProcess, args=(self._queue, _childQueue, tradeDays, strategy, codes, DyStockCommon.defaultHistDaysDataSource)) 42 | 43 | p.start() 44 | 45 | self._processes.append(p) 46 | -------------------------------------------------------------------------------- /Stock/Select/Engine/Regression/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Select/Engine/Regression/__init__.py -------------------------------------------------------------------------------- /Stock/Select/Engine/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Select/Engine/__init__.py -------------------------------------------------------------------------------- /Stock/Select/Strategy/Cta/DySS_HighTurn.py: -------------------------------------------------------------------------------- 1 | import operator 2 | 3 | from ..DyStockSelectStrategyTemplate import * 4 | from ....Data.Utility.DyStockDataUtility import * 5 | 6 | 7 | class DySS_HighTurn(DyStockSelectStrategyTemplate): 8 | name = 'DySS_HighTurn' 9 | chName = '高换手' 10 | 11 | autoFillDays = True 12 | optimizeAutoFillDays = True 13 | 14 | colNames = ['代码', '名称', '排名', '换手率(%)', '成交额(亿)', '流通股本(亿股)', '昨日换手率(%)'] 15 | 16 | param = OrderedDict\ 17 | ([ 18 | ('基准日期', datetime.today().strftime("%Y-%m-%d")), 19 | ('选几只股票', 100) 20 | ]) 21 | 22 | 23 | def __init__(self, param, info): 24 | super().__init__(param, info) 25 | 26 | # unpack parameters 27 | self._baseDate = param['基准日期'] 28 | self._selectStockNbr = param['选几只股票'] 29 | 30 | self.__data = {} 31 | 32 | def onDaysLoad(self): 33 | return self._baseDate, -1 34 | 35 | def onInit(self, dataEngine, errorDataEngine): 36 | self._daysEngine = dataEngine.daysEngine 37 | 38 | self._stockAllCodes = self._daysEngine.stockAllCodes 39 | 40 | def onStockDays(self, code, df): 41 | turn = df.ix[-1, 'turn'] 42 | amt = df.ix[-1, 'amt']/10**8 43 | volume = df.ix[-1, 'volume'] 44 | preTurn = df.ix[-2, 'turn'] 45 | 46 | float = volume/turn*100/10**8 47 | 48 | self.__data[code] = [self._stockAllCodes[code], turn, amt, float, preTurn] 49 | 50 | def onDone(self): 51 | df = pd.DataFrame(self.__data).T 52 | start = self.colNames.index('换手率(%)') 53 | df.rename(columns={i: x for i, x in enumerate(['名称'] + self.colNames[start:])}, inplace=True) 54 | 55 | series = df['换手率(%)'].rank(ascending=False) 56 | rankSeries = series 57 | 58 | series = df['成交额(亿)'].rank(ascending=False) 59 | rankSeries += series 60 | 61 | # 流通股本越大越好,这样对相对的换手率形成制约。盘子越大的股票,意味着大资金关注多,一般认为大资金是聪明钱。 62 | series = df['流通股本(亿股)'].rank(ascending=False) 63 | rankSeries += series 64 | 65 | rankSeries = rankSeries.rank() 66 | rankSeries.name = '排名' 67 | 68 | df = pd.concat([rankSeries, df], axis=1) 69 | df.sort_values('排名', ascending=True, inplace=True) 70 | 71 | # set result 72 | if self._selectStockNbr > 0: 73 | df = df.ix[:self._selectStockNbr] 74 | 75 | df = df.reindex(columns=self.colNames[1:]) 76 | df.reset_index(inplace=True) 77 | 78 | self._result = df.values.tolist() 79 | 80 | -------------------------------------------------------------------------------- /Stock/Select/Strategy/Cta/DySS_InsideRedLine.py: -------------------------------------------------------------------------------- 1 | import operator 2 | 3 | from ..DyStockSelectStrategyTemplate import * 4 | from ....Data.Utility.DyStockDataUtility import * 5 | 6 | 7 | class DySS_InsideRedLine(DyStockSelectStrategyTemplate): 8 | name = 'DySS_InsideRedLine' 9 | chName = '阳线包含' 10 | 11 | autoFillDays = True 12 | optimizeAutoFillDays = True 13 | 14 | colNames = ['代码', '名称'] 15 | 16 | param = OrderedDict\ 17 | ([ 18 | ('基准日期', datetime.today().strftime("%Y-%m-%d")), 19 | ('连续阴线数', 3), 20 | ]) 21 | 22 | 23 | def __init__(self, param, info): 24 | super().__init__(param, info) 25 | 26 | # unpack parameters 27 | self._baseDate = param['基准日期'] 28 | self._consecutiveGreenLineNbr = param['连续阴线数'] 29 | 30 | def onDaysLoad(self): 31 | return self._baseDate, -self._consecutiveGreenLineNbr-1 32 | 33 | def onInit(self, dataEngine, errorDataEngine): 34 | self._daysEngine = dataEngine.daysEngine 35 | 36 | self._stockAllCodes = self._daysEngine.stockAllCodes 37 | 38 | def onStockDays(self, code, df): 39 | increases = df['close'].pct_change()*100 40 | 41 | if increases[-self._consecutiveGreenLineNbr-1] < 7: 42 | return 43 | 44 | low = df['low'][-self._consecutiveGreenLineNbr-1] 45 | 46 | volumes = df['volume'] 47 | for i in range(-self._consecutiveGreenLineNbr-1, -1): 48 | if volumes[i] < volumes[i+1]: 49 | return 50 | 51 | if df['low'][-self._consecutiveGreenLineNbr:].min() < low: 52 | return 53 | 54 | highs = df['high'] 55 | if highs[-self._consecutiveGreenLineNbr-1:-self._consecutiveGreenLineNbr+1].max() != highs[-self._consecutiveGreenLineNbr-1:].max(): 56 | return 57 | 58 | # 设置结果 59 | group = [code, self._stockAllCodes[code]] 60 | self._result.append(group) 61 | -------------------------------------------------------------------------------- /Stock/Select/Strategy/Cta/DySS_LimitUpRise.py: -------------------------------------------------------------------------------- 1 | import operator 2 | 3 | from ..DyStockSelectStrategyTemplate import * 4 | from ....Data.Utility.DyStockDataUtility import * 5 | 6 | 7 | class DySS_LimitUpRise(DyStockSelectStrategyTemplate): 8 | name = 'DySS_LimitUpRise' 9 | chName = '板后小阳' 10 | 11 | colNames = ['代码', '名称', '涨幅(%)'] 12 | 13 | param = OrderedDict\ 14 | ([ 15 | ('基准日期', datetime.today().strftime("%Y-%m-%d")) 16 | ]) 17 | 18 | def __init__(self, param, info): 19 | super().__init__(param, info) 20 | 21 | # unpack parameters 22 | self._baseDate = param['基准日期'] 23 | 24 | def onDaysLoad(self): 25 | return self._baseDate, -30 26 | 27 | def onInit(self, dataEngine, errorDataEngine): 28 | self._daysEngine = dataEngine.daysEngine 29 | 30 | self._stockAllCodes = self._daysEngine.stockAllCodes 31 | 32 | #self._startDay = self._daysEngine.tDaysOffset(self._baseDate, -1) 33 | #self._endDay = self._daysEngine.tDaysOffset(self._baseDate, 0) 34 | 35 | def onStockDays(self, code, df): 36 | closePctChange = df['close'].pct_change().dropna() 37 | if closePctChange.shape[0] != 30: return 38 | 39 | if closePctChange[-2] < DyStockCommon.limitUpPct/100: return 40 | if closePctChange[-1] <= 0: return 41 | 42 | if (closePctChange >= DyStockCommon.limitUpPct/100).sum() > 1: return 43 | 44 | if df.ix[-1, 'turn']/df.ix[-2, 'turn'] >= 2: return 45 | 46 | # 设置结果 47 | pair = [code, self._stockAllCodes[code], closePctChange[-1]*100] 48 | self._result.append(pair) 49 | 50 | # 设置实盘结果 51 | if self._forTrade: 52 | self._resultForTrade['stocks'] = None 53 | -------------------------------------------------------------------------------- /Stock/Select/Strategy/Cta/DySS_MaWalk.py: -------------------------------------------------------------------------------- 1 | import operator 2 | 3 | from ..DyStockSelectStrategyTemplate import * 4 | from ....Data.Utility.DyStockDataUtility import * 5 | 6 | 7 | class DySS_MaWalk(DyStockSelectStrategyTemplate): 8 | name = 'DySS_MaWalk' 9 | chName = '均线游走' 10 | 11 | autoFillDays = True 12 | optimizeAutoFillDays = True 13 | 14 | colNames = ['代码', '名称'] 15 | 16 | param = OrderedDict\ 17 | ([ 18 | ('基准日期', datetime.today().strftime("%Y-%m-%d")), 19 | ('游走均线', 20), 20 | ('游走均线之上日数', 3), 21 | ('均线多头排列', '20,30,60'), 22 | ]) 23 | 24 | 25 | def __init__(self, param, info): 26 | super().__init__(param, info) 27 | 28 | # unpack parameters 29 | self._baseDate = param['基准日期'] 30 | self._walkMa = param['游走均线'] 31 | self._walkMaUpDayNbr = param['游走均线之上日数'] 32 | self._longMas = [int(x) for x in param['均线多头排列'].split(',')]; self._longMas.sort() 33 | 34 | def onDaysLoad(self): 35 | return self._baseDate, -max(self._longMas[-1], self._walkMa) - self._walkMaUpDayNbr 36 | 37 | def onInit(self, dataEngine, errorDataEngine): 38 | self._daysEngine = dataEngine.daysEngine 39 | 40 | self._stockAllCodes = self._daysEngine.stockAllCodes 41 | 42 | def onStockDays(self, code, df): 43 | maDf = DyStockDataUtility.getMas(df, set([self._walkMa] + self._longMas)) 44 | df = df.ix[maDf.index] 45 | 46 | if df.shape[0] <= self._walkMaUpDayNbr: 47 | return 48 | 49 | # 最低价在均线之上 50 | lows = df.ix[-self._walkMaUpDayNbr-1:-1, 'low'] 51 | mas = maDf.ix[-self._walkMaUpDayNbr-1:-1, 'ma%s'%self._walkMa] 52 | if (lows < mas).sum() > 0: 53 | return 54 | 55 | # 当日下穿均线 56 | close = df.ix[-1, 'close'] 57 | low = df.ix[-1, 'low'] 58 | ma = maDf.ix[-1, 'ma%d'%self._walkMa] 59 | 60 | if not(low <= ma and close > ma): 61 | return 62 | 63 | # 当日均线多头排列 64 | preMaDiff = None 65 | for i in range(len(self._longMas) - 1): 66 | ma = maDf.ix[-1, 'ma%s'%self._longMas[i]] 67 | nextMa = maDf.ix[-1, 'ma%s'%self._longMas[i+1]] 68 | 69 | if ma < nextMa: 70 | return 71 | 72 | maDiff = ma - nextMa 73 | if preMaDiff is not None: 74 | if preMaDiff > maDiff: 75 | return 76 | 77 | preMaDiff = maDiff 78 | 79 | # 设置结果 80 | group = [code, self._stockAllCodes[code]] 81 | self._result.append(group) 82 | -------------------------------------------------------------------------------- /Stock/Select/Strategy/Cta/DySS_NeedleBottom.py: -------------------------------------------------------------------------------- 1 | from ..DyStockSelectStrategyTemplate import * 2 | from ....Data.Utility.DyStockDataUtility import * 3 | 4 | 5 | class DySS_NeedleBottom(DyStockSelectStrategyTemplate): 6 | name = 'DySS_NeedleBottom' 7 | chName = '单针探底' 8 | 9 | colNames = ['代码', '名称', '下影占比(%)'] 10 | 11 | param = OrderedDict\ 12 | ([ 13 | ('基准日期', datetime.today().strftime("%Y-%m-%d")), 14 | ('向前N日周期', 15), 15 | ('周期内跌幅至少(%)', 20), 16 | ('下影占比至少(%)', 61.8) 17 | ]) 18 | 19 | def __init__(self, param, info): 20 | super().__init__(param, info) 21 | 22 | # unpack parameters 23 | self._baseDate = param['基准日期'] 24 | self._forwardNTDays = param['向前N日周期'] # @self._baseDate is included 25 | self._dropDownPct = param['周期内跌幅至少(%)'] 26 | self._needleBottomRatio = param['下影占比至少(%)'] 27 | 28 | def onDaysLoad(self): 29 | return self._baseDate, -max(self._forwardNTDays, 5) + 1 30 | 31 | def onInit(self, dataEngine, errorDataEngine): 32 | self._daysEngine = dataEngine.daysEngine 33 | 34 | self._stockAllCodes = self._daysEngine.stockAllCodes 35 | 36 | self._startDay = self._daysEngine.tDaysOffset(self._baseDate, -self._forwardNTDays + 1) 37 | self._endDay = self._daysEngine.tDaysOffset(self._baseDate) 38 | 39 | def onStockDays(self, code, df): 40 | # 计算5日均线 41 | maDf = DyStockDataUtility.getMas(df, [5]) 42 | df = df.ix[self._startDay:self._endDay] 43 | 44 | # 剔除周期内停牌的股票 45 | if df.shape[0] != self._forwardNTDays: 46 | return 47 | 48 | close = df.ix[self._endDay, 'close'] 49 | low = df.ix[self._endDay, 'low'] 50 | open = df.ix[self._endDay, 'open'] 51 | high = df.ix[self._endDay, 'high'] 52 | 53 | highest = df['high'].max() 54 | lowest = df['low'].min() 55 | 56 | if lowest != low: return 57 | 58 | if low == high: return 59 | 60 | if close >= maDf.ix[self._endDay, 'ma5']: return 61 | 62 | #if close < open: return 63 | 64 | if (highest - lowest)*100/highest < self._dropDownPct: return 65 | 66 | # 当日下影占比 67 | if not self._forTrade: 68 | needleBottomRatio = (min(close, open) - low)*100 / (high - low) 69 | if needleBottomRatio < self._needleBottomRatio: return 70 | 71 | # 设置结果 72 | if not self._forTrade: 73 | pair = [code, self._stockAllCodes[code], needleBottomRatio] 74 | self._result.append(pair) 75 | 76 | # 设置实盘结果 77 | if self._forTrade: 78 | self._resultForTrade['stocks'][code] = None 79 | -------------------------------------------------------------------------------- /Stock/Select/Strategy/Cta/DySS_OverSold.py: -------------------------------------------------------------------------------- 1 | import operator 2 | 3 | from ..DyStockSelectStrategyTemplate import * 4 | from ....Data.Utility.DyStockDataUtility import * 5 | 6 | 7 | class DySS_OverSold(DyStockSelectStrategyTemplate): 8 | """ 升浪开始,回踩均线 9 | 升浪以收盘在5日均线上 10 | 升浪统计10日,回踩统计5日 11 | """ 12 | name = 'DySS_OverSold' 13 | chName = '超卖' 14 | 15 | colNames = ['代码', '名称', '收盘五日均线比', '最低五日均线比'] 16 | 17 | param = OrderedDict\ 18 | ([ 19 | ('基准日期', datetime.today().strftime("%Y-%m-%d")), 20 | ('选几只股票', 20) 21 | ]) 22 | 23 | def __init__(self, param, info): 24 | super().__init__(param, info) 25 | 26 | # unpack parameters 27 | self._baseDate = param['基准日期'] 28 | self._selectStockNbr = param['选几只股票'] 29 | 30 | def onDaysLoad(self): 31 | return self._baseDate, -4 32 | 33 | def onInit(self, dataEngine, errorDataEngine): 34 | self._daysEngine = dataEngine.daysEngine 35 | 36 | self._stockAllCodes = self._daysEngine.stockAllCodes 37 | 38 | self._endDay = self._daysEngine.tDaysOffset(self._baseDate, 0) 39 | 40 | def onStockDays(self, code, df): 41 | # 计算5, @self._backMa日均线 42 | maDf = DyStockDataUtility.getMas(df, [5]) 43 | df = df.ix[-1:] 44 | 45 | ma5 = maDf.ix[0, 'ma5'] 46 | 47 | close = df.ix[0, 'close'] 48 | low = df.ix[0, 'low'] 49 | 50 | # 设置结果 51 | pair = [code, self._stockAllCodes[code], close/ma5, low/ma5] 52 | self._result.append(pair) 53 | self._result.sort(key = operator.itemgetter(2)) 54 | self._result = self._result[:self._selectStockNbr] 55 | 56 | # 设置实盘结果 57 | if self._forTrade: 58 | self._resultForTrade['date'] = self._endDay 59 | -------------------------------------------------------------------------------- /Stock/Select/Strategy/Cta/DySS_Rebound.py: -------------------------------------------------------------------------------- 1 | import operator 2 | 3 | from ..DyStockSelectStrategyTemplate import * 4 | from ....Data.Utility.DyStockDataUtility import * 5 | 6 | 7 | class DySS_Rebound(DyStockSelectStrategyTemplate): 8 | name = 'DySS_Rebound' 9 | chName = '反弹' 10 | 11 | colNames = ['代码', '名称', '效率系数', '当日跌幅占比'] 12 | 13 | param = OrderedDict\ 14 | ([ 15 | ('基准日期', datetime.today().strftime("%Y-%m-%d")) 16 | ]) 17 | 18 | def __init__(self, param, info): 19 | super().__init__(param, info) 20 | 21 | # unpack parameters 22 | self._baseDate = param['基准日期'] 23 | 24 | def onDaysLoad(self): 25 | return self._baseDate, -5 26 | 27 | def onInit(self, dataEngine, errorDataEngine): 28 | self._daysEngine = dataEngine.daysEngine 29 | 30 | self._stockAllCodes = self._daysEngine.stockAllCodes 31 | 32 | def onStockDays(self, code, df): 33 | # 剔除周期内停牌的股票 34 | if df.shape[0] != 6: 35 | return 36 | 37 | # 剔除当日收盘价等于最低价,意味着收盘肯定不是跌停 38 | if df.ix[-1, 'close'] == df.ix[-1, 'low']: return 39 | 40 | # 下降趋势 41 | direction = df.ix[-1, 'close'] - df.ix[0, 'close'] 42 | if direction > 0: return 43 | 44 | # 绝对波动 45 | closes = df['close'] 46 | change = closes - closes.shift(1) 47 | volatility = abs(change).sum() 48 | 49 | # 效率系数 50 | efficiencyRatio = direction/volatility 51 | 52 | if efficiencyRatio > -0.8: return 53 | 54 | curDropRatio = abs(change[-1])/abs(direction) 55 | if curDropRatio > 0.5: return 56 | 57 | # 设置结果 58 | pair = [code, self._stockAllCodes[code], efficiencyRatio, curDropRatio] 59 | self._result.append(pair) 60 | self._result.sort(key=operator.itemgetter(2), reverse=False) 61 | -------------------------------------------------------------------------------- /Stock/Select/Strategy/Cta/DySS_Stagflation.py: -------------------------------------------------------------------------------- 1 | import operator 2 | 3 | from ..DyStockSelectStrategyTemplate import * 4 | from ....Data.Utility.DyStockDataUtility import * 5 | 6 | 7 | class DySS_Stagflation(DyStockSelectStrategyTemplate): 8 | name = 'DySS_Stagflation' 9 | chName = '滞涨' 10 | 11 | colNames = ['代码', '名称', '现价最低比(%)', '最大跌幅(%)', '现价60日均线比(%)'] 12 | 13 | param = OrderedDict\ 14 | ([ 15 | ('基准日期', datetime.today().strftime("%Y-%m-%d")), 16 | ('向前N日周期', 30), 17 | ('选几只股票', 50) 18 | ]) 19 | 20 | def __init__(self, param, info): 21 | super().__init__(param, info) 22 | 23 | # unpack parameters 24 | self._baseDate = param['基准日期'] 25 | self._forwardNTDays = param['向前N日周期'] 26 | self._selectStockNbr = param['选几只股票'] 27 | 28 | def onDaysLoad(self): 29 | return self._baseDate, -max(self._forwardNTDays, 60) + 1 30 | 31 | def onInit(self, dataEngine, errorDataEngine): 32 | self._daysEngine = dataEngine.daysEngine 33 | 34 | self._stockAllCodes = self._daysEngine.stockAllCodes 35 | 36 | self._startDay = self._daysEngine.tDaysOffset(self._baseDate, -self._forwardNTDays + 1) 37 | self._endDay = self._daysEngine.tDaysOffset(self._baseDate, 0) 38 | 39 | def onStockDays(self, code, df): 40 | # 计算60日均线 41 | maDf = DyStockDataUtility.getMas(df, [60]) 42 | df = df.ix[self._startDay:self._endDay] 43 | 44 | ma60 = maDf.ix[self._endDay, 'ma60'] 45 | close = df.ix[self._endDay, 'close'] 46 | 47 | low = df['low'].min() 48 | 49 | # 最低价在最高价的右侧 50 | lowDay = df['low'].idxmin() 51 | high = df.ix[self._startDay:lowDay, 'high'].max() 52 | 53 | closeLowRatio = (close - low)*100/low 54 | maxDropRatio = (high - low)*100/high 55 | closeM60Ration = (close - ma60)*100/ma60 56 | 57 | # 设置结果 58 | pair = [code, self._stockAllCodes[code], closeLowRatio, maxDropRatio, closeM60Ration] 59 | self._result.append(pair) 60 | self._result.sort(key=operator.itemgetter(2)) 61 | self._result = self._result[:self._selectStockNbr] 62 | -------------------------------------------------------------------------------- /Stock/Select/Strategy/Cta/__init__.py: -------------------------------------------------------------------------------- 1 | class __init__(object): 2 | """description of class""" 3 | 4 | 5 | -------------------------------------------------------------------------------- /Stock/Select/Strategy/Fundamental/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Select/Strategy/Fundamental/__init__.py -------------------------------------------------------------------------------- /Stock/Select/Strategy/Other/DySS_ETF.py: -------------------------------------------------------------------------------- 1 | from ..DyStockSelectStrategyTemplate import * 2 | from ....Common.DyStockCommon import * 3 | 4 | 5 | class DySS_ETF(DyStockSelectStrategyTemplate): 6 | name = 'DySS_ETF' 7 | chName = 'ETF' 8 | 9 | colNames = ['代码', '名称'] 10 | 11 | param = OrderedDict\ 12 | ([ 13 | ('基准日期', datetime.today().strftime("%Y-%m-%d")), 14 | ]) 15 | 16 | def __init__(self, param, info): 17 | super().__init__(param, info) 18 | 19 | # unpack parameters 20 | self._baseDate = param['基准日期'] 21 | self._isRun = False 22 | 23 | def onCodes(self): 24 | return [DyStockCommon.etf50, DyStockCommon.etf300, DyStockCommon.etf500] 25 | 26 | def onDaysLoad(self): 27 | return self._baseDate, 0 28 | 29 | def onIndexDays(self, code, df): 30 | if self._isRun: 31 | return 32 | 33 | for code in [DyStockCommon.etf50, DyStockCommon.etf300, DyStockCommon.etf500]: 34 | pair = [code, DyStockCommon.funds[code]] 35 | self._result.append(pair) 36 | 37 | self._isRun = True 38 | -------------------------------------------------------------------------------- /Stock/Select/Strategy/Other/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Select/Strategy/Other/__init__.py -------------------------------------------------------------------------------- /Stock/Select/Strategy/Stats/DySS_LimitUp.py: -------------------------------------------------------------------------------- 1 | from ..DyStockSelectStrategyTemplate import * 2 | from ....Data.Utility.DyStockDataUtility import * 3 | 4 | 5 | class DySS_LimitUp(DyStockSelectStrategyTemplate): 6 | name = 'DySS_LimitUp' 7 | chName = '连板' 8 | 9 | colNames = ['代码', '名称', '连板数'] 10 | 11 | param = OrderedDict\ 12 | ([ 13 | ('基准日期', datetime.today().strftime("%Y-%m-%d")), 14 | ('最大连板数', 7) 15 | ]) 16 | 17 | def __init__(self, param, info): 18 | super().__init__(param, info) 19 | 20 | # unpack parameters 21 | self._baseDate = param['基准日期'] 22 | self._maxLimitUpNbr = param['最大连板数'] 23 | 24 | def onDaysLoad(self): 25 | return self._baseDate, -self._maxLimitUpNbr 26 | 27 | def onInit(self, dataEngine, errorDataEngine): 28 | self._daysEngine = dataEngine.daysEngine 29 | 30 | self._stockAllCodes = self._daysEngine.stockAllCodes 31 | 32 | #self._startDay = self._daysEngine.tDaysOffset(self._baseDate, -1) 33 | #self._endDay = self._daysEngine.tDaysOffset(self._baseDate, 0) 34 | 35 | def onStockDays(self, code, df): 36 | closePctChange = df['close'].pct_change().dropna() 37 | 38 | # 剔除停牌或者新股 39 | if closePctChange.shape[0] < self._maxLimitUpNbr: 40 | return 41 | 42 | limitUpBool = closePctChange >= DyStockCommon.limitUpPct/100 43 | limitUpNbr = int(limitUpBool.sum()) 44 | if limitUpNbr == 0: 45 | return 46 | 47 | # 剔除一字板涨停 48 | limitUpDf = df[1:][limitUpBool] 49 | if limitUpDf[limitUpDf['high'] == limitUpDf['low']].shape[0] == limitUpDf.shape[0]: 50 | return 51 | 52 | # 设置结果 53 | pair = [code, self._stockAllCodes[code], limitUpNbr] 54 | self._result.append(pair) 55 | -------------------------------------------------------------------------------- /Stock/Select/Strategy/Stats/DySS_UpDownList.py: -------------------------------------------------------------------------------- 1 | import operator 2 | 3 | from ..DyStockSelectStrategyTemplate import * 4 | from ....Data.Utility.DyStockDataUtility import * 5 | 6 | 7 | class DySS_UpDownList(DyStockSelectStrategyTemplate): 8 | name = 'DySS_UpDownList' 9 | chName = '涨跌幅榜' 10 | 11 | colNames = ['代码', '名称', None, '波动效率'] 12 | 13 | param = OrderedDict\ 14 | ([ 15 | ('基准日期', datetime.today().strftime("%Y-%m-%d")), 16 | ('向前N日周期', 30), 17 | ('涨跌', 1), 18 | ('选几只股票', 200) 19 | ]) 20 | 21 | paramToolTip = {'涨跌': '1: 涨, 0: 跌'} 22 | 23 | 24 | def __init__(self, param, info): 25 | super().__init__(param, info) 26 | 27 | # unpack parameters 28 | self._baseDate = param['基准日期'] 29 | self._forwardNTDays = param['向前N日周期'] 30 | self._upDown = False if param['涨跌'] == 0 else True 31 | self._selectStockNbr = param['选几只股票'] 32 | 33 | self.colNames[2] = '前{0}日{1}幅(%)'.format(self._forwardNTDays, '涨' if self._upDown else '跌') 34 | 35 | def onDaysLoad(self): 36 | return self._baseDate, -self._forwardNTDays 37 | 38 | def onInit(self, dataEngine, errorDataEngine): 39 | self._daysEngine = dataEngine.daysEngine 40 | 41 | self._stockAllCodes = self._daysEngine.stockAllCodes 42 | 43 | def onStockDays(self, code, df): 44 | # 剔除最前3日一字板, 新股首日涨幅剔除 45 | dayNbr = min(4, self._forwardNTDays) 46 | dayNbr_ = 0 47 | for i in range(1, dayNbr): 48 | if df.ix[i, 'high'] != df.ix[i, 'low']: break 49 | dayNbr_ += 1 50 | 51 | if dayNbr - 1 == dayNbr_: return 52 | 53 | # 涨幅 54 | pct = (df.ix[-1, 'close'] - df.ix[0, 'close'])*100/df.ix[0, 'close'] 55 | 56 | efficiencyRatio, _ = DyStockDataUtility.getVolatilityEfficiencyRatio(df['close']) 57 | 58 | # 设置结果 59 | pair = [code, self._stockAllCodes[code], pct, efficiencyRatio] 60 | self._result.append(pair) 61 | self._result.sort(key=operator.itemgetter(2), reverse=self._upDown) 62 | self._result = self._result[:self._selectStockNbr] 63 | -------------------------------------------------------------------------------- /Stock/Select/Strategy/Stats/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Select/Strategy/Stats/__init__.py -------------------------------------------------------------------------------- /Stock/Select/Strategy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Select/Strategy/__init__.py -------------------------------------------------------------------------------- /Stock/Select/Ui/Basic/Dlg/DyStockSelectAddColumnsDlg.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QDialog, QGridLayout, QLabel, QLineEdit, QPushButton, QApplication, QRadioButton, QButtonGroup 2 | 3 | 4 | class DyStockSelectAddColumnsDlg(QDialog): 5 | 6 | def __init__(self, data, title, parent=None): 7 | super().__init__(parent) 8 | 9 | self._data = data 10 | 11 | self._initUi(title) 12 | 13 | def _initUi(self, title): 14 | self.setWindowTitle('添加{0}列'.format(title)) 15 | 16 | # 控件 17 | increaseColumnsLable = QLabel('基准日期几日{0}'.format(title)) 18 | self._increaseColumnsLineEdit = QLineEdit(','.join(self._data['days']) if self._data else '1,2,3,4,5,10') 19 | 20 | # 前 & 后 21 | forwardRadioButton = QRadioButton('向前') 22 | backwardRadioButton = QRadioButton('向后'); backwardRadioButton.setChecked(True) 23 | 24 | # 添加到QButtonGroup 25 | self._wardButtonGroup = QButtonGroup() 26 | self._wardButtonGroup.addButton(forwardRadioButton, 1); 27 | self._wardButtonGroup.addButton(backwardRadioButton, 2) 28 | 29 | cancelPushButton = QPushButton('Cancel') 30 | okPushButton = QPushButton('OK') 31 | cancelPushButton.clicked.connect(self._cancel) 32 | okPushButton.clicked.connect(self._ok) 33 | 34 | # 布局 35 | grid = QGridLayout() 36 | grid.setSpacing(10) 37 | 38 | grid.addWidget(increaseColumnsLable, 0, 0, 1, 2) 39 | grid.addWidget(self._increaseColumnsLineEdit, 1, 0, 1, 2) 40 | 41 | grid.addWidget(forwardRadioButton, 2, 0) 42 | grid.addWidget(backwardRadioButton, 2, 1) 43 | 44 | grid.addWidget(okPushButton, 3, 1) 45 | grid.addWidget(cancelPushButton, 3, 0) 46 | 47 | 48 | self.setLayout(grid) 49 | self.setMinimumWidth(QApplication.desktop().size().width()//5) 50 | 51 | def _ok(self): 52 | checkedButton = self._wardButtonGroup.checkedButton() 53 | text = checkedButton.text() 54 | self._data['backward'] = True if text == '向后' else False 55 | 56 | self._data['days'] = [int(x) for x in self._increaseColumnsLineEdit.text().split(',')] 57 | 58 | self.accept() 59 | 60 | def _cancel(self): 61 | self.reject() 62 | -------------------------------------------------------------------------------- /Stock/Select/Ui/Basic/Dlg/DyStockSelectFilterDlg.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QDialog, QGridLayout, QLabel, QTextEdit, QPushButton, QApplication, QCheckBox 2 | 3 | from DyCommon.Ui.DyTableWidget import * 4 | 5 | 6 | class DyStockSelectFilterDlg(QDialog): 7 | 8 | def __init__(self, data, colNames, parent=None): 9 | super().__init__(parent) 10 | 11 | self._data = data 12 | 13 | self._initUi(colNames) 14 | 15 | def _initUi(self, colNames): 16 | self.setWindowTitle('过滤') 17 | 18 | # 控件 19 | table = DyTableWidget(parant=None, readOnly=True, index=False, floatCut=True, autoScroll=False) 20 | table.setColNames(['列名', '表达式']) 21 | rows = [[name, 'x[{0}]'.format(i)] for i, name in enumerate(colNames)] 22 | table.fastAppendRows(rows) 23 | 24 | descriptionLabel = QLabel('过滤表达式(Python语法)') 25 | self._filterTextEdit = QTextEdit() 26 | self._newWindowCheckBox = QCheckBox('新窗口') 27 | self._newWindowCheckBox.setChecked(True) 28 | self._highlightCheckBox = QCheckBox('原窗口高亮') 29 | self._highlightCheckBox.setChecked(True) 30 | 31 | cancelPushButton = QPushButton('Cancel') 32 | okPushButton = QPushButton('OK') 33 | cancelPushButton.clicked.connect(self._cancel) 34 | okPushButton.clicked.connect(self._ok) 35 | 36 | # 布局 37 | grid = QGridLayout() 38 | grid.setSpacing(10) 39 | 40 | grid.addWidget(table, 0, 0, 22, 1) 41 | grid.addWidget(self._newWindowCheckBox, 0, 1) 42 | grid.addWidget(self._highlightCheckBox, 0, 2) 43 | 44 | grid.addWidget(descriptionLabel, 1, 1) 45 | 46 | grid.addWidget(self._filterTextEdit, 2, 1, 20, 20) 47 | 48 | grid.addWidget(okPushButton, 0, 21) 49 | grid.addWidget(cancelPushButton, 1, 21) 50 | 51 | 52 | self.setLayout(grid) 53 | self.resize(QApplication.desktop().size().width()//2, QApplication.desktop().size().height()//4*3) 54 | 55 | def _ok(self): 56 | filter = self._filterTextEdit.toPlainText().replace('\n', ' ') 57 | 58 | self._data['filter'] = filter 59 | self._data['newWindow'] = self._newWindowCheckBox.isChecked() 60 | self._data['highlight'] = self._highlightCheckBox.isChecked() 61 | 62 | self.accept() 63 | 64 | def _cancel(self): 65 | self.reject() 66 | -------------------------------------------------------------------------------- /Stock/Select/Ui/Basic/Dlg/DyStockSelectIndustryCompareDlg.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QApplication, QDialog, QGridLayout, QLabel, QLineEdit, QPushButton, QCheckBox 2 | 3 | 4 | class DyStockSelectIndustryCompareDlg(QDialog): 5 | 6 | def __init__(self, name, baseDate, data, parent=None): 7 | super().__init__(parent) 8 | 9 | self._data = data 10 | 11 | self._initUi(name, baseDate) 12 | 13 | def _initUi(self, name, baseDate): 14 | self.setWindowTitle('行业对比[{0}]-基准日期[{1}]'.format(name, baseDate)) 15 | 16 | # 控件 17 | forwardNTDaysLabel = QLabel('向前N日涨幅(%)') 18 | self._forwardNTDaysLineEdit = QLineEdit('30') 19 | 20 | self._industry2CheckBox = QCheckBox('行业二级分级') 21 | #self._industry2CheckBox.setChecked(True) 22 | 23 | self._industry3CheckBox = QCheckBox('行业三级分级') 24 | self._industry3CheckBox.setChecked(True) 25 | 26 | cancelPushButton = QPushButton('Cancel') 27 | okPushButton = QPushButton('OK') 28 | cancelPushButton.clicked.connect(self._cancel) 29 | okPushButton.clicked.connect(self._ok) 30 | 31 | # 布局 32 | grid = QGridLayout() 33 | grid.setSpacing(10) 34 | 35 | grid.addWidget(forwardNTDaysLabel, 0, 0) 36 | grid.addWidget(self._forwardNTDaysLineEdit, 0, 1) 37 | 38 | grid.addWidget(self._industry2CheckBox, 1, 0) 39 | grid.addWidget(self._industry3CheckBox, 1, 1) 40 | 41 | grid.addWidget(okPushButton, 2, 1) 42 | grid.addWidget(cancelPushButton, 2, 0) 43 | 44 | self.setLayout(grid) 45 | 46 | self.setMinimumWidth(QApplication.desktop().size().width()//5) 47 | 48 | def _ok(self): 49 | self._data['forwardNTDays'] = int(self._forwardNTDaysLineEdit.text()) 50 | 51 | self._data['industry2'] = self._industry2CheckBox.isChecked() 52 | self._data['industry3'] = self._industry3CheckBox.isChecked() 53 | 54 | self.accept() 55 | 56 | def _cancel(self): 57 | self.reject() 58 | -------------------------------------------------------------------------------- /Stock/Select/Ui/Basic/Dlg/DyStockSelectRefactoryParamsDlg.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QDialog, QGridLayout, QLabel, QTextEdit, QPushButton, QApplication, QCheckBox 2 | 3 | from DyCommon.Ui.DyTableWidget import * 4 | 5 | 6 | class DyStockSelectRefactoryParamsDlg(QDialog): 7 | 8 | def __init__(self, data, header, params, parent=None): 9 | super().__init__(parent) 10 | 11 | self._data = data 12 | 13 | self._initUi(header, params) 14 | 15 | def _initUi(self, header, params): 16 | self.setWindowTitle('重构参数') 17 | 18 | # 控件 19 | self._table = DyTableWidget(parent=None, readOnly=False, index=False, floatCut=True, autoScroll=False) 20 | self._table.setColNames(header) 21 | self._table.fastAppendRows(params) 22 | 23 | self._newWindowCheckBox = QCheckBox('新窗口') 24 | self._newWindowCheckBox.setChecked(True) 25 | 26 | cancelPushButton = QPushButton('Cancel') 27 | okPushButton = QPushButton('OK') 28 | cancelPushButton.clicked.connect(self._cancel) 29 | okPushButton.clicked.connect(self._ok) 30 | 31 | # 布局 32 | grid = QGridLayout() 33 | grid.setSpacing(10) 34 | 35 | grid.addWidget(self._newWindowCheckBox, 0, 0) 36 | grid.addWidget(self._table, 1, 0, 1, 2) 37 | 38 | grid.addWidget(okPushButton, 2, 1) 39 | grid.addWidget(cancelPushButton, 2, 0) 40 | 41 | 42 | self.setLayout(grid) 43 | self.resize(QApplication.desktop().size().width()//3, QApplication.desktop().size().height()//4*3) 44 | 45 | def _ok(self): 46 | params = self._table.getAll() 47 | params = {x[0]: x[1] for x in params} 48 | 49 | self._data['params'] = params 50 | self._data['newWindow'] = self._newWindowCheckBox.isChecked() 51 | 52 | self.accept() 53 | 54 | def _cancel(self): 55 | self.reject() 56 | -------------------------------------------------------------------------------- /Stock/Select/Ui/Basic/Dlg/DyStockSelectSaveAsDlg.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QDialog, QGridLayout, QPushButton, QRadioButton, QButtonGroup, QApplication 2 | 3 | 4 | class DyStockSelectSaveAsDlg(QDialog): 5 | 6 | def __init__(self, data, strategyName, parent=None): 7 | super().__init__(parent) 8 | 9 | self._data = data 10 | 11 | self._initUi(strategyName) 12 | 13 | def _initUi(self, strategyName): 14 | self.setWindowTitle('[{0}]另存为'.format(strategyName)) 15 | 16 | allRadioButton = QRadioButton('所有'); allRadioButton.setChecked(True) 17 | highlightRadioButton = QRadioButton('高亮') 18 | 19 | # 添加到QButtonGroup 20 | self._buttonGroup = QButtonGroup() 21 | self._buttonGroup.addButton(allRadioButton, 1); 22 | self._buttonGroup.addButton(highlightRadioButton, 2) 23 | 24 | cancelPushButton = QPushButton('Cancel') 25 | okPushButton = QPushButton('OK') 26 | cancelPushButton.clicked.connect(self._cancel) 27 | okPushButton.clicked.connect(self._ok) 28 | 29 | # 布局 30 | grid = QGridLayout() 31 | grid.setSpacing(10) 32 | 33 | grid.addWidget(allRadioButton, 1, 0) 34 | grid.addWidget(highlightRadioButton, 1, 1) 35 | 36 | grid.addWidget(okPushButton, 2, 1) 37 | grid.addWidget(cancelPushButton, 2, 0) 38 | 39 | self.setLayout(grid) 40 | self.setMinimumWidth(QApplication.desktop().size().width()//5) 41 | 42 | def _ok(self): 43 | checkedButton = self._buttonGroup.checkedButton() 44 | text = checkedButton.text() 45 | self._data['all'] = True if text == '所有' else False 46 | 47 | self.accept() 48 | 49 | def _cancel(self): 50 | self.reject() 51 | -------------------------------------------------------------------------------- /Stock/Select/Ui/Basic/Dlg/DyStockSelectStockInfoDlg.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QDialog, QGridLayout, QPushButton, QApplication, QMessageBox 2 | 3 | from DyCommon.Ui.DyTreeWidget import * 4 | 5 | 6 | class DyStockSelectStockInfoDlg(QDialog): 7 | """ 个股资料选择对话框 8 | """ 9 | fields = \ 10 | [ 11 | ['公司资料', 12 | ['所属行业'], 13 | ['主营业务'], 14 | ['涉及概念'] 15 | ], 16 | ['股本', 17 | ['实际流通股(亿)'], 18 | ['实际流通市值(亿元)'], 19 | ['机构占比流通(%)'], 20 | ] 21 | ] 22 | 23 | def __init__(self, data, parent=None): 24 | super().__init__(parent) 25 | 26 | self._data = data 27 | 28 | self._initUi() 29 | 30 | def _initUi(self): 31 | self.setWindowTitle('个股资料(F10)') 32 | 33 | # 控件 34 | cancelPushButton = QPushButton('Cancel') 35 | okPushButton = QPushButton('OK') 36 | cancelPushButton.clicked.connect(self._cancel) 37 | okPushButton.clicked.connect(self._ok) 38 | 39 | self._stockInfoWidget = DyTreeWidget(self.fields) 40 | 41 | # 布局 42 | grid = QGridLayout() 43 | grid.setSpacing(10) 44 | 45 | grid.addWidget(self._stockInfoWidget, 0, 0, 20, 10) 46 | 47 | grid.addWidget(okPushButton, 0, 10) 48 | grid.addWidget(cancelPushButton, 1, 10) 49 | 50 | self.setLayout(grid) 51 | self.resize(QApplication.desktop().size().width()//3, QApplication.desktop().size().height()//2) 52 | 53 | def _ok(self): 54 | indicators = self._stockInfoWidget.getCheckedTexts() 55 | 56 | if not indicators: 57 | QMessageBox.warning(self, '错误', '没有选择指标!') 58 | return 59 | 60 | self._data['indicators'] = indicators 61 | 62 | self.accept() 63 | 64 | def _cancel(self): 65 | self.reject() 66 | -------------------------------------------------------------------------------- /Stock/Select/Ui/Basic/Dlg/DyStockSelectVolatilityDistDlg.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QApplication, QDialog, QGridLayout, QLabel, QLineEdit, QPushButton, QRadioButton, QButtonGroup 2 | 3 | 4 | class DyStockSelectVolatilityDistDlg(QDialog): 5 | """ 6 | !!!暂时这个类没有用 7 | """ 8 | 9 | def __init__(self, name, baseDate, data, parent=None): 10 | """ 11 | @name: 股票名称 12 | """ 13 | super().__init__(parent) 14 | 15 | self._data = data 16 | 17 | self._initUi(name, baseDate) 18 | 19 | def _initUi(self, name, baseDate): 20 | self.setWindowTitle('波动分布[{0}]'.format(name)) 21 | 22 | # 控件 23 | forwardNTDaysLabel = QLabel('基准日期[{0}]向前N日(不包含基准日期)'.format(baseDate)) 24 | self._forwardNTDaysLineEdit = QLineEdit('30') 25 | 26 | # 自身波动和绝对波动 27 | # 个股绝对波动 = 个股自身波动 + 大盘波动 28 | selfVolatilityRadioButton = QRadioButton('自身波动'); selfVolatilityRadioButton.setChecked(True) 29 | selfVolatilityRadioButton.setToolTip('个股绝对波动 = 个股自身波动 + 大盘波动') 30 | 31 | absoluteVolatilityRadioButton = QRadioButton('绝对波动') 32 | absoluteVolatilityRadioButton.setToolTip('个股绝对波动 = 个股自身波动 + 大盘波动') 33 | 34 | # 添加到QButtonGroup 35 | self._volatilityButtonGroup = QButtonGroup() 36 | self._volatilityButtonGroup.addButton(selfVolatilityRadioButton, 1); 37 | self._volatilityButtonGroup.addButton(absoluteVolatilityRadioButton, 2) 38 | 39 | cancelPushButton = QPushButton('Cancel') 40 | okPushButton = QPushButton('OK') 41 | cancelPushButton.clicked.connect(self._cancel) 42 | okPushButton.clicked.connect(self._ok) 43 | 44 | # 布局 45 | grid = QGridLayout() 46 | grid.setSpacing(10) 47 | 48 | grid.addWidget(forwardNTDaysLabel, 0, 0) 49 | grid.addWidget(self._forwardNTDaysLineEdit, 0, 1) 50 | 51 | grid.addWidget(selfVolatilityRadioButton, 1, 0) 52 | grid.addWidget(absoluteVolatilityRadioButton, 1, 1) 53 | 54 | grid.addWidget(okPushButton, 2, 1) 55 | grid.addWidget(cancelPushButton, 2, 0) 56 | 57 | self.setLayout(grid) 58 | 59 | self.setMinimumWidth(QApplication.desktop().size().width()//5) 60 | 61 | def _getVolatility(self): 62 | checkedButton = self._volatilityButtonGroup.checkedButton() 63 | text = checkedButton.text() 64 | 65 | return True if text == '自身波动' else False 66 | 67 | def _ok(self): 68 | self._data['forwardNTDays'] = int(self._forwardNTDaysLineEdit.text()) 69 | self._data['selfVolatility'] = self._getVolatility() 70 | 71 | self.accept() 72 | 73 | def _cancel(self): 74 | self.reject() 75 | -------------------------------------------------------------------------------- /Stock/Select/Ui/Basic/Dlg/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Select/Ui/Basic/Dlg/__init__.py -------------------------------------------------------------------------------- /Stock/Select/Ui/Basic/Other/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Select/Ui/Basic/Other/__init__.py -------------------------------------------------------------------------------- /Stock/Select/Ui/Basic/Param/DyStockSelectParamWidget.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtCore 2 | from PyQt5.QtWidgets import QTabWidget 3 | 4 | from .DyStockSelectStrategyParamWidget import * 5 | 6 | 7 | class DyStockSelectParamWidget(QTabWidget): 8 | 9 | def __init__(self): 10 | super().__init__() 11 | 12 | self._strategyParamWidgets = {} 13 | 14 | self.setTabsClosable(True) 15 | self.tabCloseRequested.connect(self._closeTab) 16 | 17 | def set(self, strategyName, paramters, tooltips=None): 18 | if strategyName not in self._strategyParamWidgets: 19 | widget = DyStockSelectStrategyParamWidget() 20 | self.addTab(widget, strategyName) 21 | 22 | # save 23 | self._strategyParamWidgets[strategyName] = widget 24 | 25 | self._strategyParamWidgets[strategyName].set(paramters) 26 | self._strategyParamWidgets[strategyName].setToolTip(tooltips) 27 | 28 | self.setCurrentWidget(self._strategyParamWidgets[strategyName]) 29 | 30 | def get(self, strategyName): 31 | return self._strategyParamWidgets[strategyName].get() 32 | 33 | def _closeTab(self, index): 34 | tabName = self.tabText(index) 35 | 36 | param = self._strategyParamWidgets[tabName].get() 37 | self._strategyWidget.uncheckStrategy(tabName, param) 38 | 39 | del self._strategyParamWidgets[tabName] 40 | 41 | self.removeTab(index) 42 | 43 | def setStrategyWidget(self, strategyWidget): 44 | self._strategyWidget = strategyWidget 45 | 46 | -------------------------------------------------------------------------------- /Stock/Select/Ui/Basic/Param/DyStockSelectStrategyParamWidget.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | 3 | from DyCommon.Ui.DyTableWidget import * 4 | from EventEngine.DyEvent import * 5 | 6 | 7 | class DyStockSelectStrategyParamWidget(DyTableWidget): 8 | 9 | def __init__(self): 10 | super().__init__(None, False, False, False, False) 11 | 12 | def set(self, paramters): 13 | """ @paramters: ordered dict """ 14 | if paramters is None: 15 | return 16 | 17 | header = list(paramters) 18 | self.setColNames(header) 19 | 20 | self[0] = [x if x is None or isinstance(x, str) or isinstance(x, int) or isinstance(x, float) else str(x) for x in paramters.values()] 21 | 22 | for i, name in enumerate(header): 23 | if '权重' in name: 24 | self.setItemBackground(0, i, Qt.yellow) 25 | self.setItemForeground(0, i, Qt.black) 26 | 27 | def get(self): 28 | colNbr = self.columnCount() 29 | param = OrderedDict() 30 | 31 | for i in range(colNbr): 32 | key = self.horizontalHeaderItem(i).text() 33 | value = self[0, i] 34 | 35 | param[key] = value 36 | 37 | return param 38 | 39 | def setToolTip(self, tooltips=None): 40 | if tooltips is None: 41 | return 42 | 43 | for name, text in tooltips.items(): 44 | pos = self._getColPos(name) 45 | item = self.horizontalHeaderItem(pos) 46 | item.setToolTip(text) 47 | -------------------------------------------------------------------------------- /Stock/Select/Ui/Basic/Param/__init__.py: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /Stock/Select/Ui/Basic/Regression/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Select/Ui/Basic/Regression/__init__.py -------------------------------------------------------------------------------- /Stock/Select/Ui/Basic/Select/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Select/Ui/Basic/Select/__init__.py -------------------------------------------------------------------------------- /Stock/Select/Ui/Basic/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from Stock import DynamicLoadStrategyFields 4 | 5 | 6 | # dynamically load strategies from Stock/Select/Strategy 7 | __pathList = os.path.dirname(__file__).split(os.path.sep) 8 | __stratgyPath = os.path.sep.join(__pathList[:-2] + ['Strategy']) 9 | 10 | DyStockSelectStrategyClsMap = {} 11 | DyStockSelectStrategyWidgetAutoFields = DynamicLoadStrategyFields(__stratgyPath, 'Stock.Select.Strategy', DyStockSelectStrategyClsMap) 12 | -------------------------------------------------------------------------------- /Stock/Select/Ui/Other/DyStockSelectDayKChartPeriodDlg.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QDialog, QGridLayout, QLabel, QLineEdit, QPushButton 2 | 3 | 4 | class DyStockSelectDayKChartPeriodDlg(QDialog): 5 | 6 | def __init__(self, data, parent=None): 7 | super(DyStockSelectDayKChartPeriodDlg, self).__init__(parent) 8 | 9 | self._data = data 10 | 11 | self._initUi() 12 | 13 | def _initUi(self): 14 | self.setWindowTitle('日K线前后交易日周期') 15 | 16 | # 控件 17 | dayKChartPeriodLable = QLabel('股票(指数)日K线前后交易日周期') 18 | self._dayKChartPeriodLineEdit = QLineEdit(str(self._data['periodNbr']) if self._data else '60' ) 19 | 20 | cancelPushButton = QPushButton('Cancel') 21 | okPushButton = QPushButton('OK') 22 | cancelPushButton.clicked.connect(self._cancel) 23 | okPushButton.clicked.connect(self._ok) 24 | 25 | # 布局 26 | grid = QGridLayout() 27 | grid.setSpacing(10) 28 | 29 | grid.addWidget(dayKChartPeriodLable, 0, 0, 1, 2) 30 | grid.addWidget(self._dayKChartPeriodLineEdit, 1, 0, 1, 2) 31 | 32 | grid.addWidget(okPushButton, 2, 1) 33 | grid.addWidget(cancelPushButton, 2, 0) 34 | 35 | 36 | self.setLayout(grid) 37 | 38 | def _ok(self): 39 | self._data['periodNbr'] = int(self._dayKChartPeriodLineEdit.text()) 40 | 41 | self.accept() 42 | 43 | def _cancel(self): 44 | self.reject() 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Stock/Select/Ui/Other/DyStockSelectIndexMaStatsDlg.py: -------------------------------------------------------------------------------- 1 | from datetime import * 2 | 3 | from PyQt5.QtWidgets import QDialog, QGridLayout, QLabel, QLineEdit, QPushButton, QCheckBox 4 | 5 | from ....Common.DyStockCommon import * 6 | 7 | 8 | class DyStockSelectIndexMaStatsDlg(QDialog): 9 | 10 | def __init__(self, data, parent=None): 11 | super().__init__(parent) 12 | 13 | self._data = data 14 | 15 | self._initUi() 16 | 17 | def _initUi(self): 18 | self.setWindowTitle('指数均线统计') 19 | 20 | # 控件 21 | startDateLable = QLabel('开始日期') 22 | self._startDateLineEdit = QLineEdit(datetime.now().strftime("%Y-%m-%d")) 23 | 24 | endDateLable = QLabel('结束日期') 25 | self._endDateLineEdit = QLineEdit(datetime.now().strftime("%Y-%m-%d")) 26 | 27 | # 图示哪些指标 28 | showIndicatorLabel = QLabel('图示指标') 29 | 30 | # 收盘价和成交量 31 | self._maCheckBoxes = [] 32 | self._vmaCheckBoxes = [] 33 | for ma in [5, 10, 20, 30, 60]: 34 | self._maCheckBoxes.append(QCheckBox(str(ma) + '日均线')) 35 | self._maCheckBoxes[-1].setChecked(True) 36 | 37 | self._vmaCheckBoxes.append(QCheckBox(str(ma) + '日量均线')) 38 | 39 | cancelPushButton = QPushButton('Cancel') 40 | okPushButton = QPushButton('OK') 41 | cancelPushButton.clicked.connect(self._cancel) 42 | okPushButton.clicked.connect(self._ok) 43 | 44 | # 布局 45 | grid = QGridLayout() 46 | grid.setSpacing(10) 47 | 48 | grid.addWidget(startDateLable, 0, 0) 49 | grid.addWidget(self._startDateLineEdit, 1, 0) 50 | 51 | grid.addWidget(endDateLable, 0, 1) 52 | grid.addWidget(self._endDateLineEdit, 1, 1) 53 | 54 | grid.addWidget(showIndicatorLabel, 3, 0) 55 | 56 | for i, maCheckBox in enumerate(self._maCheckBoxes): 57 | grid.addWidget(maCheckBox, 4 + i, 0) 58 | 59 | for i, vmaCheckBox in enumerate(self._vmaCheckBoxes): 60 | grid.addWidget(vmaCheckBox, 4 + i, 1) 61 | 62 | grid.addWidget(okPushButton, 4 + i + 1, 1) 63 | grid.addWidget(cancelPushButton, 4 + i + 1, 0) 64 | 65 | self.setLayout(grid) 66 | 67 | def _ok(self): 68 | self._data['startDate'] = self._startDateLineEdit.text() 69 | self._data['endDate'] = self._endDateLineEdit.text() 70 | 71 | self._data['mas'] = [] 72 | for maCheckBox in self._maCheckBoxes: 73 | if maCheckBox.isChecked(): 74 | self._data['mas'].append(int(maCheckBox.text()[:-3])) 75 | 76 | self._data['vmas'] = [] 77 | for vmaCheckBox in self._vmaCheckBoxes: 78 | if vmaCheckBox.isChecked(): 79 | self._data['vmas'].append(int(vmaCheckBox.text()[:-4])) 80 | 81 | self.accept() 82 | 83 | def _cancel(self): 84 | self.reject() 85 | -------------------------------------------------------------------------------- /Stock/Select/Ui/Other/DyStockSelectJaccardIndexDlg.py: -------------------------------------------------------------------------------- 1 | from datetime import * 2 | 3 | from PyQt5.QtWidgets import QDialog, QGridLayout, QLabel, QLineEdit, QPushButton 4 | 5 | 6 | class DyStockSelectJaccardIndexDlg(QDialog): 7 | 8 | def __init__(self, data, parent=None): 9 | super().__init__(parent) 10 | 11 | self._data = data 12 | 13 | self._initUi() 14 | 15 | def _initUi(self): 16 | self.setWindowTitle('杰卡德指数') 17 | 18 | # 控件 19 | label = QLabel('参数(格式: 周期1,涨幅1(%);周期2,涨幅2(%);..., 比如10,10;20,20;...)') 20 | self._lineEdit = QLineEdit('10,10;20,20;30,30;40,40;50,50;60,60') 21 | 22 | startDateLable = QLabel('开始日期') 23 | self._startDateLineEdit = QLineEdit(datetime.now().strftime("%Y-%m-%d")) 24 | 25 | endDateLable = QLabel('结束日期') 26 | self._endDateLineEdit = QLineEdit(datetime.now().strftime("%Y-%m-%d")) 27 | 28 | cancelPushButton = QPushButton('Cancel') 29 | okPushButton = QPushButton('OK') 30 | cancelPushButton.clicked.connect(self._cancel) 31 | okPushButton.clicked.connect(self._ok) 32 | 33 | # 布局 34 | grid = QGridLayout() 35 | grid.setSpacing(10) 36 | 37 | grid.addWidget(startDateLable, 0, 0) 38 | grid.addWidget(self._startDateLineEdit, 1, 0) 39 | 40 | grid.addWidget(endDateLable, 0, 1) 41 | grid.addWidget(self._endDateLineEdit, 1, 1) 42 | 43 | grid.addWidget(label, 2, 0, 1, 2) 44 | grid.addWidget(self._lineEdit, 3, 0, 1, 2) 45 | 46 | grid.addWidget(okPushButton, 4, 1) 47 | grid.addWidget(cancelPushButton, 4, 0) 48 | 49 | self.setLayout(grid) 50 | 51 | def _ok(self): 52 | self._data['startDate'] = self._startDateLineEdit.text() 53 | self._data['endDate'] = self._endDateLineEdit.text() 54 | 55 | param = self._lineEdit.text() 56 | param = param.split(';') 57 | paramDict = {} 58 | for param_ in param: 59 | day, increase = param_.split(',') 60 | paramDict[int(day)] = int(increase) 61 | 62 | self._data['param'] = paramDict 63 | 64 | self.accept() 65 | 66 | def _cancel(self): 67 | self.reject() 68 | -------------------------------------------------------------------------------- /Stock/Select/Ui/Other/DyStockSelectTestedStocksDlg.py: -------------------------------------------------------------------------------- 1 | from datetime import * 2 | import os 3 | import re 4 | 5 | from PyQt5.QtWidgets import QDialog, QGridLayout, QLabel, QTextEdit, QPushButton, QApplication 6 | 7 | from DyCommon.DyCommon import * 8 | 9 | 10 | class DyStockSelectTestedStocksDlg(QDialog): 11 | 12 | def __init__(self, data, parent=None): 13 | super().__init__(parent) 14 | 15 | self._data = data 16 | 17 | self._init() 18 | self._initUi() 19 | 20 | def _init(self): 21 | path = DyCommon.createPath('Stock/User/Config/Testing') 22 | self._file = os.path.join(path, 'DyStockSelectTestedStocks.dy') 23 | 24 | def _read(self): 25 | if os.path.exists(self._file): 26 | with open(self._file) as f: 27 | codes = f.read() 28 | else: 29 | codes = "" 30 | 31 | return codes 32 | 33 | def _save(self): 34 | with open(self._file, 'w') as f: 35 | f.write(self._codesTextEdit.toPlainText()) 36 | 37 | def _initUi(self): 38 | self.setWindowTitle('要调试的股票') 39 | 40 | # 控件 41 | descriptionLabel = QLabel('要调试的股票代码') 42 | self._codesTextEdit = QTextEdit() 43 | self._codesTextEdit.setPlainText(self._read()) 44 | 45 | cancelPushButton = QPushButton('Cancel') 46 | okPushButton = QPushButton('OK') 47 | cancelPushButton.clicked.connect(self._cancel) 48 | okPushButton.clicked.connect(self._ok) 49 | 50 | # 布局 51 | grid = QGridLayout() 52 | grid.setSpacing(10) 53 | 54 | grid.addWidget(descriptionLabel, 0, 0) 55 | 56 | grid.addWidget(self._codesTextEdit, 1, 0, 20, 10) 57 | 58 | grid.addWidget(okPushButton, 1, 11) 59 | grid.addWidget(cancelPushButton, 2, 11) 60 | 61 | 62 | self.setLayout(grid) 63 | self.resize(QApplication.desktop().size().width()//3, QApplication.desktop().size().height()//2) 64 | 65 | def _ok(self): 66 | # save 67 | self._save() 68 | 69 | # set out data 70 | codes = re.split(',|\n| ', self._codesTextEdit.toPlainText()) 71 | temp = [] 72 | for x in codes: 73 | if x and x not in temp: temp.append(x) 74 | 75 | codes = [x + '.SH' if x[0] in ['6', '5'] else x + '.SZ' for x in temp] 76 | 77 | self._data['codes'] = codes 78 | 79 | self.accept() 80 | 81 | def _cancel(self): 82 | self.reject() 83 | -------------------------------------------------------------------------------- /Stock/Select/Ui/Other/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Select/Ui/Other/__init__.py -------------------------------------------------------------------------------- /Stock/Select/Ui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Select/Ui/__init__.py -------------------------------------------------------------------------------- /Stock/Select/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Select/__init__.py -------------------------------------------------------------------------------- /Stock/Trade/AccountManager/Broker/DyStockGtjaAccountManager.py: -------------------------------------------------------------------------------- 1 | from ..DyStockAccountManager import * 2 | from ....Common.DyStockCommon import * 3 | 4 | 5 | class DyStockGtjaAccountManager(DyStockAccountManager): 6 | """ 7 | 国泰君安Web管理类 8 | 由于Web接口不同数据之间的异步性,推送券商原始数据时,一定要保证顺序。 9 | 委托->成交->资金->持仓 10 | """ 11 | broker = 'gtja' 12 | brokerName = '国泰君安' 13 | 14 | headerNameMap = {'capital': {'availCash': '可用余额'}, 15 | 'position': {'code': '证券代码', 16 | 'name': '证券名称', 17 | 'totalVolume': '实际数量', 18 | 'availVolume': '可用数量', 19 | 'price': '最新价格', 20 | 'cost': '成本价(元)' 21 | }, 22 | 'curEntrust': {'code': '证券代码', 23 | 'price': '委托价格', 24 | 'totalVolume': '委托数量', 25 | 'dealedVolume': '成交数量', 26 | 'type': '类型', 27 | 'status': '委托状态', 28 | 'entrustId': '委托序号' 29 | }, 30 | 'curDeal': {'code': '证券代码', 31 | 'price': '成交价', 32 | 'datetime': '成交时间', 33 | 'dealedVolume': '成交数', 34 | 'type': '交易类型', 35 | 'dealId': '合同号' 36 | } 37 | } 38 | 39 | 40 | def __init__(self, eventEngine, info): 41 | super().__init__(eventEngine, info) 42 | 43 | def _matchDyEntrustByBrokerDeal(self, dyEntrust, dealType, dealedVolume, brokerEntrustId=None): 44 | """ 45 | 根据券商的成交单匹配DevilYuan系统的委托单 46 | 子类可以重载此函数 47 | """ 48 | # 由于券商的当日委托推送和当日成交推送是异步的,所以要考虑这之间可能有新的委托 49 | # 这里不考虑废单撤单状态,整个时序保证了不可能 50 | # !!!如果有跟策略股票和类型相同的手工委托单(通过其他系统的委托),则可能出现会错误地认为是由策略发出的。 51 | if dyEntrust.status != DyStockEntrust.Status.allDealed and dyEntrust.status != DyStockEntrust.Status.partDealed: 52 | return False 53 | 54 | #!!! 国泰君安的当日成交单的'交易类型'是'普通成交'和'撤单成交' 55 | # 所以这里就没法匹配type 56 | if dealType != '普通成交': 57 | return False 58 | 59 | # 在获取当日成交的时候,同一个委托又有了新的成交。 60 | # 由于推送的委托是上一次的,并且是贪婪式匹配,如果同一时刻两个策略发出相同的委托,则可能匹配错。 61 | # 但由于策略生成新委托时,若同一类型的委托还没有完成,账户管理类则拒绝策略的新委托。 62 | # 这个保证了匹配错误不会发生。 63 | if dyEntrust.matchedDealedVolume + dealedVolume > dyEntrust.dealedVolume: 64 | return False 65 | 66 | return True -------------------------------------------------------------------------------- /Stock/Trade/AccountManager/Broker/DyStockThsAccountManager.py: -------------------------------------------------------------------------------- 1 | from ..DyStockAccountManager import * 2 | from ....Common.DyStockCommon import * 3 | 4 | 5 | class DyStockThsAccountManager(DyStockAccountManager): 6 | """ 7 | 同花顺UI管理类 8 | 由于UI接口不同数据之间的异步性,推送券商原始数据时,一定要保证顺序。 9 | 委托->成交->资金->持仓 10 | """ 11 | broker = 'ths' 12 | brokerName = '同花顺' 13 | 14 | headerNameMap = {'capital': {'availCash': '可用金额'}, 15 | 'position': {'code': '证券代码', 16 | 'name': '证券名称', 17 | 'totalVolume': '股票余额', 18 | 'availVolume': '可用余额', 19 | 'price': '市价', 20 | 'cost': '成本价' 21 | }, 22 | 'curEntrust': {'code': '证券代码', 23 | 'price': '委托价格', 24 | 'totalVolume': '委托数量', 25 | 'dealedVolume': '成交数量', 26 | 'type': '操作', 27 | 'status': '备注', 28 | 'entrustId': '合同编号' 29 | }, 30 | 'curDeal': {'code': '证券代码', 31 | 'price': '成交均价', 32 | 'datetime': '成交时间', 33 | 'dealedVolume': '成交数量', 34 | 'type': '操作', 35 | 'dealId': '成交编号', 36 | 'entrustId': '合同编号', 37 | } 38 | } 39 | 40 | 41 | def __init__(self, eventEngine, info): 42 | super().__init__(eventEngine, info) 43 | 44 | -------------------------------------------------------------------------------- /Stock/Trade/AccountManager/Broker/DyStockYhAccountManager.py: -------------------------------------------------------------------------------- 1 | from ..DyStockAccountManager import * 2 | from ....Common.DyStockCommon import * 3 | 4 | 5 | class DyStockYhAccountManager(DyStockAccountManager): 6 | """ 7 | 银河证券UI管理类 8 | 由于UI接口不同数据之间的异步性,推送券商原始数据时,一定要保证顺序。 9 | 委托->成交->资金->持仓 10 | """ 11 | broker = 'yh' 12 | brokerName = '银河证券' 13 | 14 | headerNameMap = {'capital': {'availCash': '可用金额'}, 15 | 'position': {'code': '证券代码', 16 | 'name': '证券名称', 17 | 'totalVolume': '当前持仓', 18 | 'availVolume': '可用余额', 19 | 'price': '参考市价', 20 | 'cost': '参考成本价' 21 | }, 22 | 'curEntrust': {'code': '证券代码', 23 | 'price': '委托价格', 24 | 'totalVolume': '委托数量', 25 | 'dealedVolume': '成交数量', 26 | 'type': '操作', 27 | 'status': '备注', 28 | 'entrustId': '合同编号' 29 | }, 30 | 'curDeal': {'code': '证券代码', 31 | 'price': '成交均价', 32 | 'datetime': '成交时间', 33 | 'dealedVolume': '成交数量', 34 | 'type': '操作', 35 | 'dealId': '成交编号', 36 | 'entrustId': '合同编号', 37 | } 38 | } 39 | 40 | 41 | def __init__(self, eventEngine, info): 42 | super().__init__(eventEngine, info) 43 | 44 | -------------------------------------------------------------------------------- /Stock/Trade/AccountManager/Broker/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Trade/AccountManager/Broker/__init__.py -------------------------------------------------------------------------------- /Stock/Trade/AccountManager/StopMode/DyStockStopLossMaMode.py: -------------------------------------------------------------------------------- 1 | from .DyStockStopMode import * 2 | from ...DyStockTradeCommon import * 3 | 4 | 5 | class DyStockStopLossMaMode(DyStockStopMode): 6 | 7 | stopLossPnlRatio = -5 8 | 9 | def __init__(self, accountManager, dataEngine, ma): 10 | super().__init__(accountManager) 11 | 12 | self._dataEngine = dataEngine 13 | self._daysEngine = self._dataEngine.daysEngine 14 | self._ma = int(ma) 15 | 16 | self._tradeStartTime = '14:55:00' 17 | 18 | self._curInit() 19 | 20 | def _curInit(self): 21 | self._preparedData = {} 22 | 23 | def onOpen(self, date): 24 | 25 | self._curInit() 26 | 27 | preDate = self._daysEngine.tDaysOffsetInDb(date, -1) 28 | 29 | for code in self._accountManager.curPos: 30 | if not self._daysEngine.loadCode(code, [preDate, -self._ma+2], latestAdjFactorInDb=False): 31 | return False 32 | 33 | df = self._daysEngine.getDataFrame(code) 34 | if df.shape[0] != (self._ma - 1): return False 35 | 36 | self._preparedData[code] = df['close'].values.tolist() 37 | 38 | return True 39 | 40 | def _processAdj(self, code, tick): 41 | """ 处理除复权 """ 42 | 43 | if tick.preClose is None: return 44 | 45 | if code not in self._preparedData: return False 46 | if code not in self._accountManager.curPos: return False 47 | 48 | closes = self._preparedData[code] 49 | 50 | if tick.preClose == closes[-1]: 51 | return True 52 | 53 | # 复权 54 | adjFactor = tick.preClose/closes[-1] 55 | 56 | # 价格 57 | closes = list(map(lambda x,y:x*y, closes, [adjFactor]*len(closes))) 58 | closes[-1] = tick.preClose # 浮点数的精度问题 59 | 60 | self._preparedData[code] = closes 61 | 62 | return True 63 | 64 | def _stopLoss(self, code, tick): 65 | ma = (sum(self._preparedData[code]) + tick.price)/self._ma 66 | 67 | pos = self._accountManager.curPos[code] 68 | 69 | if tick.price < ma and pos.pnlRatio < self.stopLossPnlRatio: 70 | self._accountManager.closePos(tick.datetime, code, getattr(tick, DyStockTradeCommon.sellPrice), DyStockSellReason.stopLoss, tickOrBar=tick) 71 | 72 | def onTicks(self, ticks): 73 | for code, pos in self._accountManager.curPos.items(): 74 | tick = ticks.get(code) 75 | if tick is None: 76 | continue 77 | 78 | if tick.time < self._tradeStartTime: 79 | return 80 | 81 | if not self._processAdj(code, tick): 82 | continue 83 | 84 | self._stopLoss(code, tick) 85 | 86 | def onBars(self, bars): 87 | self.onTicks(bars) -------------------------------------------------------------------------------- /Stock/Trade/AccountManager/StopMode/DyStockStopLossPnlRatioMode.py: -------------------------------------------------------------------------------- 1 | from .DyStockStopMode import * 2 | from ...DyStockTradeCommon import * 3 | 4 | 5 | class DyStockStopLossPnlRatioMode(DyStockStopMode): 6 | 7 | def __init__(self, accountManager, pnlRatio): 8 | super().__init__(accountManager) 9 | 10 | self._pnlRatio = pnlRatio 11 | 12 | def onTicks(self, ticks): 13 | for code, pos in self._accountManager.curPos.items(): 14 | tick = ticks.get(code) 15 | if tick is None: 16 | continue 17 | 18 | if pos.pnlRatio < self._pnlRatio: 19 | self._accountManager.closePos(tick.datetime, code, getattr(tick, DyStockTradeCommon.sellPrice), DyStockSellReason.stopLoss, tickOrBar=tick) 20 | 21 | def onBars(self, bars): 22 | self.onTicks(bars) 23 | -------------------------------------------------------------------------------- /Stock/Trade/AccountManager/StopMode/DyStockStopLossStepMode.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from .DyStockStopMode import * 4 | from ...DyStockTradeCommon import * 5 | 6 | 7 | class DyStockStopLossStepMode(DyStockStopMode): 8 | 9 | def __init__(self, accountManager, initSLM=0.9, step=0.1, increment=0.09): 10 | """ 11 | 激活阶梯停损,可以不需要止盈模式,也就是说包含止盈和止损。 12 | 13 | https://www.ricequant.com/community/topic/1423/#share-source-code_content_7899_886349 14 | @initSLM=0.9 # 初始止损比例 M 15 | @step=0.10 # 间隔 X, 阶梯长度 16 | @increment=0.09 # 止损增量 Y, 阶梯变化率(阶梯每改变一次, 止损线上涨的幅度) 17 | 18 | 止损线改变次数 = floor[log(周期内最高股价/买入价)/log(1 + X%)] 19 | 止损比例 = M * [1+Y%] ^ 止损线改变次数 20 | 止损价 = 止损比例 * 成本价 21 | 22 | if 现价< 止损价: 23 | 直接跌破止损价, 卖出止损。 24 | else: 25 | 继续持有 26 | """ 27 | super().__init__(accountManager) 28 | 29 | self._initSLM = initSLM 30 | self._step = step 31 | self._increment = increment 32 | 33 | def onTicks(self, ticks): 34 | for code, pos in self._accountManager.curPos.items(): 35 | tick = ticks.get(code) 36 | if tick is None: 37 | continue 38 | 39 | currSL = self._initSLM * (1 + self._increment)**int((math.log(pos.high/pos.cost)/math.log(1 + self._step))) 40 | 41 | if tick.price < pos.cost*currSL: 42 | self._accountManager.closePos(tick.datetime, code, getattr(tick, DyStockTradeCommon.sellPrice), DyStockSellReason.stopLossStep, tickOrBar=tick) 43 | 44 | def onBars(self, bars): 45 | self.onTicks(bars) 46 | -------------------------------------------------------------------------------- /Stock/Trade/AccountManager/StopMode/DyStockStopMode.py: -------------------------------------------------------------------------------- 1 | class DyStockStopMode(object): 2 | 3 | def __init__(self, accountManager): 4 | self._accountManager = accountManager 5 | 6 | def onOpen(self, date): 7 | return True 8 | 9 | def onTicks(self, ticks): 10 | pass 11 | 12 | def onBars(self, bars): 13 | pass 14 | 15 | def setAccountManager(self, accountManager): 16 | self._accountManager = accountManager 17 | -------------------------------------------------------------------------------- /Stock/Trade/AccountManager/StopMode/DyStockStopProfitMaMode.py: -------------------------------------------------------------------------------- 1 | from .DyStockStopMode import * 2 | from ...DyStockTradeCommon import * 3 | 4 | 5 | class DyStockStopProfitMaMode(DyStockStopMode): 6 | 7 | profitRunningPnlRatio = 10 8 | 9 | def __init__(self, accountManager, dataEngine, ma): 10 | super().__init__(accountManager) 11 | 12 | self._dataEngine = dataEngine 13 | self._daysEngine = self._dataEngine.daysEngine 14 | self._ma = int(ma) 15 | 16 | self._tradeStartTime = '14:55:00' 17 | 18 | self._curInit() 19 | 20 | def _curInit(self): 21 | self._preparedData = {} 22 | 23 | def onOpen(self, date): 24 | 25 | self._curInit() 26 | 27 | preDate = self._daysEngine.tDaysOffsetInDb(date, -1) 28 | 29 | for code in self._accountManager.curPos: 30 | if not self._daysEngine.loadCode(code, [preDate, -self._ma+2], latestAdjFactorInDb = False): 31 | return False 32 | 33 | df = self._daysEngine.getDataFrame(code) 34 | if df.shape[0] != (self._ma - 1): return False 35 | 36 | self._preparedData[code] = df['close'].values.tolist() 37 | 38 | return True 39 | 40 | def _processAdj(self, code, tick): 41 | """ 处理除复权 """ 42 | 43 | if tick.preClose is None: return 44 | 45 | if code not in self._preparedData: return False 46 | if code not in self._accountManager.curPos: return False 47 | 48 | closes = self._preparedData[code] 49 | 50 | if tick.preClose == closes[-1]: 51 | return True 52 | 53 | # 复权 54 | adjFactor = tick.preClose/closes[-1] 55 | 56 | # 价格 57 | closes = list(map(lambda x,y:x*y, closes, [adjFactor]*len(closes))) 58 | closes[-1] = tick.preClose # 浮点数的精度问题 59 | 60 | self._preparedData[code] = closes 61 | 62 | return True 63 | 64 | def _stopProfit(self, code, tick): 65 | ma = (sum(self._preparedData[code]) + tick.price)/self._ma 66 | 67 | pos = self._accountManager.curPos[code] 68 | 69 | if pos.maxPnlRatio > self.profitRunningPnlRatio and tick.price < ma: 70 | self._accountManager.closePos(tick.datetime, code, getattr(tick, DyStockTradeCommon.sellPrice), DyStockSellReason.stopProfit, tickOrBar=tick) 71 | 72 | def onTicks(self, ticks): 73 | for code, pos in self._accountManager.curPos.items(): 74 | tick = ticks.get(code) 75 | if tick is None: 76 | continue 77 | 78 | if tick.time < self._tradeStartTime: 79 | return 80 | 81 | if not self._processAdj(code, tick): 82 | continue 83 | 84 | self._stopProfit(code, tick) -------------------------------------------------------------------------------- /Stock/Trade/AccountManager/StopMode/DyStockStopProfitPnlRatioMode.py: -------------------------------------------------------------------------------- 1 | from .DyStockStopMode import * 2 | from ...DyStockTradeCommon import * 3 | 4 | 5 | class DyStockStopProfitPnlRatioMode(DyStockStopMode): 6 | 7 | def __init__(self, accountManager, pnlRatio): 8 | super().__init__(accountManager) 9 | 10 | self._pnlRatio = pnlRatio 11 | 12 | def onTicks(self, ticks): 13 | for code, pos in self._accountManager.curPos.items(): 14 | tick = ticks.get(code) 15 | if tick is None: 16 | continue 17 | 18 | if pos.pnlRatio >= self._pnlRatio: 19 | self._accountManager.closePos(tick.datetime, code, getattr(tick, DyStockTradeCommon.sellPrice), DyStockSellReason.stopProfit, tickOrBar=tick) 20 | 21 | def onBars(self, bars): 22 | self.onTicks(bars) 23 | -------------------------------------------------------------------------------- /Stock/Trade/AccountManager/StopMode/DyStockStopTimeMode.py: -------------------------------------------------------------------------------- 1 | from .DyStockStopMode import * 2 | from ...DyStockTradeCommon import * 3 | 4 | 5 | class DyStockStopTimeMode(DyStockStopMode): 6 | 7 | profitRunningPnlRatio = 10 8 | 9 | def __init__(self, accountManager, dayNbr, pnlRatio): 10 | super().__init__(accountManager) 11 | 12 | self._dayNbr = dayNbr 13 | self._pnlRatio = pnlRatio 14 | 15 | self._tradeStartTime = '14:55:00' 16 | 17 | def onTicks(self, ticks): 18 | for code, pos in self._accountManager.curPos.items(): 19 | tick = ticks.get(code) 20 | if tick is None: 21 | continue 22 | 23 | if pos.holdingPeriod >= self._dayNbr: 24 | if pos.pnlRatio >= self._pnlRatio: 25 | self._accountManager.closePos(tick.datetime, code, getattr(tick, DyStockTradeCommon.sellPrice), DyStockSellReason.stopTime, tickOrBar=tick) 26 | 27 | def onBars(self, bars): 28 | self.onTicks(bars) 29 | -------------------------------------------------------------------------------- /Stock/Trade/AccountManager/StopMode/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Trade/AccountManager/StopMode/__init__.py -------------------------------------------------------------------------------- /Stock/Trade/AccountManager/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Trade/AccountManager/__init__.py -------------------------------------------------------------------------------- /Stock/Trade/Broker/DyStockTradeBrokerEngine.py: -------------------------------------------------------------------------------- 1 | from EventEngine.DyEvent import * 2 | from ..DyStockTradeCommon import * 3 | from .YhNew.YhTrader import YhTrader 4 | from .Ths.ThsTrader import ThsTrader 5 | from .Simu.SimuTrader import * 6 | 7 | 8 | class DyStockTradeBrokerEngine(object): 9 | """ 券商交易接口引擎 """ 10 | 11 | traderMap = { 12 | 'yh': YhTrader, 13 | 'ths': ThsTrader, 14 | 15 | 'simu1': SimuTrader1, 16 | 'simu2': SimuTrader2, 17 | 'simu3': SimuTrader3, 18 | 'simu4': SimuTrader4, 19 | 'simu5': SimuTrader5, 20 | 'simu6': SimuTrader6, 21 | 'simu7': SimuTrader7, 22 | 'simu8': SimuTrader8, 23 | 'simu9': SimuTrader9, 24 | } 25 | 26 | def __init__(self, eventEngine, info): 27 | self._eventEngine = eventEngine 28 | self._info = info 29 | 30 | self._traders = {} 31 | 32 | self._registerEvent() 33 | 34 | def _registerEvent(self): 35 | self._eventEngine.register(DyEventType.stockLogin, self._stockLoginHandler, DyStockTradeEventHandType.brokerEngine) 36 | self._eventEngine.register(DyEventType.stockLogout, self._stockLogoutHandler, DyStockTradeEventHandType.brokerEngine) 37 | 38 | def _stockLoginHandler(self, event): 39 | broker = event.data['broker'] 40 | 41 | # create trader instance 42 | trader = self.traderMap[broker](self._eventEngine, self._info) 43 | 44 | # login 45 | trader.login() 46 | 47 | # sync pos 48 | trader.syncPos() 49 | 50 | # update account 51 | trader.updateAccount() 52 | 53 | self._traders[broker] = trader 54 | 55 | def _stockLogoutHandler(self, event): 56 | broker = event.data['broker'] 57 | oneKeyHangUp = True if event.data.get('oneKeyHangUp') else False # 是否是一键挂机导致的交易接口退出 58 | 59 | trader = self._traders[broker] 60 | 61 | trader.logout(oneKeyHangUp) 62 | 63 | del self._traders[broker] -------------------------------------------------------------------------------- /Stock/Trade/Broker/Simu/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Trade/Broker/Simu/__init__.py -------------------------------------------------------------------------------- /Stock/Trade/Broker/ThirdLibrary/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Trade/Broker/ThirdLibrary/__init__.py -------------------------------------------------------------------------------- /Stock/Trade/Broker/ThirdLibrary/getcode_jdk1.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Trade/Broker/ThirdLibrary/getcode_jdk1.5.jar -------------------------------------------------------------------------------- /Stock/Trade/Broker/ThirdLibrary/yjb_verify_code.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Trade/Broker/ThirdLibrary/yjb_verify_code.jar -------------------------------------------------------------------------------- /Stock/Trade/Broker/Ths/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Trade/Broker/Ths/__init__.py -------------------------------------------------------------------------------- /Stock/Trade/Broker/Ths/pop_dialog_handler.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import re 3 | import time 4 | 5 | 6 | class PopDialogHandler: 7 | def __init__(self, app): 8 | self._app = app 9 | 10 | def handle(self, title): 11 | if any(s in title for s in {"提示信息", "委托确认", "网上交易用户协议"}): 12 | self._submit_by_shortcut() 13 | return None 14 | 15 | if "提示" in title: 16 | content = self._extract_content() 17 | self._submit_by_click() 18 | return {"message": content} 19 | 20 | content = self._extract_content() 21 | self._close() 22 | return {"message": "unknown message: {}".format(content)} 23 | 24 | def _extract_content(self): 25 | return self._app.top_window().Static.window_text() 26 | 27 | def _extract_entrust_id(self, content): 28 | return re.search(r"\d+", content).group() 29 | 30 | def _submit_by_click(self): 31 | self._app.top_window()["确定"].click() 32 | 33 | def _submit_by_shortcut(self): 34 | self._app.top_window().type_keys("%Y") 35 | 36 | def _close(self): 37 | self._app.top_window().close() 38 | 39 | 40 | class TradePopDialogHandler(PopDialogHandler): 41 | def handle(self, title): 42 | if title == "委托确认": 43 | self._submit_by_shortcut() 44 | return None 45 | 46 | if title == "提示信息": 47 | content = self._extract_content() 48 | if "超出涨跌停" in content: 49 | self._submit_by_shortcut() 50 | return None 51 | 52 | if "委托价格的小数价格应为" in content: 53 | self._submit_by_shortcut() 54 | return None 55 | 56 | return None 57 | 58 | if title == "提示": 59 | content = self._extract_content() 60 | if "成功" in content: 61 | entrust_no = self._extract_entrust_id(content) 62 | self._submit_by_click() 63 | return {"entrust_no": entrust_no} 64 | 65 | self._submit_by_click() 66 | time.sleep(0.05) 67 | raise IOError(content) 68 | self._close() 69 | return None -------------------------------------------------------------------------------- /Stock/Trade/Broker/UiTrader.py: -------------------------------------------------------------------------------- 1 | from .DyTrader import * 2 | 3 | 4 | class UiTrader(DyTrader): 5 | """ 6 | 券商窗口交易接口基类 7 | """ 8 | name = 'UI' 9 | 10 | heartBeatTimer = 60 11 | pollingCurEntrustTimer = 5 12 | maxRetryNbr = 3 # 最大重试次数 13 | 14 | 15 | def __init__(self, eventEngine, info, accountConfigFile=None): 16 | super().__init__(eventEngine, info, None, accountConfigFile) 17 | 18 | self._balanceHeader = None 19 | self._positionHeader = None 20 | 21 | def _sendHeartBeat(self, event): 22 | self.refresh() 23 | -------------------------------------------------------------------------------- /Stock/Trade/Broker/WebTrader.py: -------------------------------------------------------------------------------- 1 | import ssl 2 | import random 3 | import requests 4 | from requests.adapters import HTTPAdapter 5 | from requests.packages.urllib3.poolmanager import PoolManager 6 | 7 | from .DyTrader import * 8 | 9 | 10 | class Ssl3HttpAdapter(HTTPAdapter): 11 | def init_poolmanager(self, connections, maxsize, block=False): 12 | self.poolmanager = PoolManager(num_pools=connections, 13 | maxsize=maxsize, 14 | block=block, 15 | ssl_version=ssl.PROTOCOL_TLSv1) 16 | 17 | 18 | class WebTrader(DyTrader): 19 | """ 20 | 券商Web交易接口基类 21 | """ 22 | name = 'Web' 23 | 24 | heartBeatTimer = 60 25 | pollingCurEntrustTimer = 1 26 | maxRetryNbr = 3 # 最大重试次数 27 | 28 | 29 | def __init__(self, eventEngine, info, configFile=None, accountConfigFile=None): 30 | super().__init__(eventEngine, info, configFile, accountConfigFile) 31 | 32 | self._httpAdapter = None 33 | 34 | def _preLogin(self): 35 | # 开始一个会话 36 | self._session = requests.session() 37 | if self._httpAdapter is not None: 38 | self._session.mount('https://', self._httpAdapter()) 39 | 40 | # session headers 41 | headers = { 42 | 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko' 43 | } 44 | self._session.headers.update(headers) 45 | 46 | def _postLogout(self): 47 | self._session.close() 48 | -------------------------------------------------------------------------------- /Stock/Trade/Broker/YhNew/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Stock/Trade/Broker/YhNew/clienttrader.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | import os 4 | import time 5 | from abc import abstractmethod 6 | 7 | from . import helpers 8 | from .config import client 9 | 10 | 11 | class ClientTrader: 12 | def __init__(self): 13 | self._config = client.create(self.broker_type) 14 | 15 | def prepare(self, config_path=None, user=None, password=None, exe_path=None, comm_password=None, 16 | **kwargs): 17 | """ 18 | 登陆客户端 19 | :param config_path: 登陆配置文件,跟参数登陆方式二选一 20 | :param user: 账号 21 | :param password: 明文密码 22 | :param exe_path: 客户端路径类似 r'C:\\htzqzyb2\\xiadan.exe', 默认 r'C:\\htzqzyb2\\xiadan.exe' 23 | :param comm_password: 通讯密码 24 | :return: 25 | """ 26 | if config_path is not None: 27 | account = helpers.file2dict(config_path) 28 | user = account['user'] 29 | password = account['password'] 30 | self.login(user, password, exe_path or self._config.DEFAULT_EXE_PATH, comm_password, **kwargs) 31 | 32 | @abstractmethod 33 | def login(self, user, password, exe_path, comm_password=None, **kwargs): 34 | pass 35 | 36 | @property 37 | @abstractmethod 38 | def broker_type(self): 39 | pass 40 | 41 | @property 42 | @abstractmethod 43 | def balance(self): 44 | pass 45 | 46 | @property 47 | @abstractmethod 48 | def position(self): 49 | pass 50 | 51 | @property 52 | @abstractmethod 53 | def cancel_entrusts(self): 54 | pass 55 | 56 | @property 57 | @abstractmethod 58 | def today_entrusts(self): 59 | pass 60 | 61 | @property 62 | @abstractmethod 63 | def today_trades(self): 64 | pass 65 | 66 | @abstractmethod 67 | def cancel_entrust(self, entrust_no): 68 | pass 69 | 70 | @abstractmethod 71 | def buy(self, security, price, amount, **kwargs): 72 | pass 73 | 74 | @abstractmethod 75 | def sell(self, security, price, amount, **kwargs): 76 | pass 77 | 78 | def auto_ipo(self): 79 | raise NotImplementedError 80 | 81 | def _run_exe_path(self, exe_path): 82 | return os.path.join( 83 | os.path.dirname(exe_path), 'xiadan.exe' 84 | ) 85 | 86 | def _wait(self, seconds): 87 | time.sleep(seconds) 88 | 89 | def exit(self): 90 | self._app.kill() 91 | 92 | def _close_prompt_windows(self): 93 | self._wait(1) 94 | for w in self._app.windows(class_name='#32770'): 95 | if w.window_text() != self._config.TITLE: 96 | w.close() 97 | self._wait(1) 98 | -------------------------------------------------------------------------------- /Stock/Trade/Broker/YhNew/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Trade/Broker/YhNew/config/__init__.py -------------------------------------------------------------------------------- /Stock/Trade/Broker/YhNew/exceptions.py: -------------------------------------------------------------------------------- 1 | # coding:utf8 2 | 3 | class TradeError(IOError): 4 | pass 5 | -------------------------------------------------------------------------------- /Stock/Trade/Broker/YhNew/helpers.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | import pytesseract 3 | 4 | 5 | def recognize_verify_code(image_path, broker='ht'): 6 | """识别验证码,返回识别后的字符串,使用 tesseract 实现 7 | :param image_path: 图片路径 8 | :param broker: 券商 ['ht', 'yjb', 'gf', 'yh'] 9 | :return recognized: verify code string""" 10 | 11 | if broker == 'yh_client': 12 | return detect_yh_client_result(image_path) 13 | 14 | # no default tesseract 识别 15 | raise ValueError('不支持的验证码识别') 16 | 17 | def detect_yh_client_result(image_path): 18 | """封装了tesseract的识别,部署在阿里云上,服务端源码地址为: https://github.com/shidenggui/yh_verify_code_docker""" 19 | 20 | image = Image.open(image_path) 21 | code = pytesseract.image_to_string(image, config='-psm 7') 22 | return code.replace(' ', '') 23 | -------------------------------------------------------------------------------- /Stock/Trade/Broker/YhNew/log.py: -------------------------------------------------------------------------------- 1 | from DyCommon.DyCommon import * 2 | 3 | 4 | class log(object): 5 | """ 6 | log adaptor for easytrader 7 | """ 8 | 9 | dyInfo = None 10 | 11 | def info(text): 12 | log.dyInfo.print(text, DyLogData.info) 13 | 14 | def error(text): 15 | log.dyInfo.print(text, DyLogData.error) 16 | 17 | def warning(text): 18 | log.dyInfo.print(text, DyLogData.warning) 19 | 20 | -------------------------------------------------------------------------------- /Stock/Trade/Broker/__init__.py: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /Stock/Trade/Engine/DyStockTradeMainEngine.py: -------------------------------------------------------------------------------- 1 | from DyCommon.DyCommon import * 2 | 3 | from EventEngine.DyEventEngine import * 4 | 5 | from ..DyStockTradeCommon import * 6 | 7 | from ..Market.DyStockMarketEngine import * 8 | from ..Strategy.DyStockCtaEngine import * 9 | from ..Broker.DyStockTradeBrokerEngine import * 10 | from ...Data.Engine.DyStockDataEngine import * 11 | #from ..QQ.DyStockTradeQQMsgEngine import * 12 | from ..WeChat.DyStockTradeWxEngine import * 13 | #from ..Rpc.DyStockTradeRpcEngine import DyStockTradeRpcEngine 14 | 15 | 16 | class DyStockTradeMainEngine(object): 17 | def __init__(self): 18 | self._eventEngine = DyEventEngine(DyStockTradeEventHandType.nbr) 19 | self._info = DyInfo(self._eventEngine) 20 | 21 | self._dataEngine = DyStockDataEngine(self._eventEngine, self._info, False) 22 | 23 | # 实时行情监控 24 | self._stockMarketEngine = DyStockMarketEngine(self._eventEngine, self._info) 25 | 26 | # 交易接口 27 | self._stockBrokerEngine = DyStockTradeBrokerEngine(self._eventEngine, self._info) 28 | 29 | # 策略CTA引擎 30 | self._stockCtaEngine = DyStockCtaEngine(self._dataEngine, self._eventEngine, self._info) 31 | 32 | # QQ消息 33 | #self._QQMsgEngine = DyStockTradeQQMsgEngine(self._eventEngine, self._info) 34 | 35 | # 微信 36 | self._wxEngine = DyStockTradeWxEngine(self._eventEngine, self._info) 37 | 38 | # RPC 39 | #self._rpcEngine = DyStockTradeRpcEngine(self._eventEngine, self._info) 40 | 41 | self._eventEngine.start() 42 | 43 | @property 44 | def eventEngine(self): 45 | return self._eventEngine 46 | 47 | @property 48 | def info(self): 49 | return self._info 50 | 51 | def exit(self): 52 | """退出程序前调用,保证正常退出""" 53 | # 停止事件引擎 54 | self._eventEngine.stop() 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Stock/Trade/Engine/__init__.py: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /Stock/Trade/Market/DyStockMarketFilter.py: -------------------------------------------------------------------------------- 1 | class DyStockMarketFilter(object): 2 | 3 | def __init__(self, monitoredStocks=None): 4 | self._filter = None if monitoredStocks is None else set(monitoredStocks) # [code] 5 | 6 | def addFilter(self, monitoredStocks): 7 | if self._filter is None: 8 | self._filter = set(monitoredStocks) 9 | else: 10 | self._filter |= set(monitoredStocks) 11 | 12 | def filter(self, data): 13 | """ 过滤市场发过来的股票tick数据或者bar数据 14 | @data: {code:DyStockCtaTickData} or {code:DyStockCtaBarData} 15 | @return: {code:DyStockCtaTickData} or {code:DyStockCtaBarData} 16 | """ 17 | if self._filter is None: 18 | return data 19 | 20 | newData = {} 21 | for code in self._filter: 22 | data_ = data.get(code) 23 | if data_ is not None: 24 | newData[code] = data_ 25 | 26 | return newData 27 | 28 | def removeFilter(self, stocks): 29 | if self._filter is not None: 30 | self._filter -= set(stocks) 31 | 32 | @property 33 | def codes(self): 34 | return self._filter 35 | 36 | -------------------------------------------------------------------------------- /Stock/Trade/Market/__init__.py: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /Stock/Trade/Strategy/Cta/DyST_BankIntraDaySpread.py: -------------------------------------------------------------------------------- 1 | from ..DyStockCtaTemplate import * 2 | 3 | 4 | class DyST_BankIntraDaySpread(DyStockCtaTemplate): 5 | 6 | name = 'DyST_BankIntraDaySpread' 7 | chName = '银行日内价差' 8 | 9 | backTestingMode = 'bar1m' 10 | 11 | broker = 'yh' 12 | 13 | # 策略实盘参数 14 | codes = ['601988.SH', '601288.SH', '601398.SH', '601939.SH'] 15 | spread = 0.5 16 | 17 | 18 | def __init__(self, ctaEngine, info, state, strategyParam=None): 19 | super().__init__(ctaEngine, info, state, strategyParam) 20 | 21 | self._curInit() 22 | 23 | def _onOpenConfig(self): 24 | self._monitoredStocks.extend(self.codes) 25 | 26 | def _curInit(self, date=None): 27 | pass 28 | 29 | @DyStockCtaTemplate.onOpenWrapper 30 | def onOpen(self, date, codes=None): 31 | # 当日初始化 32 | self._curInit(date) 33 | 34 | self._onOpenConfig() 35 | 36 | return True 37 | 38 | def onTicks(self, ticks): 39 | """ 40 | 收到行情TICKs推送 41 | @ticks: {code: DyStockCtaTickData} 42 | """ 43 | 44 | if self._curPos: 45 | code = list(self._curPos)[0] 46 | tick = ticks.get(code) 47 | if tick is None: 48 | return 49 | 50 | increase = (tick.price - tick.preClose)/tick.preClose*100 51 | 52 | spreads = {} 53 | for code_ in self.codes: 54 | if code == code_: 55 | continue 56 | 57 | tick_ = ticks.get(code_) 58 | if tick_ is None: 59 | continue 60 | 61 | spread = increase - (tick_.price - tick_.preClose)/tick_.preClose*100 62 | if spread >= self.spread: 63 | spreads[code_] = spread 64 | 65 | codes = sorted(spreads, key=lambda k: spreads[k], reverse=True) 66 | if codes: 67 | self.closePos(ticks.get(code)) 68 | 69 | self.buyByRatio(ticks.get(codes[0]), 50, self.cAccountLeftCashRatio) 70 | 71 | else: 72 | increases = {} 73 | for code in self.codes: 74 | tick = ticks.get(code) 75 | if tick is None: 76 | continue 77 | 78 | increases[code] = (tick.price - tick.preClose)/tick.preClose*100 79 | 80 | codes = sorted(increases, key=lambda k: increases[k]) 81 | if codes: 82 | self.buyByRatio(ticks.get(codes[0]), 50, self.cAccountLeftCashRatio) 83 | 84 | def onBars(self, bars): 85 | self.onTicks(bars) 86 | -------------------------------------------------------------------------------- /Stock/Trade/Strategy/Cta/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Trade/Strategy/Cta/__init__.py -------------------------------------------------------------------------------- /Stock/Trade/Strategy/__init__.py: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /Stock/Trade/Ui/Basic/Account/DyStockTradeAccountWidget.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QTabWidget 2 | 3 | from .DyStockTradeBrokerAccountWidget import * 4 | from ....DyStockTradeCommon import * 5 | 6 | 7 | class DyStockTradeAccountWidget(QTabWidget): 8 | """ 股票交易账户窗口, 管理所有券商的账户窗口 """ 9 | 10 | signalLogin = QtCore.pyqtSignal(type(DyEvent())) 11 | signalLogout = QtCore.pyqtSignal(type(DyEvent())) 12 | 13 | 14 | def __init__(self, eventEngine): 15 | super().__init__() 16 | 17 | self._eventEngine = eventEngine 18 | 19 | self._brokerAccountWidgets = {} 20 | 21 | self._registerEvent() 22 | 23 | def _registerEvent(self): 24 | self.signalLogin.connect(self._stockLoginHandler) 25 | self._eventEngine.register(DyEventType.stockLogin, self.signalLogin.emit) 26 | 27 | self.signalLogout.connect(self._stockLogoutHandler) 28 | self._eventEngine.register(DyEventType.stockLogout, self.signalLogout.emit) 29 | 30 | def _stockLoginHandler(self, event): 31 | broker = event.data['broker'] 32 | 33 | # create broker account widget 34 | widget = DyStockTradeBrokerAccountWidget(self._eventEngine, broker) 35 | self.addTab(widget, DyStockTradeCommon.accountMap[broker]) 36 | 37 | self._brokerAccountWidgets[broker] = widget 38 | 39 | def _stockLogoutHandler(self, event): 40 | broker = event.data['broker'] 41 | 42 | widget = self._brokerAccountWidgets[broker] 43 | widget.close() 44 | 45 | self.removeTab(self.indexOf(widget)) 46 | 47 | del self._brokerAccountWidgets[broker] 48 | -------------------------------------------------------------------------------- /Stock/Trade/Ui/Basic/Account/DyStockTradeBrokerAccountWidget.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QTabWidget 2 | 3 | from .DyStockTradeCapitalWidget import * 4 | from .DyStockTradeCurDealsWidget import * 5 | from .DyStockTradeCurEntrustsWidget import * 6 | from .DyStockTradePositionWidget import * 7 | 8 | 9 | class DyStockTradeBrokerAccountWidget(QTabWidget): 10 | """ 券商股票交易账户窗口 """ 11 | 12 | 13 | def __init__(self, eventEngine, broker): 14 | super().__init__() 15 | 16 | self._eventEngine = eventEngine 17 | self._broker = broker 18 | 19 | self._initUi() 20 | 21 | def _initUi(self): 22 | self._widgets = [] 23 | 24 | widget = DyStockTradeCapitalWidget(self._eventEngine, self._broker) 25 | self.addTab(widget, '资金') 26 | self._widgets.append(widget) 27 | 28 | widget = DyStockTradePositionWidget(self._eventEngine, self._broker) 29 | self.addTab(widget, '持仓') 30 | self._widgets.append(widget) 31 | 32 | widget = DyStockTradeCurEntrustsWidget(self._eventEngine, self._broker) 33 | self.addTab(widget, '当日委托') 34 | self._widgets.append(widget) 35 | 36 | widget = DyStockTradeCurDealsWidget(self._eventEngine, self._broker) 37 | self.addTab(widget, '当日成交') 38 | self._widgets.append(widget) 39 | 40 | def closeEvent(self, event): 41 | for widget in self._widgets: 42 | widget.close() 43 | 44 | return super().closeEvent(event) 45 | -------------------------------------------------------------------------------- /Stock/Trade/Ui/Basic/Account/DyStockTradeCapitalWidget.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtCore 2 | 3 | from DyCommon.Ui.DyTableWidget import * 4 | from EventEngine.DyEvent import * 5 | 6 | 7 | class DyStockTradeCapitalWidget(DyTableWidget): 8 | """ 9 | 股票交易账户资金状况窗口 10 | !!!券商接口推送的原始数据 11 | """ 12 | signal = QtCore.pyqtSignal(type(DyEvent())) 13 | 14 | 15 | def __init__(self, eventEngine, broker): 16 | super().__init__(None, True, False) 17 | 18 | self._eventEngine = eventEngine 19 | self._broker = broker 20 | 21 | self._headerSet = False 22 | 23 | self._registerEvent() 24 | 25 | def _signalEmitWrapper(self, event): 26 | self.signal.emit(event) 27 | 28 | def _registerEvent(self): 29 | self.signal.connect(self._stockCapitalUpdateHandler) 30 | self._eventEngine.register(DyEventType.stockCapitalUpdate + self._broker, self._signalEmitWrapper) 31 | self._eventEngine.register(DyEventType.stockCapitalTickUpdate + self._broker, self._signalEmitWrapper) 32 | 33 | def _unregisterEvent(self): 34 | self.signal.disconnect(self._stockCapitalUpdateHandler) 35 | self._eventEngine.unregister(DyEventType.stockCapitalUpdate + self._broker, self._signalEmitWrapper) 36 | self._eventEngine.unregister(DyEventType.stockCapitalTickUpdate + self._broker, self._signalEmitWrapper) 37 | 38 | def _stockCapitalUpdateHandler(self, event): 39 | header = event.data['header'] 40 | rows = event.data['rows'] 41 | 42 | if not self._headerSet: 43 | self.setColNames(header) 44 | self._headerSet = True 45 | 46 | # strip 47 | for row in rows: 48 | if isinstance(row[0], str): 49 | row[0] = row[0].strip() 50 | 51 | self[0] = rows[0] 52 | 53 | def closeEvent(self, event): 54 | self._unregisterEvent() 55 | 56 | return super().closeEvent(event) 57 | -------------------------------------------------------------------------------- /Stock/Trade/Ui/Basic/Account/DyStockTradeCurDealsWidget.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtCore 2 | 3 | from DyCommon.Ui.DyTableWidget import * 4 | from EventEngine.DyEvent import * 5 | 6 | 7 | class DyStockTradeCurDealsWidget(DyTableWidget): 8 | """ 股票交易账户当日成交窗口 """ 9 | 10 | signal = QtCore.pyqtSignal(type(DyEvent())) 11 | 12 | 13 | def __init__(self, eventEngine, broker): 14 | super().__init__(None, True, False, floatRound=3) 15 | 16 | self._eventEngine = eventEngine 17 | self._broker = broker 18 | 19 | self._headerSet = False 20 | 21 | self._registerEvent() 22 | 23 | def _signalEmitWrapper(self, event): 24 | """ !!!Note: The value of signal.emit will always be changed each time you getting. 25 | """ 26 | self.signal.emit(event) 27 | 28 | def _registerEvent(self): 29 | self.signal.connect(self._stockCurDealsUpdateHandler) 30 | self._eventEngine.register(DyEventType.stockCurDealsUpdate + self._broker, self._signalEmitWrapper) 31 | 32 | def _unregisterEvent(self): 33 | self.signal.disconnect(self._stockCurDealsUpdateHandler) 34 | self._eventEngine.unregister(DyEventType.stockCurDealsUpdate + self._broker, self._signalEmitWrapper) 35 | 36 | def _stockCurDealsUpdateHandler(self, event): 37 | header = event.data['header'] 38 | rows = event.data['rows'] 39 | 40 | if not self._headerSet: 41 | self.setColNames(header) 42 | self._headerSet = True 43 | 44 | self.fastAppendRows(rows, new=True) 45 | 46 | self.setItemsForeground(range(self.rowCount()), (('买入', Qt.red), ('卖出', Qt.darkGreen))) 47 | 48 | def closeEvent(self, event): 49 | self._unregisterEvent() 50 | 51 | return super().closeEvent(event) 52 | -------------------------------------------------------------------------------- /Stock/Trade/Ui/Basic/Account/DyStockTradeCurEntrustsWidget.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtCore 2 | 3 | from DyCommon.Ui.DyTableWidget import * 4 | from EventEngine.DyEvent import * 5 | 6 | 7 | class DyStockTradeCurEntrustsWidget(DyTableWidget): 8 | """ 股票交易账户当日委托窗口 """ 9 | 10 | signal = QtCore.pyqtSignal(type(DyEvent())) 11 | 12 | 13 | def __init__(self, eventEngine, broker): 14 | super().__init__(None, True, False, floatRound=3) 15 | 16 | self._eventEngine = eventEngine 17 | self._broker = broker 18 | 19 | self._headerSet = False 20 | 21 | self._registerEvent() 22 | 23 | def _signalEmitWrapper(self, event): 24 | """ !!!Note: The value of signal.emit will always be changed each time you getting. 25 | """ 26 | self.signal.emit(event) 27 | 28 | def _registerEvent(self): 29 | self.signal.connect(self._stockCurEntrustsUpdateHandler) 30 | self._eventEngine.register(DyEventType.stockCurEntrustsUpdate + self._broker, self._signalEmitWrapper) 31 | 32 | def _unregisterEvent(self): 33 | self.signal.disconnect(self._stockCurEntrustsUpdateHandler) 34 | self._eventEngine.unregister(DyEventType.stockCurEntrustsUpdate + self._broker, self._signalEmitWrapper) 35 | 36 | def _stockCurEntrustsUpdateHandler(self, event): 37 | header = event.data['header'] 38 | rows = event.data['rows'] 39 | 40 | if not self._headerSet: 41 | self.setColNames(header) 42 | self._headerSet = True 43 | 44 | # strip 45 | for row in rows: 46 | row[0] = row[0].strip() 47 | 48 | self.fastAppendRows(rows, new=True) 49 | 50 | self.setItemsForeground(range(self.rowCount()), (('买入', Qt.red), ('卖出', Qt.darkGreen))) 51 | 52 | def closeEvent(self, event): 53 | self._unregisterEvent() 54 | 55 | return super().closeEvent(event) 56 | -------------------------------------------------------------------------------- /Stock/Trade/Ui/Basic/Account/DyStockTradePositionWidget.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtCore 2 | 3 | from DyCommon.Ui.DyTableWidget import * 4 | from EventEngine.DyEvent import * 5 | 6 | 7 | class DyStockTradePositionWidget(DyTableWidget): 8 | """ 9 | 股票交易账户持仓窗口 10 | !!!券商接口推送的原始数据 11 | """ 12 | signal = QtCore.pyqtSignal(type(DyEvent())) 13 | 14 | 15 | def __init__(self, eventEngine, broker): 16 | super().__init__(None, True, False, floatRound=3) 17 | 18 | self._eventEngine = eventEngine 19 | self._broker = broker 20 | 21 | self._headerSet = False 22 | 23 | self._registerEvent() 24 | 25 | def _signalEmitWrapper(self, event): 26 | self.signal.emit(event) 27 | 28 | def _registerEvent(self): 29 | self.signal.connect(self._stockPositionUpdateHandler) 30 | self._eventEngine.register(DyEventType.stockPositionUpdate + self._broker, self._signalEmitWrapper) 31 | self._eventEngine.register(DyEventType.stockPositionTickUpdate + self._broker, self._signalEmitWrapper) 32 | 33 | def _unregisterEvent(self): 34 | self.signal.disconnect(self._stockPositionUpdateHandler) 35 | self._eventEngine.unregister(DyEventType.stockPositionUpdate + self._broker, self._signalEmitWrapper) 36 | self._eventEngine.unregister(DyEventType.stockPositionTickUpdate + self._broker, self._signalEmitWrapper) 37 | 38 | def _stockPositionUpdateHandler(self, event): 39 | header = event.data['header'] 40 | rows = event.data['rows'] 41 | autoForegroundColName = event.data['autoForegroundHeaderName'] 42 | 43 | if not self._headerSet: 44 | self.setColNames(header) 45 | self._headerSet = True 46 | 47 | self.fastAppendRows(rows, autoForegroundColName=autoForegroundColName, new=True) 48 | 49 | def closeEvent(self, event): 50 | self._unregisterEvent() 51 | 52 | return super().closeEvent(event) -------------------------------------------------------------------------------- /Stock/Trade/Ui/Basic/Account/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Trade/Ui/Basic/Account/__init__.py -------------------------------------------------------------------------------- /Stock/Trade/Ui/Basic/DyStockMarketIndexMonitorWidget.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtCore 2 | 3 | from DyCommon.Ui.DyTableWidget import * 4 | from EventEngine.DyEvent import * 5 | from ...Market.DyStockMarketFilter import * 6 | from ....Common.DyStockCommon import * 7 | 8 | 9 | class DyStockMarketIndexMonitorWidget(DyTableWidget): 10 | 11 | signal = QtCore.pyqtSignal(type(DyEvent())) 12 | 13 | header = ['名称','最新','涨幅(%)','金额(亿)'] 14 | 15 | indexes = ['000001.SH', # 上证指数 16 | '399001.SZ', # 深证成指 17 | '399006.SZ', # 创业板指 18 | '399005.SZ' # 中小板指 19 | ] 20 | 21 | 22 | def __init__(self, eventEngine, name=None): 23 | super().__init__(None, True, False) 24 | 25 | self._eventEngine = eventEngine 26 | self._name = name 27 | self._filter = DyStockMarketFilter(DyStockMarketIndexMonitorWidget.indexes) 28 | self._latestTickTime = None 29 | self._latestFreq = 'N/A' 30 | 31 | self.setColNames(DyStockMarketIndexMonitorWidget.header) 32 | self.setAutoForegroundCol('涨幅(%)') 33 | 34 | self._registerEvent() 35 | 36 | def _stockMarketTicksHandler(self, event): 37 | ticks = self._filter.filter(event.data) 38 | 39 | # update UI table 40 | for code, tickData in ticks.items(): 41 | rowData = [tickData.name, 42 | tickData.price, 43 | (tickData.price - tickData.preClose)*100/tickData.preClose, 44 | tickData.amount/10**8 45 | ] 46 | 47 | self[code] = rowData 48 | 49 | # get latest index tick 50 | shTickData = ticks.get(DyStockCommon.shIndex) 51 | szTickData = ticks.get(DyStockCommon.szIndex) 52 | 53 | if shTickData is not None and szTickData is not None: 54 | tickData = shTickData if shTickData.time > szTickData.time else szTickData 55 | elif shTickData is not None: 56 | tickData = shTickData 57 | else: 58 | tickData = szTickData 59 | 60 | if tickData is None: 61 | return 62 | 63 | # 更新频率, 计算新浪更新数据的频率 64 | if self._latestTickTime is not None and tickData.time > self._latestTickTime: 65 | self._latestFreq = DyStockCommon.getTimeInterval(self._latestTickTime, tickData.time) 66 | 67 | title = '{0}[{1}], 频率{2}s'.format(self.parentWidget().windowTitle()[:4], tickData.datetime.strftime('%Y-%m-%d %H:%M:%S'), self._latestFreq) 68 | self.parentWidget().setWindowTitle(title) 69 | 70 | self._latestTickTime = tickData.time 71 | 72 | def _registerEvent(self): 73 | self.signal.connect(self._stockMarketTicksHandler) 74 | self._eventEngine.register(DyEventType.stockMarketTicks, self.signal.emit) 75 | -------------------------------------------------------------------------------- /Stock/Trade/Ui/Basic/StrategyMarket/Data/DyStockTradeStrategyMarketMonitorDataWidget.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | 3 | from DyCommon.Ui.DyTableWidget import * 4 | 5 | 6 | class DyStockTradeStrategyMarketMonitorDataWidget(DyTableWidget): 7 | """ 股票实盘策略数据窗口 """ 8 | 9 | 10 | class LogData: 11 | """ 12 | log-structured storage for clone 13 | """ 14 | def __init__(self): 15 | self.newData = None 16 | self.updatedData = OrderedDict() # {row key: row} 17 | 18 | def init(self, data): 19 | self.newData = data 20 | self.updatedData = OrderedDict() 21 | 22 | def update(self, data): 23 | for row in data: 24 | code = row[0] # pos 0 is code, date or something else, but should be key for one row 25 | self.updatedData[code] = row 26 | 27 | 28 | def __init__(self, strategyCls, parent): 29 | super().__init__(None, True, False) 30 | 31 | self._strategyCls = strategyCls 32 | self._parent = parent 33 | 34 | self._logData = self.LogData() # for clone 35 | 36 | self.setColNames(strategyCls.dataHeader) 37 | self.setAutoForegroundCol('涨幅(%)') 38 | 39 | def update(self, data, newData=False): 40 | """ @data: [[col0, col1, ...]] """ 41 | 42 | if newData: # !!!new, without considering keys 43 | self.fastAppendRows(data, autoForegroundColName='涨幅(%)', new=True) 44 | 45 | self._logData.init(data) 46 | else: # updating by keys 47 | rowKeys = [] 48 | for row in data: 49 | code = row[0] # pos 0 is code, date or something else, but should be key for one row 50 | self[code] = row 51 | 52 | rowKeys.append(code) 53 | 54 | self.setItemsForeground(rowKeys, (('买入', Qt.red), ('卖出', Qt.darkGreen))) 55 | 56 | self._logData.update(data) 57 | 58 | def clone(self): 59 | self_ = self.__class__(self._strategyCls, self._parent) 60 | 61 | # new data 62 | if self._logData.newData is not None: 63 | self_.update(self._logData.newData, newData=True) 64 | 65 | # data with keys 66 | data = [row for _, row in self._logData.updatedData.items()] 67 | if data: 68 | self_.update(data, newData=False) 69 | 70 | return self_ 71 | 72 | def closeEvent(self, event): 73 | self._parent.removeCloneDataWidget(self) 74 | 75 | return super().closeEvent(event) 76 | -------------------------------------------------------------------------------- /Stock/Trade/Ui/Basic/StrategyMarket/Data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Trade/Ui/Basic/StrategyMarket/Data/__init__.py -------------------------------------------------------------------------------- /Stock/Trade/Ui/Basic/StrategyMarket/Ind/Account/DyStockTradeStrategyAccountWidget.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QTabWidget 2 | 3 | from .DyStockTradeStrategyDealsWidget import * 4 | from .DyStockTradeStrategyEntrustsWidget import * 5 | from .DyStockTradeStrategyPosWidget import * 6 | from ......DyStockTradeCommon import * 7 | 8 | 9 | class DyStockTradeStrategyAccountWidget(QTabWidget): 10 | """ 策略账户窗口 """ 11 | 12 | def __init__(self, eventEngine, strategyCls): 13 | super().__init__() 14 | 15 | self._posWidget = DyStockTradeStrategyPosWidget(eventEngine, strategyCls) 16 | self.addTab(self._posWidget, '账户:%s'%DyStockTradeCommon.accountMap[strategyCls.broker]) 17 | 18 | self._entrustsWidget = DyStockTradeStrategyEntrustsWidget() 19 | self.addTab(self._entrustsWidget, '委托') 20 | 21 | self._dealsWidget = DyStockTradeStrategyDealsWidget() 22 | self.addTab(self._dealsWidget, '成交') 23 | 24 | def closeEvent(self, event): 25 | self._posWidget.close() 26 | self._entrustsWidget.close() 27 | self._dealsWidget.close() 28 | 29 | return super().closeEvent(event) 30 | 31 | def updatePos(self, event): 32 | self._posWidget.update(event.data) 33 | 34 | def updateEntrusts(self, event): 35 | self._entrustsWidget.update(event.data) 36 | 37 | def updateDeals(self, event): 38 | self._dealsWidget.update(event.data) 39 | 40 | 41 | -------------------------------------------------------------------------------- /Stock/Trade/Ui/Basic/StrategyMarket/Ind/Account/DyStockTradeStrategyDealsWidget.py: -------------------------------------------------------------------------------- 1 | from DyCommon.Ui.DyTableWidget import * 2 | 3 | 4 | class DyStockTradeStrategyDealsWidget(DyTableWidget): 5 | """ 策略成交窗口,不是当日成交是策略启动后的成交 """ 6 | 7 | header = ['DY成交号', '券商成交号', '成交时间', '代码', '名称', '类型', '成交价(元)', '成交数量(股)', '成交额(元)'] 8 | 9 | def __init__(self): 10 | super().__init__(readOnly=True, index=True, floatRound=3) 11 | 12 | self.setColNames(self.header) 13 | 14 | def update(self, deals): 15 | """ 16 | @deals: [DyStockDeal] 17 | """ 18 | rowKeys = [] 19 | for deal in deals: 20 | rowNbr = self.appendRow([deal.dyDealId, deal.brokerDealId, 21 | deal.datetime, # 成交时间,来自券商的格式,这边不做处理 22 | deal.code, deal.name, 23 | deal.type, 24 | deal.price, deal.volume, 25 | deal.price*deal.volume 26 | ]) 27 | 28 | rowKeys.append(rowNbr) 29 | 30 | self.setItemsForeground(rowKeys, (('买入', Qt.red), ('卖出', Qt.darkGreen))) -------------------------------------------------------------------------------- /Stock/Trade/Ui/Basic/StrategyMarket/Ind/Account/DyStockTradeStrategyEntrustsWidget.py: -------------------------------------------------------------------------------- 1 | from DyCommon.Ui.DyTableWidget import * 2 | 3 | 4 | class DyStockTradeStrategyEntrustsWidget(DyTableWidget): 5 | """ 策略委托窗口,不是当日委托是策略启动后的委托 """ 6 | 7 | header = ['DY委托号', '券商委托号', '委托时间', '代码', '名称', '类型', '委托价(元)', '委托数量(股)', '成交数量(股)', '状态'] 8 | 9 | def __init__(self): 10 | super().__init__(readOnly=True, index=True, floatRound=3) 11 | 12 | self.setColNames(self.header) 13 | 14 | def update(self, entrusts): 15 | """ 16 | @entrusts: OrderedDict or dict only with one entrust. {dyEntrustId: DyStockEntrust} 17 | """ 18 | rowKeys = [] 19 | for dyEntrustId, entrust in entrusts.items(): 20 | self[dyEntrustId] = [entrust.dyEntrustId, entrust.brokerEntrustId, 21 | entrust.entrustDatetime.strftime('%Y-%m-%d %H:%M:%S'), 22 | entrust.code, entrust.name, 23 | entrust.type, 24 | entrust.price, 25 | int(entrust.totalVolume), int(entrust.dealedVolume), 26 | entrust.status 27 | ] 28 | 29 | rowKeys.append(dyEntrustId) 30 | 31 | self.setItemsForeground(rowKeys, (('买入', Qt.red), ('卖出', Qt.darkGreen))) -------------------------------------------------------------------------------- /Stock/Trade/Ui/Basic/StrategyMarket/Ind/Account/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Trade/Ui/Basic/StrategyMarket/Ind/Account/__init__.py -------------------------------------------------------------------------------- /Stock/Trade/Ui/Basic/StrategyMarket/Ind/DyStockTradeStrategyMarketMonitorOpWidget.py: -------------------------------------------------------------------------------- 1 | from DyCommon.Ui.DyTableWidget import * 2 | 3 | 4 | class DyStockTradeStrategyMarketMonitorOpWidget(DyTableWidget): 5 | """ 股票实盘策略操作窗口 """ 6 | 7 | def __init__(self, strategyCls): 8 | super().__init__(readOnly=True, index=False, floatRound=3) 9 | 10 | self._strategyCls = strategyCls 11 | 12 | self.setColNames(strategyCls.opHeader) 13 | 14 | self._pnlColPos = [] 15 | for i, name in enumerate(strategyCls.opHeader, 1 if self.hasIndex() else 0): 16 | if '盈亏' in name: 17 | self._pnlColPos.append(i) 18 | 19 | def update(self, data): 20 | """ @data: [[col0,col1,...]] """ 21 | 22 | self.setSortingEnabled(False) 23 | 24 | rowKeys = [] 25 | for row in data: 26 | rowPos = self.appendRow(row, disableSorting=False) 27 | rowKeys.append(rowPos) 28 | 29 | # 设置盈亏前景色 30 | for col in self._pnlColPos: 31 | item = self.item(rowPos, col) 32 | itemData = item.data(self._role) 33 | 34 | try: 35 | if itemData > 0: 36 | item.setForeground(Qt.red) 37 | elif itemData < 0: 38 | item.setForeground(Qt.darkGreen) 39 | except: 40 | pass 41 | 42 | self.setItemsForeground(rowKeys, (('买入', Qt.red), ('卖出', Qt.darkGreen))) 43 | 44 | self.setSortingEnabled(True) 45 | -------------------------------------------------------------------------------- /Stock/Trade/Ui/Basic/StrategyMarket/Ind/DyStockTradeStrategyMarketMonitorSignalDetailsWidget.py: -------------------------------------------------------------------------------- 1 | from DyCommon.Ui.DyTableWidget import * 2 | 3 | 4 | class DyStockTradeStrategyMarketMonitorSignalDetailsWidget(DyTableWidget): 5 | """ 股票实盘策略信号明细窗口 """ 6 | 7 | def __init__(self, strategyCls): 8 | super().__init__(None, True, False, floatRound=3) 9 | 10 | self._strategyCls = strategyCls 11 | 12 | self.setColNames(strategyCls.signalDetailsHeader) 13 | 14 | def update(self, data): 15 | """ @data: [[col0, col1, ...]] """ 16 | 17 | self.setSortingEnabled(False) 18 | 19 | rowKeys = [] 20 | for row in data: 21 | rowPos = self.appendRow(row, disableSorting=False) 22 | 23 | rowKeys.append(rowPos) 24 | 25 | self.setItemsForeground(rowKeys, (('买入', Qt.red), ('卖出', Qt.darkGreen))) 26 | 27 | self.setSortingEnabled(True) 28 | -------------------------------------------------------------------------------- /Stock/Trade/Ui/Basic/StrategyMarket/Ind/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Trade/Ui/Basic/StrategyMarket/Ind/__init__.py -------------------------------------------------------------------------------- /Stock/Trade/Ui/Basic/StrategyMarket/Other/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Trade/Ui/Basic/StrategyMarket/Other/__init__.py -------------------------------------------------------------------------------- /Stock/Trade/Ui/Basic/StrategyMarket/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Trade/Ui/Basic/StrategyMarket/__init__.py -------------------------------------------------------------------------------- /Stock/Trade/Ui/Basic/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from Stock import DynamicLoadStrategyFields 4 | 5 | 6 | # dynamically load strategies from Stock/Trade/Strategy 7 | __pathList = os.path.dirname(__file__).split(os.path.sep) 8 | __stratgyPath = os.path.sep.join(__pathList[:-2] + ['Strategy']) 9 | 10 | DyStockTradeStrategyClsMap = {} 11 | DyStockTradeStrategyWidgetAutoFields = DynamicLoadStrategyFields(__stratgyPath, 'Stock.Trade.Strategy', DyStockTradeStrategyClsMap) 12 | -------------------------------------------------------------------------------- /Stock/Trade/Ui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Trade/Ui/__init__.py -------------------------------------------------------------------------------- /Stock/Trade/WeChat/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/Stock/Trade/WeChat/__init__.py -------------------------------------------------------------------------------- /Stock/Trade/__init__.py: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /Stock/__init__.py: -------------------------------------------------------------------------------- 1 | # Check user package intelligently 2 | from Stock.Common.DyStockCommon import DyStockCommon 3 | 4 | try: 5 | from WindPy import * 6 | except ImportError: 7 | if DyStockCommon.WindPyInstalled: 8 | print("DevilYuan-Warnning: Import WindPy error, switch default data source of stock history days to TuShare!") 9 | DyStockCommon.WindPyInstalled = False 10 | 11 | 12 | import os 13 | import importlib 14 | 15 | 16 | # dynamically load strategies from specific package, like 'Stock.Trade.Strategy' 17 | def __loadStrategies(dir, packageCommonPrefix, strategyClsMap, onlyDir=True): 18 | fields = [] 19 | for root, dirs, files in os.walk(dir): 20 | for dirName in dirs: 21 | if dirName[:2] == '__': # python internal dirs 22 | continue 23 | 24 | dirFields = [] 25 | fields.append(dirFields) 26 | 27 | dirFields.append(dirName) 28 | retFields = __loadStrategies(os.path.sep.join([dir, dirName]), packageCommonPrefix, strategyClsMap, onlyDir=False) 29 | if retFields: 30 | dirFields.append(retFields) 31 | 32 | if not onlyDir: 33 | packageName = dir.replace(os.path.sep, '.') 34 | packagePrefixStart = packageName.index(packageCommonPrefix) 35 | packagePrefix = packageName[packagePrefixStart:] 36 | 37 | for file in files: 38 | if file == '__init__.py' or file[-3:] != '.py': 39 | continue 40 | 41 | module = importlib.import_module('{}.{}'.format(packagePrefix, file[:-3])) 42 | 43 | strategyClsName = file[:-3] 44 | strategyCls = module.__getattribute__(strategyClsName) 45 | 46 | strategyClsMap[strategyClsName] = strategyCls 47 | 48 | fields.append([strategyCls]) # strategy class 49 | 50 | break 51 | 52 | return fields 53 | 54 | def DynamicLoadStrategyFields(dir, packageCommonPrefix, strategyClsMap): 55 | """ 56 | @strategyClsMap: {strategy class name: strategy class}, out parameter 57 | """ 58 | return __loadStrategies(dir, packageCommonPrefix, strategyClsMap, onlyDir=True) -------------------------------------------------------------------------------- /docs/backtesting/config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/docs/backtesting/config.png -------------------------------------------------------------------------------- /docs/backtesting/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/docs/backtesting/result.png -------------------------------------------------------------------------------- /docs/backtesting/resultDetails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/docs/backtesting/resultDetails.png -------------------------------------------------------------------------------- /docs/brief_introduction.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/docs/brief_introduction.pdf -------------------------------------------------------------------------------- /docs/config/Config.md: -------------------------------------------------------------------------------- 1 | # 股票历史日线数据源 2 | 主窗口菜单:配置 -> 股票历史数据源 3 | - 使用TuSharePro做为历史日线数据源 4 | ![image](https://github.com/moyuanz/DevilYuan/blob/master/docs/config/Config_TuSharePro.png) 5 | # MongoDB 6 | 主窗口菜单:配置 -> MongoDB,这个是配置对应的数据库名和表名 7 | # 实盘交易 8 | ### 微信 9 | 主窗口菜单:配置 -> 实盘 -> 微信,这个是配置通过方糖微信提醒 10 | ### 账号 11 | 主窗口菜单:配置 -> 实盘 -> 账号,这个是配置实盘交易的账号信息 12 | -------------------------------------------------------------------------------- /docs/config/Config_TuSharePro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/docs/config/Config_TuSharePro.png -------------------------------------------------------------------------------- /docs/data/DownloadHistoryData.md: -------------------------------------------------------------------------------- 1 | # 下载股票历史数据 2 | 股票历史数据支持的类型: 3 | - 公用数据 4 | - 股票代码表 5 | - 股票交易日数据 6 | - 个股数据 7 | - 个股,指数和ETF(50,300,500)历史日线数据,含如下指标 8 | - OHLCV 9 | - 成交额 10 | - 换手率 11 | - 复权因子 12 | - 个股和ETF(50,300,500)历史分笔数据 13 | - 腾讯和通达信好像不支持大盘指数的历史分笔,如果策略需要参考指数分笔数据,可以用ETF50,ETF300,ETF500代替 14 | 15 | # 自动更新方式 16 | 1. 手动下载某一历史**交易日**的日线数据,日期格式是yyyy-mm-dd。下载历史日线数据时,会同时下载股票代码表和对应的交易日数据。 17 | ![image](https://github.com/moyuanz/DevilYuan/blob/master/docs/data/mannualDaysConfig.png) 18 | 2. 手动下载步骤1里的对应的**交易日**的历史分笔数据 19 | ![image](https://github.com/moyuanz/DevilYuan/blob/master/docs/data/mannualTicksConfig.png) 20 | 3. 一键更新,自动更新历史日线和分笔数据到今天。如果今天是交易日,**请在18:30后更新**,因为数据源也需要数据落库。 21 | ![image](https://github.com/moyuanz/DevilYuan/blob/master/docs/data/result.png) 22 | 23 | **注意:一键更新会读取数据库最新的交易日数据,根据数据库里最新的日期,自动更新到今天。也就是锚是交易日最新日期。如果分笔数据的最新日期比交易日数据早,则一键更新会导致分笔数据的缺失。当然你可以用手动更新分笔数据,来下载缺失的数据。程序会根据数据库的数据自己判断是否要下载数据。** 24 | 25 | 26 | # 手动更新方式 27 | 1. 手动更新日线数据,输入开始日期和结束日期。如果结束日期正好是今天,并且今天是交易日,**请在18:30后更新**,因为数据源也需要数据落库。 28 | 2. 手动更新分笔数据,输入开始日期和结束日期。分笔数据的时间段可以是日线数据时间段的子集,或者一样。因为每次下载分笔数据,程序会自动根据日线数据判断出个股有没有停牌,停牌日期无须下载个股分笔数据。但建议两者时间段是一样的,这样自己不会把数据搞错位了。 29 | 30 | # 定时一键更新 31 | 启动此功能时,程序会每天定时在18:30过几分钟的时间发起自动更新,不管当天是否是交易日。由于Python的GIL原因,**请独立开启一个程序运行定时一键更新**。 32 | 33 | # 注意 34 | - 当日线数据用TuShare或者TuSharePro更新时,ETF的日线数据缺少复权因子,换手率和成交额。策略中使用ETF日线数据,要注意这种情况。 35 | - 个股TuShare日线数据的复权因子好像不是特别准确。建议用TuSharePro。 36 | - 由于Wind会对免费账号做限流,所以下载数据最好是一段一段的下载。比如每次下载三个月或者一个月的数据。 37 | - 历史分笔数据会优先从腾讯下载,如果没有则从通达信下载。通达信的分笔数据没有秒的信息,DevilYuan系统使用随机算法生成秒的信息。 38 | - 若日线数据和分笔数据下载有缺失,log会分别用抬头¥DyStockDataDaysEngine¥和¥DyStockDataTicksEngine¥标示。用户可以利用此信息,再次下载补全数据。 39 | - TuShare和TuSharePro的股票代码表,交易日数据和日线数据共用数据库的表,如果日线数据下载既使用了TuShare又使用了TuSharePro,会导致数据的不一致性。 40 | -------------------------------------------------------------------------------- /docs/data/mannualDaysConfig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/docs/data/mannualDaysConfig.png -------------------------------------------------------------------------------- /docs/data/mannualTicksConfig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/docs/data/mannualTicksConfig.png -------------------------------------------------------------------------------- /docs/data/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/docs/data/result.png -------------------------------------------------------------------------------- /docs/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/docs/main.png -------------------------------------------------------------------------------- /docs/select/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/docs/select/result.png -------------------------------------------------------------------------------- /docs/trade/WriteATradeStrategy.md: -------------------------------------------------------------------------------- 1 | # 写一个实盘策略 2 | 在Stock\Trade\Strategy\Cta下添加一个策略文件,或者你可以在Stock\Trade\Strategy下创建新的目录夹,然后把策略添加到这个新目录夹下 3 | ![image](https://github.com/moyuanz/DevilYuan/blob/master/docs/trade/strategyPath.png) 4 | 5 | ### 注意 6 | 由于支持自动载入策略到UI,策略的类名一定要跟策略的文件名一致。 7 | 8 | # 策略回测 9 | 在主窗口打开策略回测窗口,勾选对应的策略即可回测。回测采用的类事件方式,跟实盘保持一致,这样导致回测比较慢。 10 | ![image](https://github.com/moyuanz/DevilYuan/blob/master/docs/backtesting/resultDetails.png) 11 | 12 | # 策略实盘 13 | 在主窗口打开实盘窗口。 14 | ![image](https://github.com/moyuanz/DevilYuan/blob/master/docs/trade/trade.png) 15 | 16 | 17 | # 策略文件解析 18 | -------------------------------------------------------------------------------- /docs/trade/strategyPath.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/docs/trade/strategyPath.png -------------------------------------------------------------------------------- /docs/trade/strategyUi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/docs/trade/strategyUi.png -------------------------------------------------------------------------------- /docs/trade/trade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/docs/trade/trade.png -------------------------------------------------------------------------------- /docs/trade/trade_xmind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/docs/trade/trade_xmind.png -------------------------------------------------------------------------------- /docs/trade/yhLoginUI.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackfrued/DevilYuan/b99a37e933c96f701ab5d175274826e13ff7eeec/docs/trade/yhLoginUI.jpg --------------------------------------------------------------------------------